Initial commit: buildfor_life_repair inventory system

SvelteKit + PostgreSQL app for tracking vintage computers, audio equipment,
components, and installation history. Features device/component CRUD, operation
logs, QR code labels, global search, image uploads, and dark mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 17:11:05 +07:00
commit 6f0e0ad6c6
64 changed files with 8996 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from './schema.js';
import { env } from '$env/dynamic/private';
const pool = new pg.Pool({
connectionString: env.DATABASE_URL
});
export const db = drizzle(pool, { schema });
export type Database = typeof db;
+199
View File
@@ -0,0 +1,199 @@
import {
pgTable,
uuid,
text,
integer,
boolean,
timestamp,
index,
check
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
// ─── 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)]
);
// ─── 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')`)
]
);
// ─── 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')`)
]
);
+9
View File
@@ -0,0 +1,9 @@
import QRCode from 'qrcode';
export async function generateQrSvg(url: string): Promise<string> {
return QRCode.toString(url, { type: 'svg', margin: 1 });
}
export async function generateQrDataUrl(url: string): Promise<string> {
return QRCode.toDataURL(url, { margin: 1, width: 256 });
}
+73
View File
@@ -0,0 +1,73 @@
import { randomUUID } from 'crypto';
import { writeFile, unlink, mkdir } from 'fs/promises';
import { join, extname } from 'path';
import sharp from 'sharp';
const UPLOAD_BASE = 'static/uploads';
const THUMBNAIL_WIDTH = 300;
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/heic'];
const ALLOWED_DOC_TYPES = ['application/pdf', 'text/plain', 'application/zip'];
export async function saveImage(
file: File,
subfolder: 'devices' | 'components'
): Promise<{ filePath: string; thumbnailPath: string }> {
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
throw new Error(`Invalid image type: ${file.type}`);
}
const ext = extname(file.name) || '.jpg';
const filename = `${randomUUID()}${ext}`;
const thumbFilename = `thumb_${filename}`;
const dir = join(UPLOAD_BASE, subfolder);
await mkdir(dir, { recursive: true });
const buffer = Buffer.from(await file.arrayBuffer());
// Save original
const filePath = join(dir, filename);
await writeFile(filePath, buffer);
// Generate thumbnail
const thumbnailPath = join(dir, thumbFilename);
await sharp(buffer).resize(THUMBNAIL_WIDTH).jpeg({ quality: 80 }).toFile(thumbnailPath);
// Return paths relative to static/ for serving
return {
filePath: `/uploads/${subfolder}/${filename}`,
thumbnailPath: `/uploads/${subfolder}/${thumbFilename}`
};
}
export async function saveDocument(file: File): Promise<{ filePath: string; originalFilename: string }> {
if (![...ALLOWED_IMAGE_TYPES, ...ALLOWED_DOC_TYPES].includes(file.type)) {
throw new Error(`Invalid file type: ${file.type}`);
}
const ext = extname(file.name) || '';
const filename = `${randomUUID()}${ext}`;
const dir = join(UPLOAD_BASE, 'documents');
await mkdir(dir, { recursive: true });
const buffer = Buffer.from(await file.arrayBuffer());
const filePath = join(dir, filename);
await writeFile(filePath, buffer);
return {
filePath: `/uploads/documents/${filename}`,
originalFilename: file.name
};
}
export async function deleteFile(filePath: string): Promise<void> {
try {
// filePath is like /uploads/devices/xxx.jpg, need to prepend static/
const fullPath = join('static', filePath);
await unlink(fullPath);
} catch {
// File may already be deleted
}
}