- 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.
43 lines
1.8 KiB
TypeScript
43 lines
1.8 KiB
TypeScript
import { sql } from 'drizzle-orm'
|
|
import { Hono } from 'hono'
|
|
import { setupSchema } from '../../shared/schemas.ts'
|
|
import { startSession } from '../auth/middleware.ts'
|
|
import { hashPassword } from '../auth/password.ts'
|
|
import type { Db } from '../db/client.ts'
|
|
import { organizations, users } from '../db/schema.ts'
|
|
import { audit } from '../lib/audit.ts'
|
|
import { validate, type AppEnv } from '../lib/http.ts'
|
|
import { publicUser } from './users.ts'
|
|
|
|
const SETUP_LOCK = 717_001
|
|
|
|
async function hasUsers(db: Db) {
|
|
const rows = await db.select({ id: users.id }).from(users).limit(1)
|
|
return rows.length > 0
|
|
}
|
|
|
|
/** First-run setup: creates the organization and its first admin. Only works while there are no users. */
|
|
export const setupRoutes = new Hono<AppEnv>()
|
|
.get('/', async (c) => c.json({ needsSetup: !(await hasUsers(c.get('db'))) }))
|
|
|
|
.post('/', validate('json', setupSchema), async (c) => {
|
|
const input = c.req.valid('json')
|
|
const passwordHash = await hashPassword(input.password)
|
|
|
|
const user = await c.get('db').transaction(async (tx) => {
|
|
await tx.execute(sql`select pg_advisory_xact_lock(${SETUP_LOCK})`)
|
|
if (await hasUsers(tx)) return null
|
|
const [organization] = await tx.insert(organizations).values({ name: input.organizationName }).returning()
|
|
const [admin] = await tx
|
|
.insert(users)
|
|
.values({ organizationId: organization.id, email: input.email, name: input.name, role: 'admin', passwordHash })
|
|
.returning(publicUser)
|
|
await audit(tx, { organizationId: organization.id, actorId: admin.id, entityType: 'organization', entityId: organization.id, action: 'setup' })
|
|
return admin
|
|
})
|
|
if (!user) return c.json({ error: 'Setup has already been completed' }, 409)
|
|
|
|
await startSession(c, user.id)
|
|
return c.json({ user }, 201)
|
|
})
|