/** * Dev seed script — populates the dev database with sample data for manual testing. * Run: bun run src/db/seeds/dev-seed.ts * * Prerequisites: backend must have been started at least once (migrations + RBAC seeds applied). */ import postgres from 'postgres' const DB_URL = process.env.DATABASE_URL ?? 'postgresql://lunarfront:lunarfront@localhost:5432/lunarfront' const COMPANY_ID = 'a0000000-0000-4000-8000-000000000001' const sql = postgres(DB_URL) async function seed() { console.log('Seeding dev database...') // Create company and location if they don't exist const [company] = await sql`SELECT id FROM company WHERE id = ${COMPANY_ID}` if (!company) { await sql`INSERT INTO company (id, name, timezone) VALUES (${COMPANY_ID}, 'Demo Store', 'America/Chicago')` await sql`INSERT INTO location (id, name, tax_rate, service_tax_rate) VALUES ('a0000000-0000-4000-8000-000000000002', 'Main Store', '0.0825', '0.0825')` console.log(' Created company and location') // Seed RBAC const { SYSTEM_PERMISSIONS, DEFAULT_ROLES } = await import('../seeds/rbac.js') for (const p of SYSTEM_PERMISSIONS) { await sql`INSERT INTO permission (slug, domain, action, description) VALUES (${p.slug}, ${p.domain}, ${p.action}, ${p.description}) ON CONFLICT (slug) DO NOTHING` } const permRows = await sql`SELECT id, slug FROM permission` const permMap = new Map(permRows.map((r: any) => [r.slug, r.id])) for (const roleDef of DEFAULT_ROLES) { const [role] = await sql`INSERT INTO role (name, slug, description, is_system) VALUES (${roleDef.name}, ${roleDef.slug}, ${roleDef.description}, true) RETURNING id` for (const permSlug of roleDef.permissions) { const permId = permMap.get(permSlug) if (permId) await sql`INSERT INTO role_permission (role_id, permission_id) VALUES (${role.id}, ${permId}) ON CONFLICT DO NOTHING` } } console.log(' Seeded RBAC permissions and roles') } // --- Admin user (if not exists) --- const adminPassword = process.env.ADMIN_PASSWORD ?? 'admin1234' const [adminUser] = await sql`SELECT id FROM "user" WHERE email = 'admin@lunarfront.dev'` if (!adminUser) { const bcrypt = await import('bcryptjs') const hashedPw = await bcrypt.hash(adminPassword, 10) const [user] = await sql`INSERT INTO "user" (email, password_hash, first_name, last_name, role) VALUES ('admin@lunarfront.dev', ${hashedPw}, 'Admin', 'User', 'admin') RETURNING id` const [adminRole] = await sql`SELECT id FROM role WHERE slug = 'admin' LIMIT 1` if (adminRole) { await sql`INSERT INTO user_role_assignment (user_id, role_id) VALUES (${user.id}, ${adminRole.id}) ON CONFLICT DO NOTHING` } console.log(` Created admin user: admin@lunarfront.dev / ${adminPassword}`) } else { const [adminRole] = await sql`SELECT id FROM role WHERE slug = 'admin' LIMIT 1` if (adminRole) { await sql`DELETE FROM user_role_assignment WHERE user_id = ${adminUser.id}` await sql`INSERT INTO user_role_assignment (user_id, role_id) VALUES (${adminUser.id}, ${adminRole.id}) ON CONFLICT DO NOTHING` } } // --- Accounts --- const accounts = [ { name: 'Smith Family', email: 'smith@example.com', phone: '555-0101' }, { name: 'Johnson Family', email: 'johnson@example.com', phone: '555-0102' }, { name: 'Lincoln High School', email: 'office@lincoln.edu', phone: '555-0200' }, { name: 'Garcia Workshop', email: 'garcia@studio.com', phone: '555-0103' }, { name: 'Mike Thompson', email: 'mike.t@email.com', phone: '555-0104' }, { name: 'Emily Chen', email: 'emily.chen@email.com', phone: '555-0105' }, { name: 'Westside Church', email: 'admin@westsidechurch.org', phone: '555-0300' }, { name: 'Oak Elementary', email: 'office@oakelementary.edu', phone: '555-0201' }, ] const acctIds: Record = {} for (const a of accounts) { const existing = await sql`SELECT id FROM account WHERE name = ${a.name}` if (existing.length > 0) { acctIds[a.name] = existing[0].id continue } const num = String(Math.floor(100000 + Math.random() * 900000)) const [row] = await sql`INSERT INTO account (name, email, phone, account_number, billing_mode) VALUES (${a.name}, ${a.email}, ${a.phone}, ${num}, 'consolidated') RETURNING id` acctIds[a.name] = row.id console.log(` Account: ${a.name}`) } // --- Members --- const members = [ { accountName: 'Smith Family', firstName: 'David', lastName: 'Smith', email: 'david@example.com' }, { accountName: 'Smith Family', firstName: 'Sarah', lastName: 'Smith', email: 'sarah@example.com' }, { accountName: 'Smith Family', firstName: 'Tommy', lastName: 'Smith', isMinor: true }, { accountName: 'Johnson Family', firstName: 'Lisa', lastName: 'Johnson', email: 'lisa.j@example.com' }, { accountName: 'Johnson Family', firstName: 'Jake', lastName: 'Johnson', isMinor: true }, { accountName: 'Garcia Workshop', firstName: 'Carlos', lastName: 'Garcia', email: 'carlos@studio.com' }, { accountName: 'Mike Thompson', firstName: 'Mike', lastName: 'Thompson', email: 'mike.t@email.com' }, { accountName: 'Emily Chen', firstName: 'Emily', lastName: 'Chen', email: 'emily.chen@email.com' }, ] for (const m of members) { const acctId = acctIds[m.accountName] const existing = await sql`SELECT id FROM member WHERE first_name = ${m.firstName} AND last_name = ${m.lastName}` if (existing.length > 0) continue const num = String(Math.floor(100000 + Math.random() * 900000)) await sql`INSERT INTO member (account_id, first_name, last_name, email, member_number, is_minor) VALUES (${acctId}, ${m.firstName}, ${m.lastName}, ${m.email ?? null}, ${num}, ${m.isMinor ?? false})` console.log(` Member: ${m.firstName} ${m.lastName}`) } // --- Repair Service Templates --- const templates = [ { name: 'Screen Repair', itemCategory: 'Electronics', size: 'Phone', itemType: 'flat_rate', price: '89.00', cost: '25.00' }, { name: 'Screen Repair', itemCategory: 'Electronics', size: 'Tablet', itemType: 'flat_rate', price: '129.00', cost: '45.00' }, { name: 'Battery Replacement', itemCategory: 'Electronics', size: 'Phone', itemType: 'flat_rate', price: '59.00', cost: '15.00' }, { name: 'Battery Replacement', itemCategory: 'Electronics', size: 'Laptop', itemType: 'flat_rate', price: '99.00', cost: '35.00' }, { name: 'Tune-Up', itemCategory: 'Bicycles', size: 'Standard', itemType: 'flat_rate', price: '65.00', cost: '10.00' }, { name: 'Brake Adjustment', itemCategory: 'Bicycles', size: null, itemType: 'flat_rate', price: '35.00', cost: '5.00' }, { name: 'Blade Sharpening', itemCategory: 'Tools', size: null, itemType: 'flat_rate', price: '15.00', cost: '3.00' }, { name: 'Motor Repair', itemCategory: 'Appliances', size: null, itemType: 'labor', price: '85.00', cost: null }, { name: 'Zipper Replacement', itemCategory: 'Clothing', size: null, itemType: 'flat_rate', price: '25.00', cost: '5.00' }, { name: 'Sole Replacement', itemCategory: 'Footwear', size: null, itemType: 'flat_rate', price: '55.00', cost: '15.00' }, { name: 'Watch Battery', itemCategory: 'Watches', size: null, itemType: 'flat_rate', price: '15.00', cost: '3.00' }, { name: 'Furniture Refinishing', itemCategory: 'Furniture', size: null, itemType: 'labor', price: '150.00', cost: null }, { name: 'Diagnostic Check', itemCategory: null, size: null, itemType: 'flat_rate', price: '30.00', cost: '5.00' }, { name: 'General Cleaning', itemCategory: null, size: null, itemType: 'flat_rate', price: '30.00', cost: '5.00' }, ] for (const t of templates) { const existing = await sql`SELECT id FROM repair_service_template WHERE name = ${t.name} AND COALESCE(item_category, '') = ${t.itemCategory ?? ''} AND COALESCE(size, '') = ${t.size ?? ''}` if (existing.length > 0) continue await sql`INSERT INTO repair_service_template (name, item_category, size, item_type, default_price, default_cost, is_active) VALUES (${t.name}, ${t.itemCategory}, ${t.size}, ${t.itemType}, ${t.price}, ${t.cost}, true)` console.log(` Template: ${t.name} ${t.itemCategory ?? ''} ${t.size ?? ''}`) } // --- Repair Tickets --- const tickets = [ { customer: 'Mike Thompson', item: 'Samsung Galaxy S24', serial: 'IMEI-354789102', problem: 'Cracked screen, touch not responsive in bottom half', condition: 'good', status: 'in_progress', estimate: '89.00' }, { customer: 'Emily Chen', item: 'HP LaserJet Pro M404', serial: 'HP-CNB3K12345', problem: 'Paper jam sensor error, won\'t feed from tray 2', condition: 'fair', status: 'pending_approval', estimate: '85.00' }, { customer: 'David Smith', item: 'Trek Marlin 7 Mountain Bike', serial: null, problem: 'Rear derailleur bent, chain skipping gears', condition: 'fair', status: 'ready', estimate: '65.00' }, { customer: 'Carlos Garcia', item: 'KitchenAid Stand Mixer KSM150', serial: 'W10807813', problem: 'Motor making grinding noise at low speeds', condition: 'good', status: 'new', estimate: null }, { customer: 'Lisa Johnson', item: 'Apple MacBook Pro 14"', serial: null, problem: 'Battery draining rapidly, trackpad click intermittent', condition: 'poor', status: 'diagnosing', estimate: null }, { customer: 'Walk-In Customer', item: 'Leather Work Boots', serial: null, problem: 'Sole separating from upper on both shoes', condition: 'fair', status: 'intake', estimate: null }, ] for (const t of tickets) { const existing = await sql`SELECT id FROM repair_ticket WHERE customer_name = ${t.customer} AND problem_description = ${t.problem}` if (existing.length > 0) continue const num = String(Math.floor(100000 + Math.random() * 900000)) const acctId = acctIds[t.customer] ?? null await sql`INSERT INTO repair_ticket (ticket_number, customer_name, account_id, item_description, serial_number, problem_description, condition_in, status, estimated_cost) VALUES (${num}, ${t.customer}, ${acctId}, ${t.item}, ${t.serial}, ${t.problem}, ${t.condition}, ${t.status}, ${t.estimate})` console.log(` Ticket: ${t.customer} — ${t.item} [${t.status}]`) } // --- Repair Batch --- const batchExists = await sql`SELECT id FROM repair_batch WHERE contact_name = 'Mr. Williams'` if (batchExists.length === 0) { const batchNum = String(Math.floor(100000 + Math.random() * 900000)) const schoolId = acctIds['Lincoln High School'] const [batch] = await sql`INSERT INTO repair_batch (batch_number, account_id, contact_name, contact_phone, contact_email, item_count, notes, status) VALUES (${batchNum}, ${schoolId}, 'Mr. Williams', '555-0210', 'williams@lincoln.edu', 5, 'Annual equipment checkup — 5 items', 'intake') RETURNING id` const batchTickets = [ { item: 'Chromebook #101', problem: 'Screen flickering, hinge loose', condition: 'fair' }, { item: 'Chromebook #102', problem: 'Keyboard unresponsive, several keys stuck', condition: 'fair' }, { item: 'Projector — Epson EB-X51', problem: 'Lamp dim, color wheel noise', condition: 'poor' }, { item: 'Label Printer — Dymo 450', problem: 'Feed mechanism jammed', condition: 'good' }, { item: 'PA Speaker — JBL EON715', problem: 'Crackling at high volume', condition: 'fair' }, ] for (const bt of batchTickets) { const num = String(Math.floor(100000 + Math.random() * 900000)) await sql`INSERT INTO repair_ticket (ticket_number, customer_name, account_id, repair_batch_id, item_description, problem_description, condition_in, status) VALUES (${num}, 'Lincoln High School', ${schoolId}, ${batch.id}, ${bt.item}, ${bt.problem}, ${bt.condition}, 'new')` console.log(` Batch ticket: ${bt.item}`) } console.log(` Batch: Lincoln High School — 5 items`) } console.log('\nDev seed complete!') await sql.end() } seed().catch((err) => { console.error('Seed failed:', err) process.exit(1) })