// Loads demo data into an empty database: `npm run db:seed`. Not for production use. import { hashPassword } from '../auth/password.ts' import { loadConfig } from '../config.ts' import { openDatabase } from './client.ts' import { auditLog, areas, incidentCounters, incidents, organizations, sites, users } from './schema.ts' const DEMO_PASSWORD = 'safetylog-demo' const config = loadConfig() const { db, close } = await openDatabase(config.databaseUrl) try { if ((await db.select({ id: users.id }).from(users).limit(1)).length > 0) { console.error('Database already has users; seeding is only for empty databases.') process.exitCode = 1 } else { await db.transaction(async (tx) => { const [org] = await tx.insert(organizations).values({ name: 'Northstar Co.' }).returning() const passwordHash = await hashPassword(DEMO_PASSWORD) const people = await tx .insert(users) .values([ { name: 'Jordan Lee', email: 'admin@example.com', role: 'admin' as const }, { name: 'Priya Shah', email: 'manager@example.com', role: 'safety_manager' as const }, { name: 'Sam Rivera', email: 'supervisor@example.com', role: 'supervisor' as const }, { name: 'Maya Chen', email: 'reporter@example.com', role: 'reporter' as const }, ].map((u) => ({ ...u, organizationId: org.id, passwordHash }))) .returning() const person = (email: string) => people.find((p) => p.email === email)!.id const layout: Record = { 'North Campus': ['Building A', 'Warehouse 04', 'Lab 2'], 'South Campus': ['Fabrication 02', 'Yard', 'Office block'], 'West Campus': ['Warehouse 01', 'Loading dock'], } const areaIds: Record = {} for (const [siteName, buildings] of Object.entries(layout)) { const [site] = await tx.insert(sites).values({ organizationId: org.id, name: siteName }).returning() for (const name of buildings) { const [area] = await tx.insert(areas).values({ siteId: site.id, name, kind: 'building' }).returning() areaIds[name] = { siteId: site.id, areaId: area.id } } } const daysAgo = (days: number) => new Date(Date.now() - days * 86_400_000) const demo = [ { title: 'Damaged guardrail on mezzanine', type: 'property_damage', severity: 'low', status: 'closed', building: 'Warehouse 01', locationDetail: 'Mezzanine east', reporter: 'manager@example.com', days: 9 }, { title: 'Hand laceration during material handling', type: 'injury', severity: 'high', status: 'investigation', building: 'Fabrication 02', locationDetail: 'Line 3', reporter: 'supervisor@example.com', days: 7 }, { title: 'Wet floor near employee entrance', type: 'hazard', severity: 'medium', status: 'actions', building: 'Building A', locationDetail: 'Ground floor lobby', reporter: 'reporter@example.com', days: 6 }, { title: 'Forklift reversed into pallet stack', type: 'near_miss', severity: 'high', status: 'triage', building: 'Warehouse 04', locationDetail: 'Loading bay C', reporter: 'admin@example.com', days: 5 }, ] as const const created = await tx .insert(incidents) .values( demo.map((item, index) => ({ organizationId: org.id, number: index + 1, title: item.title, type: item.type, severity: item.severity, status: item.status, occurredAt: daysAgo(item.days), createdAt: new Date(daysAgo(item.days).getTime() + 2 * 3_600_000), ...areaIds[item.building], locationDetail: item.locationDetail, reporterId: person(item.reporter), closedAt: item.status === 'closed' ? daysAgo(item.days - 3) : null, })), ) .returning() await tx.insert(auditLog).values( created.map((incident) => ({ organizationId: org.id, actorId: incident.reporterId, entityType: 'incident', entityId: incident.id, action: 'create', changes: { number: incident.number, title: incident.title }, createdAt: incident.createdAt, })), ) await tx.insert(incidentCounters).values({ organizationId: org.id, lastNumber: demo.length }) }) console.log(`Seeded demo data. Sign in as admin@example.com, manager@example.com, supervisor@example.com or reporter@example.com with password "${DEMO_PASSWORD}".`) } } finally { await close() }