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

+29
View File
@@ -0,0 +1,29 @@
import type { Db } from '../db/client.ts'
import { auditLog } from '../db/schema.ts'
interface AuditEntry {
organizationId: string
actorId: string | null
entityType: string
entityId: string
action: string
changes?: Record<string, unknown> | null
}
export async function audit(db: Db, entry: AuditEntry): Promise<void> {
await db.insert(auditLog).values({ ...entry, changes: entry.changes ?? null })
}
const comparable = (value: unknown) => (value instanceof Date ? value.toISOString() : (value ?? null))
/** `{ field: [before, after] }` for each field in `next` whose value differs from `current`. */
export function diff(current: Record<string, unknown>, next: Record<string, unknown>): Record<string, [unknown, unknown]> {
const changes: Record<string, [unknown, unknown]> = {}
for (const [key, value] of Object.entries(next)) {
if (value === undefined) continue
const before = comparable(current[key])
const after = comparable(value)
if (JSON.stringify(before) !== JSON.stringify(after)) changes[key] = [before, after]
}
return changes
}
+33
View File
@@ -0,0 +1,33 @@
import { zValidator } from '@hono/zod-validator'
import type { Context, ValidationTargets } from 'hono'
import { HTTPException } from 'hono/http-exception'
import type { ZodType } from 'zod'
import type { SessionUser } from '../auth/sessions.ts'
import type { Config } from '../config.ts'
import type { Db } from '../db/client.ts'
export interface AppEnv {
Variables: {
db: Db
config: Config
user: SessionUser | null
}
}
/** Validates part of the request, answering 400 with field-level issues on failure. */
export function validate<Target extends keyof ValidationTargets, Schema extends ZodType>(target: Target, schema: Schema) {
return zValidator(target, schema, (result, c) => {
if (!result.success) {
return c.json({ error: 'Invalid request', issues: result.error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })) }, 400)
}
})
}
export const fail = (status: 400 | 401 | 403 | 404 | 409 | 429, message: string) => new HTTPException(status, { message })
/** The signed-in user; use after `requireAuth`. */
export function currentUser(c: Context<AppEnv>): SessionUser {
const user = c.get('user')
if (!user) throw fail(401, 'Sign in required')
return user
}