feat: implement incident reporting and management system
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s

- Add incident reporting routes and logic to handle creation, updating, and retrieval of incidents.
- Implement visibility rules for users based on roles (reporters, supervisors).
- Create tests for incident reporting functionality to ensure correctness and reliability.
- Introduce site and area management routes, including validation for area hierarchy.
- Establish user management routes with role-based access control for creating and updating users.
- Add setup routes for initializing the application with an organization and admin user.
- Implement shared enums and schemas for consistent validation and data handling across the application.
- Create a test helper for setting up an in-memory database and API client for testing.
- Add TypeScript configuration for server-side code with strict type checking and module resolution.
This commit is contained in:
jayjennings committed 2026-10-06 10:51:18 -07:00
1 parent a382fa904a
commit c69eccb5c1
47 files changed
+6297 -45

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
/** Fixed-window in-memory limiter. Fine for a single server process; move to the database if we scale out. */
export class RateLimiter {
private hits = new Map<string, { count: number; resetAt: number }>()
private readonly limit: number
private readonly windowMs: number
constructor(limit: number, windowMs: number) {
this.limit = limit
this.windowMs = windowMs
}
/** Records an attempt; returns false once the key has exceeded the limit. */
attempt(key: string, now = Date.now()): boolean {
const entry = this.hits.get(key)
if (!entry || entry.resetAt <= now) {
this.hits.set(key, { count: 1, resetAt: now + this.windowMs })
if (this.hits.size > 10_000) this.prune(now)
return true
}
entry.count += 1
return entry.count <= this.limit
}
reset(key: string): void {
this.hits.delete(key)
}
private prune(now: number) {
for (const [key, entry] of this.hits) if (entry.resetAt <= now) this.hits.delete(key)
}
}