Files
open-safetylog-ehs/server/app.ts
T
jayjennings 3e964d1176
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s
feat(i18n): add internationalization support with English and Spanish translations
feat(kiosk): implement kiosk report submission functionality with offline support

test(outbox): add tests for outbox functionality including report submission and error handling

feat(outbox): create outbox management for queued reports with IndexedDB persistence

feat(uuid): add UUID generation utility for client IDs

feat(qr): implement QR code generation for reporting links by site and area

feat(kiosk): create Kiosk page for reporting incidents without an account
2026-10-06 11:38:40 -07:00

72 lines
2.6 KiB
TypeScript

import { Hono } from 'hono'
import { csrf } from 'hono/csrf'
import { HTTPException } from 'hono/http-exception'
import { secureHeaders } from 'hono/secure-headers'
import { loadSession } from './auth/middleware.ts'
import type { Config } from './config.ts'
import type { Db } from './db/client.ts'
import type { AppEnv } from './lib/http.ts'
import type { Notifier } from './notifications.ts'
import { actionRoutes } from './routes/actions.ts'
import { attachmentRoutes } from './routes/attachments.ts'
import { authRoutes } from './routes/auth.ts'
import { incidentRoutes } from './routes/incidents.ts'
import { kioskAdminRoutes, kioskPublicRoutes } from './routes/kiosks.ts'
import { rootCauseRoutes } from './routes/root-causes.ts'
import { setupRoutes } from './routes/setup.ts'
import { areaRoutes, siteRoutes } from './routes/sites.ts'
import { statsRoutes } from './routes/stats.ts'
import { userRoutes } from './routes/users.ts'
import type { FileStorage } from './storage.ts'
export interface AppDeps {
db: Db
config: Config
notifier: Notifier
storage: FileStorage
}
export function createApp({ db, config, notifier, storage }: AppDeps) {
const app = new Hono<AppEnv>()
app.use('*', secureHeaders())
const publicOrigin = new URL(config.publicUrl).origin
// Form/multipart posts must come from our own pages. JSON requests are already protected by CORS preflight.
const sameSite = csrf({ origin: (origin, c) => origin === publicOrigin || origin === new URL(c.req.url).origin })
app.use('/api/*', sameSite, async (c, next) => {
c.set('db', db)
c.set('config', config)
c.set('notifier', notifier)
c.set('storage', storage)
c.header('Cache-Control', 'no-store')
await next()
})
app.use('/api/*', loadSession)
app.get('/api/health', (c) => c.json({ ok: true }))
app.route('/api/setup', setupRoutes)
app.route('/api/auth', authRoutes())
app.route('/api/users', userRoutes)
app.route('/api/sites', siteRoutes)
app.route('/api/areas', areaRoutes)
app.route('/api/incidents', incidentRoutes)
app.route('/api/root-causes', rootCauseRoutes)
app.route('/api/actions', actionRoutes)
app.route('/api/attachments', attachmentRoutes)
app.route('/api/stats', statsRoutes)
app.route('/api/kiosks', kioskAdminRoutes)
app.route('/api/kiosk', kioskPublicRoutes())
app.all('/api/*', (c) => c.json({ error: 'Not found' }, 404))
app.onError((error, c) => {
if (error instanceof HTTPException) return c.json({ error: error.message }, error.status)
console.error(error)
return c.json({ error: 'Internal server error' }, 500)
})
return app
}
export type App = ReturnType<typeof createApp>