Rename Forte to LunarFront, generalize for any small business
Rebrand from Forte (music-store-specific) to LunarFront (any small business): - Package namespace @forte/* → @lunarfront/* - Database forte/forte_test → lunarfront/lunarfront_test - Docker containers, volumes, connection strings - UI branding, localStorage keys, test emails - All documentation and planning docs Generalize music-specific terminology: - instrumentDescription → itemDescription - instrumentCount → itemCount - instrumentType → itemCategory (on service templates) - New migration 0027_generalize_terminology for column renames - Seed data updated with generic examples - RBAC descriptions updated
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
-- Generalize music-specific column names to generic terminology
|
||||
|
||||
-- repair_ticket: instrument_description -> item_description
|
||||
ALTER TABLE repair_ticket RENAME COLUMN instrument_description TO item_description;
|
||||
|
||||
-- repair_batch: instrument_count -> item_count
|
||||
ALTER TABLE repair_batch RENAME COLUMN instrument_count TO item_count;
|
||||
|
||||
-- repair_service_template: instrument_type -> item_category
|
||||
ALTER TABLE repair_service_template RENAME COLUMN instrument_type TO item_category;
|
||||
|
||||
-- Update module descriptions to be industry-agnostic
|
||||
UPDATE module_config SET description = 'Rental agreements and billing' WHERE slug = 'rentals';
|
||||
UPDATE module_config SET description = 'Scheduling, staff management, and billing' WHERE slug = 'lessons';
|
||||
@@ -190,6 +190,13 @@
|
||||
"when": 1774860000000,
|
||||
"tag": "0026_modules",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 27,
|
||||
"version": "7",
|
||||
"when": 1774870000000,
|
||||
"tag": "0027_generalize_terminology",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export const repairBatches = pgTable('repair_batch', {
|
||||
dueDate: timestamp('due_date', { withTimezone: true }),
|
||||
completedDate: timestamp('completed_date', { withTimezone: true }),
|
||||
deliveredDate: timestamp('delivered_date', { withTimezone: true }),
|
||||
instrumentCount: integer('instrument_count').notNull().default(0),
|
||||
itemCount: integer('item_count').notNull().default(0),
|
||||
receivedCount: integer('received_count').notNull().default(0),
|
||||
estimatedTotal: numeric('estimated_total', { precision: 10, scale: 2 }),
|
||||
actualTotal: numeric('actual_total', { precision: 10, scale: 2 }),
|
||||
@@ -101,7 +101,7 @@ export const repairTickets = pgTable('repair_ticket', {
|
||||
customerName: varchar('customer_name', { length: 255 }).notNull(),
|
||||
customerPhone: varchar('customer_phone', { length: 50 }),
|
||||
inventoryUnitId: uuid('inventory_unit_id').references(() => inventoryUnits.id),
|
||||
instrumentDescription: text('instrument_description'),
|
||||
itemDescription: text('item_description'),
|
||||
serialNumber: varchar('serial_number', { length: 255 }),
|
||||
conditionIn: repairConditionInEnum('condition_in'),
|
||||
conditionInNotes: text('condition_in_notes'),
|
||||
@@ -158,7 +158,7 @@ export type RepairNoteInsert = typeof repairNotes.$inferInsert
|
||||
export const repairServiceTemplates = pgTable('repair_service_template', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
instrumentType: varchar('instrument_type', { length: 100 }),
|
||||
itemCategory: varchar('item_category', { length: 100 }),
|
||||
size: varchar('size', { length: 50 }),
|
||||
description: text('description'),
|
||||
itemType: repairLineItemTypeEnum('item_type').notNull().default('flat_rate'),
|
||||
|
||||
@@ -7,7 +7,7 @@ const DEV_LOCATION_ID = '00000000-0000-0000-0000-000000000010'
|
||||
|
||||
async function seed() {
|
||||
const connectionString =
|
||||
process.env.DATABASE_URL ?? 'postgresql://forte:forte@localhost:5432/forte'
|
||||
process.env.DATABASE_URL ?? 'postgresql://lunarfront:lunarfront@localhost:5432/lunarfront'
|
||||
const sql = postgres(connectionString)
|
||||
const db = drizzle(sql)
|
||||
|
||||
@@ -17,7 +17,7 @@ async function seed() {
|
||||
.insert(companies)
|
||||
.values({
|
||||
id: DEV_COMPANY_ID,
|
||||
name: 'Dev Music Co.',
|
||||
name: 'Dev Store',
|
||||
timezone: 'America/Chicago',
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
import postgres from 'postgres'
|
||||
|
||||
const DB_URL = process.env.DATABASE_URL ?? 'postgresql://forte:forte@localhost:5432/forte'
|
||||
const DB_URL = process.env.DATABASE_URL ?? 'postgresql://lunarfront:lunarfront@localhost:5432/lunarfront'
|
||||
const COMPANY_ID = 'a0000000-0000-0000-0000-000000000001'
|
||||
|
||||
const sql = postgres(DB_URL)
|
||||
@@ -17,7 +17,7 @@ async function seed() {
|
||||
// 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}, 'Forte Music Store', 'America/Chicago')`
|
||||
await sql`INSERT INTO company (id, name, timezone) VALUES (${COMPANY_ID}, 'Demo Store', 'America/Chicago')`
|
||||
await sql`INSERT INTO location (id, name) VALUES ('a0000000-0000-0000-0000-000000000002', 'Main Store')`
|
||||
console.log(' Created company and location')
|
||||
|
||||
@@ -39,16 +39,16 @@ async function seed() {
|
||||
}
|
||||
|
||||
// --- Admin user (if not exists) ---
|
||||
const [adminUser] = await sql`SELECT id FROM "user" WHERE email = 'admin@forte.dev'`
|
||||
const [adminUser] = await sql`SELECT id FROM "user" WHERE email = 'admin@lunarfront.dev'`
|
||||
if (!adminUser) {
|
||||
const bcrypt = await import('bcrypt')
|
||||
const hashedPw = await (bcrypt.default || bcrypt).hash('admin1234', 10)
|
||||
const [user] = await sql`INSERT INTO "user" (email, password_hash, first_name, last_name, role) VALUES ('admin@forte.dev', ${hashedPw}, 'Admin', 'User', 'admin') RETURNING id`
|
||||
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@forte.dev / admin1234')
|
||||
console.log(' Created admin user: admin@lunarfront.dev / admin1234')
|
||||
} else {
|
||||
const [adminRole] = await sql`SELECT id FROM role WHERE slug = 'admin' LIMIT 1`
|
||||
if (adminRole) {
|
||||
@@ -61,11 +61,11 @@ async function seed() {
|
||||
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: 'band@lincoln.edu', phone: '555-0200' },
|
||||
{ name: 'Garcia Music Studio', email: 'garcia@studio.com', phone: '555-0103' },
|
||||
{ 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: 'music@westsidechurch.org', phone: '555-0300' },
|
||||
{ name: 'Westside Church', email: 'admin@westsidechurch.org', phone: '555-0300' },
|
||||
{ name: 'Oak Elementary', email: 'office@oakelementary.edu', phone: '555-0201' },
|
||||
]
|
||||
|
||||
@@ -89,7 +89,7 @@ async function seed() {
|
||||
{ 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 Music Studio', firstName: 'Carlos', lastName: 'Garcia', email: 'carlos@studio.com' },
|
||||
{ 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' },
|
||||
]
|
||||
@@ -105,39 +105,37 @@ async function seed() {
|
||||
|
||||
// --- Repair Service Templates ---
|
||||
const templates = [
|
||||
{ name: 'Bow Rehair', instrumentType: 'Violin', size: '4/4', itemType: 'flat_rate', price: '65.00', cost: '15.00' },
|
||||
{ name: 'Bow Rehair', instrumentType: 'Violin', size: '3/4', itemType: 'flat_rate', price: '55.00', cost: '12.00' },
|
||||
{ name: 'Bow Rehair', instrumentType: 'Cello', size: null, itemType: 'flat_rate', price: '80.00', cost: '20.00' },
|
||||
{ name: 'Bow Rehair', instrumentType: 'Bass', size: null, itemType: 'flat_rate', price: '90.00', cost: '25.00' },
|
||||
{ name: 'String Change', instrumentType: 'Guitar', size: 'Acoustic', itemType: 'flat_rate', price: '25.00', cost: '8.00' },
|
||||
{ name: 'String Change', instrumentType: 'Guitar', size: 'Electric', itemType: 'flat_rate', price: '25.00', cost: '7.00' },
|
||||
{ name: 'String Change', instrumentType: 'Violin', size: '4/4', itemType: 'flat_rate', price: '35.00', cost: '12.00' },
|
||||
{ name: 'Valve Overhaul', instrumentType: 'Trumpet', size: null, itemType: 'labor', price: '85.00', cost: null },
|
||||
{ name: 'Pad Replacement', instrumentType: 'Clarinet', size: null, itemType: 'flat_rate', price: '120.00', cost: '30.00' },
|
||||
{ name: 'Pad Replacement', instrumentType: 'Flute', size: null, itemType: 'flat_rate', price: '110.00', cost: '25.00' },
|
||||
{ name: 'Cork Replacement', instrumentType: 'Clarinet', size: null, itemType: 'flat_rate', price: '45.00', cost: '5.00' },
|
||||
{ name: 'Slide Repair', instrumentType: 'Trombone', size: null, itemType: 'labor', price: '75.00', cost: null },
|
||||
{ name: 'Bridge Setup', instrumentType: 'Violin', size: '4/4', itemType: 'flat_rate', price: '40.00', cost: '10.00' },
|
||||
{ name: 'Guitar Setup', instrumentType: 'Guitar', size: null, itemType: 'flat_rate', price: '65.00', cost: '5.00' },
|
||||
{ name: 'Dent Removal', instrumentType: 'Brass', size: null, itemType: 'labor', price: '50.00', cost: null },
|
||||
{ name: 'General Cleaning', instrumentType: null, size: null, itemType: 'flat_rate', price: '30.00', cost: '5.00' },
|
||||
{ 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(instrument_type, '') = ${t.instrumentType ?? ''} AND COALESCE(size, '') = ${t.size ?? ''}`
|
||||
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, instrument_type, size, item_type, default_price, default_cost, is_active) VALUES (${t.name}, ${t.instrumentType}, ${t.size}, ${t.itemType}, ${t.price}, ${t.cost}, true)`
|
||||
console.log(` Template: ${t.name} ${t.instrumentType ?? ''} ${t.size ?? ''}`)
|
||||
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', instrument: 'Fender Stratocaster', serial: 'US22-045891', problem: 'Fret buzz on 3rd and 5th fret, needs setup', condition: 'good', status: 'in_progress', estimate: '65.00' },
|
||||
{ customer: 'Emily Chen', instrument: 'Yamaha YTR-2330 Trumpet', serial: 'YTR-78432', problem: 'Stuck 2nd valve, sluggish action on all valves', condition: 'fair', status: 'pending_approval', estimate: '85.00' },
|
||||
{ customer: 'David Smith', instrument: 'Stradivarius Copy Violin', serial: null, problem: 'Bow needs rehair, bridge slightly warped', condition: 'fair', status: 'ready', estimate: '105.00' },
|
||||
{ customer: 'Carlos Garcia', instrument: 'Martin D-28 Acoustic Guitar', serial: 'M2284563', problem: 'Broken tuning peg, needs replacement', condition: 'good', status: 'new', estimate: null },
|
||||
{ customer: 'Lisa Johnson', instrument: 'Yamaha YCL-255 Clarinet', serial: null, problem: 'Several pads worn, keys sticking', condition: 'poor', status: 'diagnosing', estimate: null },
|
||||
{ customer: 'Walk-In Customer', instrument: 'Unknown Flute', serial: null, problem: 'Customer says it squeaks on high notes', condition: 'fair', status: 'intake', estimate: null },
|
||||
{ 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) {
|
||||
@@ -145,8 +143,8 @@ async function seed() {
|
||||
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, instrument_description, serial_number, problem_description, condition_in, status, estimated_cost) VALUES (${num}, ${t.customer}, ${acctId}, ${t.instrument}, ${t.serial}, ${t.problem}, ${t.condition}, ${t.status}, ${t.estimate})`
|
||||
console.log(` Ticket: ${t.customer} — ${t.instrument} [${t.status}]`)
|
||||
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 ---
|
||||
@@ -154,22 +152,22 @@ async function seed() {
|
||||
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, instrument_count, notes, status) VALUES (${batchNum}, ${schoolId}, 'Mr. Williams', '555-0210', 'williams@lincoln.edu', 5, 'Annual band instrument checkup — 5 instruments', 'intake') RETURNING id`
|
||||
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 = [
|
||||
{ instrument: 'Student Flute', problem: 'Pads worn, needs replacement check', condition: 'fair' },
|
||||
{ instrument: 'Student Clarinet #1', problem: 'Keys sticking, cork dried out', condition: 'fair' },
|
||||
{ instrument: 'Student Clarinet #2', problem: 'Barrel crack, needs assessment', condition: 'poor' },
|
||||
{ instrument: 'Student Trumpet', problem: 'Valve oil needed, general checkup', condition: 'good' },
|
||||
{ instrument: 'Student Trombone', problem: 'Slide dent, sluggish movement', condition: 'fair' },
|
||||
{ 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, instrument_description, problem_description, condition_in, status) VALUES (${num}, 'Lincoln High School', ${schoolId}, ${batch.id}, ${bt.instrument}, ${bt.problem}, ${bt.condition}, 'new')`
|
||||
console.log(` Batch ticket: ${bt.instrument}`)
|
||||
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 instruments`)
|
||||
console.log(` Batch: Lincoln High School — 5 items`)
|
||||
}
|
||||
|
||||
console.log('\nDev seed complete!')
|
||||
|
||||
@@ -20,14 +20,14 @@ export const SYSTEM_PERMISSIONS = [
|
||||
{ slug: 'pos.admin', domain: 'pos', action: 'admin', description: 'Void transactions, override prices, manage discounts' },
|
||||
|
||||
// Rentals
|
||||
{ slug: 'rentals.view', domain: 'rentals', action: 'view', description: 'View rental contracts, fleet, billing' },
|
||||
{ slug: 'rentals.edit', domain: 'rentals', action: 'edit', description: 'Create rentals, process returns, manage fleet' },
|
||||
{ slug: 'rentals.view', domain: 'rentals', action: 'view', description: 'View rental contracts, inventory, billing' },
|
||||
{ slug: 'rentals.edit', domain: 'rentals', action: 'edit', description: 'Create rentals, process returns, manage rental inventory' },
|
||||
{ slug: 'rentals.admin', domain: 'rentals', action: 'admin', description: 'Override terms, adjust equity, cancel contracts' },
|
||||
|
||||
// Lessons
|
||||
{ slug: 'lessons.view', domain: 'lessons', action: 'view', description: 'View lesson schedules, enrollments, attendance' },
|
||||
// Lessons / Scheduling
|
||||
{ slug: 'lessons.view', domain: 'lessons', action: 'view', description: 'View schedules, enrollments, attendance' },
|
||||
{ slug: 'lessons.edit', domain: 'lessons', action: 'edit', description: 'Manage scheduling, enrollment, attendance' },
|
||||
{ slug: 'lessons.admin', domain: 'lessons', action: 'admin', description: 'Configure lesson settings, manage instructors' },
|
||||
{ slug: 'lessons.admin', domain: 'lessons', action: 'admin', description: 'Configure scheduling settings, manage staff' },
|
||||
|
||||
// Repairs
|
||||
{ slug: 'repairs.view', domain: 'repairs', action: 'view', description: 'View repair tickets, parts inventory' },
|
||||
|
||||
@@ -80,7 +80,7 @@ export function webdavBasicAuth(app: FastifyInstance) {
|
||||
|
||||
const authHeader = request.headers.authorization
|
||||
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Forte WebDAV"')
|
||||
reply.header('WWW-Authenticate', 'Basic realm="LunarFront WebDAV"')
|
||||
return reply.status(401).send('Authentication required')
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export function webdavBasicAuth(app: FastifyInstance) {
|
||||
const colonIndex = decoded.indexOf(':')
|
||||
if (colonIndex === -1) {
|
||||
recordFailure(ip)
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Forte WebDAV"')
|
||||
reply.header('WWW-Authenticate', 'Basic realm="LunarFront WebDAV"')
|
||||
return reply.status(401).send('Invalid credentials')
|
||||
}
|
||||
|
||||
@@ -103,14 +103,14 @@ export function webdavBasicAuth(app: FastifyInstance) {
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
recordFailure(ip)
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Forte WebDAV"')
|
||||
reply.header('WWW-Authenticate', 'Basic realm="LunarFront WebDAV"')
|
||||
return reply.status(401).send('Invalid credentials')
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(password, user.passwordHash)
|
||||
if (!valid) {
|
||||
recordFailure(ip)
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Forte WebDAV"')
|
||||
reply.header('WWW-Authenticate', 'Basic realm="LunarFront WebDAV"')
|
||||
return reply.status(401).send('Invalid credentials')
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
TaxExemptionUpdateSchema,
|
||||
MemberIdentifierCreateSchema,
|
||||
MemberIdentifierUpdateSchema,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import {
|
||||
AccountService,
|
||||
MemberService,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { RegisterSchema, LoginSchema } from '@forte/shared/schemas'
|
||||
import { RegisterSchema, LoginSchema } from '@lunarfront/shared/schemas'
|
||||
import { users } from '../../db/schema/users.js'
|
||||
|
||||
const SALT_ROUNDS = 10
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
SupplierCreateSchema,
|
||||
SupplierUpdateSchema,
|
||||
PaginationSchema,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import { CategoryService, SupplierService } from '../../services/inventory.service.js'
|
||||
|
||||
export const inventoryRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import { LookupCreateSchema, LookupUpdateSchema } from '@forte/shared/schemas'
|
||||
import { LookupCreateSchema, LookupUpdateSchema } from '@lunarfront/shared/schemas'
|
||||
import { UnitStatusService, ItemConditionService } from '../../services/lookup.service.js'
|
||||
import { ConflictError, ValidationError } from '../../lib/errors.js'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
InventoryUnitCreateSchema,
|
||||
InventoryUnitUpdateSchema,
|
||||
PaginationSchema,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import { ProductService, InventoryUnitService } from '../../services/product.service.js'
|
||||
|
||||
export const productRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import { eq, count, sql, type Column } from 'drizzle-orm'
|
||||
import { PaginationSchema } from '@forte/shared/schemas'
|
||||
import { PaginationSchema } from '@lunarfront/shared/schemas'
|
||||
import { RbacService } from '../../services/rbac.service.js'
|
||||
import { ValidationError } from '../../lib/errors.js'
|
||||
import { users } from '../../db/schema/users.js'
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
RepairNoteCreateSchema,
|
||||
RepairServiceTemplateCreateSchema,
|
||||
RepairServiceTemplateUpdateSchema,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import { RepairTicketService, RepairLineItemService, RepairBatchService, RepairNoteService, RepairServiceTemplateService } from '../../services/repair.service.js'
|
||||
|
||||
export const repairRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import multipart from '@fastify/multipart'
|
||||
import { PaginationSchema } from '@forte/shared/schemas'
|
||||
import { PaginationSchema } from '@lunarfront/shared/schemas'
|
||||
import { StorageFolderService, StorageFileService, StoragePermissionService } from '../../services/storage.service.js'
|
||||
import { ValidationError } from '../../lib/errors.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import { PaginationSchema } from '@forte/shared/schemas'
|
||||
import { PaginationSchema } from '@lunarfront/shared/schemas'
|
||||
import { VaultKeyService, VaultPermissionService, VaultCategoryService, VaultEntryService } from '../../services/vault.service.js'
|
||||
import { ValidationError } from '../../lib/errors.js'
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import type {
|
||||
TaxExemptionCreateInput,
|
||||
TaxExemptionUpdateInput,
|
||||
PaginationInput,
|
||||
} from '@forte/shared/schemas'
|
||||
import { isMinor, normalizeStateCode } from '@forte/shared/utils'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import { isMinor, normalizeStateCode } from '@lunarfront/shared/utils'
|
||||
import {
|
||||
withPagination,
|
||||
withSort,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
SupplierCreateInput,
|
||||
SupplierUpdateInput,
|
||||
PaginationInput,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import {
|
||||
withPagination,
|
||||
withSort,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
SYSTEM_UNIT_STATUSES,
|
||||
SYSTEM_ITEM_CONDITIONS,
|
||||
} from '../db/schema/lookups.js'
|
||||
import type { LookupCreateInput, LookupUpdateInput } from '@forte/shared/schemas'
|
||||
import type { LookupCreateInput, LookupUpdateInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
function createLookupService(
|
||||
table: typeof inventoryUnitStatuses | typeof itemConditions,
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
InventoryUnitCreateInput,
|
||||
InventoryUnitUpdateInput,
|
||||
PaginationInput,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import {
|
||||
withPagination,
|
||||
withSort,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { eq, and, inArray, count, type Column } from 'drizzle-orm'
|
||||
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
|
||||
import type { PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginationInput } from '@lunarfront/shared/schemas'
|
||||
import { permissions, roles, rolePermissions, userRoles } from '../db/schema/rbac.js'
|
||||
import { SYSTEM_PERMISSIONS, DEFAULT_ROLES } from '../db/seeds/rbac.js'
|
||||
import { ForbiddenError } from '../lib/errors.js'
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
RepairServiceTemplateCreateInput,
|
||||
RepairServiceTemplateUpdateInput,
|
||||
PaginationInput,
|
||||
} from '@forte/shared/schemas'
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import {
|
||||
withPagination,
|
||||
withSort,
|
||||
@@ -59,7 +59,7 @@ export const RepairTicketService = {
|
||||
locationId: input.locationId,
|
||||
repairBatchId: input.repairBatchId,
|
||||
inventoryUnitId: input.inventoryUnitId,
|
||||
instrumentDescription: input.instrumentDescription,
|
||||
itemDescription: input.itemDescription,
|
||||
serialNumber: input.serialNumber,
|
||||
conditionIn: input.conditionIn,
|
||||
conditionInNotes: input.conditionInNotes,
|
||||
@@ -101,7 +101,7 @@ export const RepairTicketService = {
|
||||
repairTickets.ticketNumber,
|
||||
repairTickets.customerName,
|
||||
repairTickets.customerPhone,
|
||||
repairTickets.instrumentDescription,
|
||||
repairTickets.itemDescription,
|
||||
repairTickets.serialNumber,
|
||||
])
|
||||
if (search) conditions.push(search)
|
||||
@@ -151,7 +151,7 @@ export const RepairTicketService = {
|
||||
async listByBatch(db: PostgresJsDatabase<any>, batchId: string, params: PaginationInput) {
|
||||
const baseWhere = eq(repairTickets.repairBatchId, batchId)
|
||||
const searchCondition = params.q
|
||||
? buildSearchCondition(params.q, [repairTickets.ticketNumber, repairTickets.customerName, repairTickets.instrumentDescription])
|
||||
? buildSearchCondition(params.q, [repairTickets.ticketNumber, repairTickets.customerName, repairTickets.itemDescription])
|
||||
: undefined
|
||||
const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere
|
||||
|
||||
@@ -292,7 +292,7 @@ export const RepairBatchService = {
|
||||
contactEmail: input.contactEmail,
|
||||
pickupDate: input.pickupDate ? new Date(input.pickupDate) : undefined,
|
||||
dueDate: input.dueDate ? new Date(input.dueDate) : undefined,
|
||||
instrumentCount: input.instrumentCount,
|
||||
itemCount: input.itemCount,
|
||||
notes: input.notes,
|
||||
})
|
||||
.returning()
|
||||
@@ -391,7 +391,7 @@ export const RepairServiceTemplateService = {
|
||||
.insert(repairServiceTemplates)
|
||||
.values({
|
||||
name: input.name,
|
||||
instrumentType: input.instrumentType,
|
||||
itemCategory: input.itemCategory,
|
||||
size: input.size,
|
||||
description: input.description,
|
||||
itemType: input.itemType,
|
||||
@@ -406,13 +406,13 @@ export const RepairServiceTemplateService = {
|
||||
async list(db: PostgresJsDatabase<any>, params: PaginationInput) {
|
||||
const baseWhere = eq(repairServiceTemplates.isActive, true)
|
||||
const searchCondition = params.q
|
||||
? buildSearchCondition(params.q, [repairServiceTemplates.name, repairServiceTemplates.instrumentType, repairServiceTemplates.size, repairServiceTemplates.description])
|
||||
? buildSearchCondition(params.q, [repairServiceTemplates.name, repairServiceTemplates.itemCategory, repairServiceTemplates.size, repairServiceTemplates.description])
|
||||
: undefined
|
||||
const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere
|
||||
|
||||
const sortableColumns: Record<string, Column> = {
|
||||
name: repairServiceTemplates.name,
|
||||
instrument_type: repairServiceTemplates.instrumentType,
|
||||
item_category: repairServiceTemplates.itemCategory,
|
||||
default_price: repairServiceTemplates.defaultPrice,
|
||||
sort_order: repairServiceTemplates.sortOrder,
|
||||
created_at: repairServiceTemplates.createdAt,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { userRoles } from '../db/schema/rbac.js'
|
||||
import type { StorageProvider } from '../storage/index.js'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { withPagination, withSort, buildSearchCondition, paginatedResponse } from '../utils/pagination.js'
|
||||
import type { PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginationInput } from '@lunarfront/shared/schemas'
|
||||
|
||||
const MAX_PARENT_DEPTH = 50
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
|
||||
import { vaultConfig, vaultCategories, vaultCategoryPermissions, vaultEntries } from '../db/schema/vault.js'
|
||||
import { userRoles } from '../db/schema/rbac.js'
|
||||
import { withPagination, withSort, buildSearchCondition, paginatedResponse } from '../utils/pagination.js'
|
||||
import type { PaginationInput } from '@forte/shared/schemas'
|
||||
import type { PaginationInput } from '@lunarfront/shared/schemas'
|
||||
import { randomBytes, createCipheriv, createDecipheriv, pbkdf2Sync } from 'crypto'
|
||||
import bcrypt from 'bcrypt'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sql, asc, desc, ilike, or, type SQL, type Column } from 'drizzle-orm'
|
||||
import type { PgSelect } from 'drizzle-orm/pg-core'
|
||||
import type { PaginationInput, PaginatedResponse } from '@forte/shared/schemas'
|
||||
import type { PaginationInput, PaginatedResponse } from '@lunarfront/shared/schemas'
|
||||
|
||||
/**
|
||||
* Apply pagination (offset + limit) to a Drizzle query.
|
||||
|
||||
Reference in New Issue
Block a user