feat(db): Drizzle DAL with withUser/asAdmin GUC wrapper (Phase 2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
import {
|
||||
pgTable,
|
||||
pgEnum,
|
||||
uuid,
|
||||
text,
|
||||
boolean,
|
||||
timestamp,
|
||||
smallint,
|
||||
integer,
|
||||
date,
|
||||
jsonb,
|
||||
customType,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Custom type: pgvector
|
||||
// ---------------------------------------------------------------------------
|
||||
const vector = customType<{ data: number[]; driverData: string }>({
|
||||
dataType(config) {
|
||||
return `vector(${(config as { dimensions?: number }).dimensions ?? 1536})`
|
||||
},
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Enums
|
||||
// ---------------------------------------------------------------------------
|
||||
export const userRoleEnum = pgEnum('user_role', [
|
||||
'reporter', 'supervisor', 'hse', 'capa_owner', 'management', 'admin',
|
||||
])
|
||||
|
||||
export const incidentTypeEnum = pgEnum('incident_type', [
|
||||
'injury', 'near_miss', 'hazard', 'asset_damage', 'environmental',
|
||||
'security', 'fire', 'transport',
|
||||
])
|
||||
|
||||
export const incidentStatusEnum = pgEnum('incident_status', [
|
||||
'reported', 'triaged', 'investigating', 'capa_pending', 'verification', 'closed',
|
||||
])
|
||||
|
||||
export const medicalStatusEnum = pgEnum('medical_status', [
|
||||
'none', 'first_aid', 'medical_treatment', 'lti',
|
||||
])
|
||||
|
||||
export const evidenceStageEnum = pgEnum('evidence_stage', [
|
||||
'report', 'response', 'investigation', 'capa', 'verification',
|
||||
])
|
||||
|
||||
export const rcaMethodEnum = pgEnum('rca_method', ['five_why', 'fishbone', 'other'])
|
||||
|
||||
export const capaPriorityEnum = pgEnum('capa_priority', ['low', 'med', 'high'])
|
||||
|
||||
export const capaStatusEnum = pgEnum('capa_status', [
|
||||
'open', 'in_progress', 'overdue', 'pending_verification',
|
||||
'verified', 'reopened', 'closed',
|
||||
])
|
||||
|
||||
export const doshFormTypeEnum = pgEnum('dosh_form_type', ['jkkp6', 'jkkp7', 'jkkp8'])
|
||||
export const doshStatusEnum = pgEnum('dosh_status', ['not_required', 'pending', 'submitted'])
|
||||
|
||||
export const notificationChannelEnum = pgEnum('notification_channel', [
|
||||
'email', 'whatsapp', 'in_app',
|
||||
])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tables (dependency order)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const sites = pgTable('sites', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull(),
|
||||
address: text('address'),
|
||||
region: text('region'),
|
||||
active: boolean('active').notNull().default(true),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const zones = pgTable('zones', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
siteId: uuid('site_id').notNull().references(() => sites.id),
|
||||
name: text('name').notNull(),
|
||||
qrCodeToken: text('qr_code_token').notNull().unique(),
|
||||
active: boolean('active').notNull().default(true),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull().default(''),
|
||||
email: text('email').notNull().default('').unique(),
|
||||
phone: text('phone'),
|
||||
role: userRoleEnum('role').notNull().default('reporter'),
|
||||
department: text('department'),
|
||||
siteId: uuid('site_id').references(() => sites.id),
|
||||
active: boolean('active').notNull().default(true),
|
||||
passwordHash: text('password_hash').notNull().default(''),
|
||||
emailVerifiedAt: timestamp('email_verified_at', { withTimezone: true }),
|
||||
lastLoginAt: timestamp('last_login_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const trucks = pgTable('trucks', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
truckNo: text('truck_no').notNull().unique(),
|
||||
carrier: text('carrier'),
|
||||
active: boolean('active').notNull().default(true),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const incidents = pgTable('incidents', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
referenceNo: text('reference_no').unique(),
|
||||
incidentType: incidentTypeEnum('incident_type').notNull(),
|
||||
siteId: uuid('site_id').notNull().references(() => sites.id),
|
||||
zoneId: uuid('zone_id').references(() => zones.id),
|
||||
truckId: uuid('truck_id').references(() => trucks.id),
|
||||
reportedBy: uuid('reported_by').notNull().references(() => users.id),
|
||||
reportedAt: timestamp('reported_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
description: text('description').notNull(),
|
||||
severity: smallint('severity'),
|
||||
status: incidentStatusEnum('status').notNull().default('reported'),
|
||||
injuryInvolved: boolean('injury_involved').notNull().default(false),
|
||||
assetInvolved: boolean('asset_involved').notNull().default(false),
|
||||
medicalStatus: medicalStatusEnum('medical_status'),
|
||||
lostDays: integer('lost_days'),
|
||||
isFatality: boolean('is_fatality').notNull().default(false),
|
||||
isSeriousBodilyInjury: boolean('is_serious_bodily_injury').notNull().default(false),
|
||||
isDangerousOccurrence: boolean('is_dangerous_occurrence').notNull().default(false),
|
||||
isOccupationalDisease: boolean('is_occupational_disease').notNull().default(false),
|
||||
triageNotes: text('triage_notes'),
|
||||
triagedBy: uuid('triaged_by').references(() => users.id),
|
||||
triagedAt: timestamp('triaged_at', { withTimezone: true }),
|
||||
typeDetails: jsonb('type_details'),
|
||||
embedding: vector('embedding', { dimensions: 768 }),
|
||||
closedAt: timestamp('closed_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const evidenceFiles = pgTable('evidence_files', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
incidentId: uuid('incident_id').notNull().references(() => incidents.id),
|
||||
stage: evidenceStageEnum('stage').notNull(),
|
||||
fileUrl: text('file_url').notNull(),
|
||||
fileType: text('file_type').notNull(),
|
||||
fileHash: text('file_hash').notNull(),
|
||||
uploadedBy: uuid('uploaded_by').notNull().references(() => users.id),
|
||||
uploadedAt: timestamp('uploaded_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
deleted: boolean('deleted').notNull().default(false),
|
||||
})
|
||||
|
||||
export const investigations = pgTable('investigations', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
incidentId: uuid('incident_id').notNull().references(() => incidents.id),
|
||||
investigatorId: uuid('investigator_id').notNull().references(() => users.id),
|
||||
method: rcaMethodEnum('method').notNull().default('five_why'),
|
||||
findingsText: text('findings_text'),
|
||||
rootCauseSummary: text('root_cause_summary'),
|
||||
alcoholTestResult: text('alcohol_test_result'),
|
||||
urineTestResult: text('urine_test_result'),
|
||||
witnessStatementRefs: text('witness_statement_refs').array(),
|
||||
fiveWhySteps: jsonb('five_why_steps'),
|
||||
fishboneCategories: jsonb('fishbone_categories'),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const capaActions = pgTable('capa_actions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
incidentId: uuid('incident_id').notNull().references(() => incidents.id),
|
||||
rootCauseRef: text('root_cause_ref'),
|
||||
description: text('description').notNull(),
|
||||
ownerUserId: uuid('owner_user_id').notNull().references(() => users.id),
|
||||
department: text('department').notNull(),
|
||||
dueDate: date('due_date').notNull(),
|
||||
priority: capaPriorityEnum('priority').notNull().default('med'),
|
||||
status: capaStatusEnum('status').notNull().default('open'),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
verifiedBy: uuid('verified_by').references(() => users.id),
|
||||
verifiedAt: timestamp('verified_at', { withTimezone: true }),
|
||||
effectivenessRecheckDate: date('effectiveness_recheck_date'),
|
||||
effectivenessRecheckRound: integer('effectiveness_recheck_round').notNull().default(0),
|
||||
ownerNotes: text('owner_notes'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const doshReports = pgTable('dosh_reports', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
incidentId: uuid('incident_id').notNull().references(() => incidents.id),
|
||||
formType: doshFormTypeEnum('form_type').notNull(),
|
||||
status: doshStatusEnum('status').notNull().default('not_required'),
|
||||
submittedAt: timestamp('submitted_at', { withTimezone: true }),
|
||||
submittedBy: uuid('submitted_by').references(() => users.id),
|
||||
fileUrl: text('file_url'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
|
||||
export const notificationsLog = pgTable('notifications_log', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
incidentId: uuid('incident_id').references(() => incidents.id),
|
||||
capaId: uuid('capa_id').references(() => capaActions.id),
|
||||
channel: notificationChannelEnum('channel').notNull(),
|
||||
recipient: text('recipient').notNull(),
|
||||
recipientUserId: uuid('recipient_user_id').references(() => users.id),
|
||||
readAt: timestamp('read_at', { withTimezone: true }),
|
||||
title: text('title'),
|
||||
link: text('link'),
|
||||
sentAt: timestamp('sent_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
status: text('status').notNull().default('sent'),
|
||||
})
|
||||
|
||||
export const auditLog = pgTable('audit_log', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
tableName: text('table_name').notNull(),
|
||||
recordId: uuid('record_id').notNull(),
|
||||
action: text('action').notNull(),
|
||||
changedBy: uuid('changed_by').references(() => users.id),
|
||||
changedAt: timestamp('changed_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
oldValue: jsonb('old_value'),
|
||||
newValue: jsonb('new_value'),
|
||||
})
|
||||
|
||||
export const appSettings = pgTable('app_settings', {
|
||||
key: text('key').primaryKey(),
|
||||
value: text('value').notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedBy: uuid('updated_by').references(() => users.id),
|
||||
})
|
||||
|
||||
export const incidentAddenda = pgTable('incident_addenda', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
incidentId: uuid('incident_id').notNull().references(() => incidents.id),
|
||||
author: uuid('author').notNull().references(() => users.id),
|
||||
body: text('body').notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
})
|
||||
Reference in New Issue
Block a user