feat: implement incident reporting and management system
- 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:
1 parent
a382fa904a
commit
c69eccb5c1
47 files changed
+6297
-45
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import { and, eq, gt } from 'drizzle-orm'
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
import type { Role } from '../../shared/enums.ts'
|
||||
import type { Db } from '../db/client.ts'
|
||||
import { sessions, users } from '../db/schema.ts'
|
||||
|
||||
export const SESSION_COOKIE = 'safetylog_session'
|
||||
export const SESSION_TTL_MS = 14 * 24 * 60 * 60 * 1000
|
||||
|
||||
export interface SessionUser {
|
||||
id: string
|
||||
organizationId: string
|
||||
email: string
|
||||
name: string
|
||||
role: Role
|
||||
}
|
||||
|
||||
const hashToken = (token: string) => createHash('sha256').update(token).digest('hex')
|
||||
|
||||
export async function createSession(db: Db, userId: string): Promise<{ token: string; expiresAt: Date }> {
|
||||
const token = randomBytes(32).toString('base64url')
|
||||
const expiresAt = new Date(Date.now() + SESSION_TTL_MS)
|
||||
await db.insert(sessions).values({ id: hashToken(token), userId, expiresAt })
|
||||
return { token, expiresAt }
|
||||
}
|
||||
|
||||
/** Returns the session's user, extending the session once it is past half its lifetime. */
|
||||
export async function validateSession(db: Db, token: string): Promise<{ user: SessionUser; renewedUntil?: Date } | null> {
|
||||
const id = hashToken(token)
|
||||
const [row] = await db
|
||||
.select({
|
||||
expiresAt: sessions.expiresAt,
|
||||
user: { id: users.id, organizationId: users.organizationId, email: users.email, name: users.name, role: users.role },
|
||||
})
|
||||
.from(sessions)
|
||||
.innerJoin(users, eq(users.id, sessions.userId))
|
||||
.where(and(eq(sessions.id, id), gt(sessions.expiresAt, new Date()), eq(users.active, true)))
|
||||
if (!row) return null
|
||||
|
||||
if (row.expiresAt.getTime() - Date.now() < SESSION_TTL_MS / 2) {
|
||||
const renewedUntil = new Date(Date.now() + SESSION_TTL_MS)
|
||||
await db.update(sessions).set({ expiresAt: renewedUntil }).where(eq(sessions.id, id))
|
||||
return { user: row.user, renewedUntil }
|
||||
}
|
||||
return { user: row.user }
|
||||
}
|
||||
|
||||
export async function deleteSession(db: Db, token: string): Promise<void> {
|
||||
await db.delete(sessions).where(eq(sessions.id, hashToken(token)))
|
||||
}
|
||||
|
||||
export async function deleteUserSessions(db: Db, userId: string): Promise<void> {
|
||||
await db.delete(sessions).where(eq(sessions.userId, userId))
|
||||
}
|
||||
Reference in new issue
Block a user