Files
buildfor_life_repair/src/lib/server/db/schema.ts
T
grabowski 117646d2cb Add document uploads to components (PDFs, manuals, datasheets)
- component_documents table matching device_documents structure
- Upload/delete actions on component detail page
- Documents section in sidebar with file icon, filename, delete on hover
- Devices already had document support; components now match

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:03:43 +07:00

319 lines
13 KiB
TypeScript

import {
pgTable,
uuid,
text,
integer,
boolean,
timestamp,
index,
check
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
// ─── Users & Sessions ──────────────────────────────────────────────
export const users = pgTable('users', {
id: text('id').primaryKey(),
email: text('email').unique().notNull(),
displayName: text('display_name'),
passwordHash: text('password_hash').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
export const sessions = pgTable('sessions', {
id: text('id').primaryKey(), // SHA-256 hash of the session token
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull()
});
// ─── Locations ──────────────────────────────────────────────────────
export const locations = pgTable('locations', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
description: text('description'),
parentId: uuid('parent_id').references((): any => locations.id),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
// ─── Devices ────────────────────────────────────────────────────────
export const devices = pgTable(
'devices',
{
id: uuid('id').defaultRandom().primaryKey(),
title: text('title').notNull(),
category: text('category').notNull(),
brand: text('brand'),
model: text('model'),
serialNumber: text('serial_number'),
year: integer('year'),
condition: text('condition').notNull().default('Waiting to be Tested'),
voltage: text('voltage'),
frequency: text('frequency'),
origin: text('origin'),
faultDescription: text('fault_description'),
repairNotes: text('repair_notes'),
locationId: uuid('location_id').references(() => locations.id),
initialCondition: text('initial_condition'),
generalNotes: text('general_notes'),
disabled: boolean('disabled').default(false).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [
index('devices_category_idx').on(table.category),
index('devices_condition_idx').on(table.condition),
index('devices_location_idx').on(table.locationId),
check('devices_category_check', sql`${table.category} IN ('Computer', 'Audio Equipment', 'Peripheral', 'Other')`),
check('devices_condition_check', sql`${table.condition} IN ('Working', 'In Repair', 'Waiting for Repair', 'Waiting to be Tested', 'Unrepairable')`)
]
);
// ─── Computer Details (1:1 extension) ───────────────────────────────
export const computerDetails = pgTable('computer_details', {
id: uuid('id').defaultRandom().primaryKey(),
deviceId: uuid('device_id')
.notNull()
.unique()
.references(() => devices.id, { onDelete: 'cascade' }),
osVersion: text('os_version'),
firmwareVersion: text('firmware_version'),
installedSoftware: text('installed_software')
});
// ─── Device Images ──────────────────────────────────────────────────
export const deviceImages = pgTable(
'device_images',
{
id: uuid('id').defaultRandom().primaryKey(),
deviceId: uuid('device_id')
.notNull()
.references(() => devices.id, { onDelete: 'cascade' }),
filePath: text('file_path').notNull(),
thumbnailPath: text('thumbnail_path'),
caption: text('caption'),
sortOrder: integer('sort_order').default(0),
uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [index('device_images_device_idx').on(table.deviceId)]
);
// ─── Device Documents ───────────────────────────────────────────────
export const deviceDocuments = pgTable(
'device_documents',
{
id: uuid('id').defaultRandom().primaryKey(),
deviceId: uuid('device_id')
.notNull()
.references(() => devices.id, { onDelete: 'cascade' }),
filePath: text('file_path').notNull(),
originalFilename: text('original_filename').notNull(),
fileType: text('file_type'),
description: text('description'),
uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [index('device_documents_device_idx').on(table.deviceId)]
);
// ─── Components ─────────────────────────────────────────────────────
export const components = pgTable(
'components',
{
id: uuid('id').defaultRandom().primaryKey(),
title: text('title').notNull(),
componentType: text('component_type').notNull(),
brand: text('brand'),
partNumber: text('part_number'),
serialNumber: text('serial_number'),
condition: text('condition').notNull().default('Working'),
firmwareVersion: text('firmware_version'),
specs: text('specs'),
notes: text('notes'),
currentDeviceId: uuid('current_device_id').references(() => devices.id, {
onDelete: 'set null'
}),
locationId: uuid('location_id').references(() => locations.id),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [
index('components_type_idx').on(table.componentType),
index('components_device_idx').on(table.currentDeviceId),
index('components_location_idx').on(table.locationId),
check('components_condition_check', sql`${table.condition} IN ('Working', 'Faulty', 'Unknown', 'Refurbished')`)
]
);
// ─── Component Images ───────────────────────────────────────────────
export const componentImages = pgTable(
'component_images',
{
id: uuid('id').defaultRandom().primaryKey(),
componentId: uuid('component_id')
.notNull()
.references(() => components.id, { onDelete: 'cascade' }),
filePath: text('file_path').notNull(),
thumbnailPath: text('thumbnail_path'),
caption: text('caption'),
uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [index('component_images_component_idx').on(table.componentId)]
);
// ─── Component Documents ────────────────────────────────────────────
export const componentDocuments = pgTable(
'component_documents',
{
id: uuid('id').defaultRandom().primaryKey(),
componentId: uuid('component_id')
.notNull()
.references(() => components.id, { onDelete: 'cascade' }),
filePath: text('file_path').notNull(),
originalFilename: text('original_filename').notNull(),
fileType: text('file_type'),
description: text('description'),
uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [index('component_documents_component_idx').on(table.componentId)]
);
// ─── Installation Log (append-only) ─────────────────────────────────
export const installationLog = pgTable(
'installation_log',
{
id: uuid('id').defaultRandom().primaryKey(),
componentId: uuid('component_id')
.notNull()
.references(() => components.id, { onDelete: 'cascade' }),
deviceId: uuid('device_id')
.notNull()
.references(() => devices.id, { onDelete: 'cascade' }),
action: text('action').notNull(),
performedBy: text('performed_by'),
notes: text('notes'),
performedAt: timestamp('performed_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [
index('install_log_component_idx').on(table.componentId),
index('install_log_device_idx').on(table.deviceId),
index('install_log_date_idx').on(table.performedAt),
check('install_log_action_check', sql`${table.action} IN ('installed', 'removed', 'swapped')`)
]
);
// ─── Checklist Templates ────────────────────────────────────────────
export const checklistTemplates = pgTable('checklist_templates', {
id: uuid('id').defaultRandom().primaryKey(),
title: text('title').notNull(),
description: text('description'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
export const templateItems = pgTable(
'template_items',
{
id: uuid('id').defaultRandom().primaryKey(),
templateId: uuid('template_id')
.notNull()
.references(() => checklistTemplates.id, { onDelete: 'cascade' }),
text: text('text').notNull(),
itemType: text('item_type').notNull().default('checkbox'), // 'checkbox' or 'input'
unit: text('unit'), // e.g. 'RPM', 'dB', '%'
sortOrder: integer('sort_order').default(0).notNull()
},
(table) => [index('template_items_template_idx').on(table.templateId)]
);
// ─── Device Checklists ──────────────────────────────────────────────
export const deviceChecklists = pgTable(
'device_checklists',
{
id: uuid('id').defaultRandom().primaryKey(),
deviceId: uuid('device_id')
.notNull()
.references(() => devices.id, { onDelete: 'cascade' }),
title: text('title').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [index('device_checklists_device_idx').on(table.deviceId)]
);
export const checklistItems = pgTable(
'checklist_items',
{
id: uuid('id').defaultRandom().primaryKey(),
checklistId: uuid('checklist_id')
.notNull()
.references(() => deviceChecklists.id, { onDelete: 'cascade' }),
text: text('text').notNull(),
itemType: text('item_type').notNull().default('checkbox'), // 'checkbox' or 'input'
unit: text('unit'), // e.g. 'RPM', 'dB', '%'
checked: boolean('checked').default(false).notNull(),
value: text('value'), // for input type items
sortOrder: integer('sort_order').default(0).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [index('checklist_items_checklist_idx').on(table.checklistId)]
);
// ─── Device Log (append-only repair/operation history) ───────────────
export const deviceLog = pgTable(
'device_log',
{
id: uuid('id').defaultRandom().primaryKey(),
deviceId: uuid('device_id')
.notNull()
.references(() => devices.id, { onDelete: 'cascade' }),
type: text('type').notNull(),
description: text('description').notNull(),
conditionAfter: text('condition_after'),
performedBy: text('performed_by'),
performedAt: timestamp('performed_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [
index('device_log_device_idx').on(table.deviceId),
index('device_log_date_idx').on(table.performedAt),
check('device_log_type_check', sql`${table.type} IN ('repair', 'inspection', 'cleaning', 'modification', 'diagnostic', 'recap', 'other')`)
]
);
// ─── Todos ──────────────────────────────────────────────────────────
export const todos = pgTable(
'todos',
{
id: uuid('id').defaultRandom().primaryKey(),
title: text('title').notNull(),
description: text('description'),
status: text('status').notNull().default('todo'),
priority: integer('priority').notNull().default(2), // 0=urgent, 1=high, 2=medium, 3=low
deviceId: uuid('device_id').references(() => devices.id, { onDelete: 'set null' }),
dueDate: timestamp('due_date', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => [
index('todos_status_idx').on(table.status),
index('todos_priority_idx').on(table.priority),
index('todos_device_idx').on(table.deviceId),
check('todos_status_check', sql`${table.status} IN ('todo', 'in_progress', 'done')`),
check('todos_priority_check', sql`${table.priority} IN (0, 1, 2, 3)`)
]
);