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:
Ryan Moon
2026-03-30 08:51:54 -05:00
parent 535446696c
commit 9400828f62
84 changed files with 390 additions and 820 deletions

View File

@@ -304,7 +304,7 @@ suite('RBAC', { tags: ['rbac', 'permissions'] }, (t) => {
t.test('cannot disable yourself', { tags: ['users', 'status'] }, async () => {
// Get current user ID from the users list
const usersRes = await t.api.get('/v1/users')
const currentUser = usersRes.data.data.find((u: { email: string }) => u.email === 'test@forte.dev')
const currentUser = usersRes.data.data.find((u: { email: string }) => u.email === 'test@lunarfront.dev')
t.assert.ok(currentUser)
const res = await t.api.patch(`/v1/users/${currentUser.id}/status`, { isActive: false })

View File

@@ -7,8 +7,8 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
const res = await t.api.post('/v1/repair-tickets', {
customerName: 'Walk-In Customer',
customerPhone: '555-0100',
instrumentDescription: 'Yamaha Trumpet',
problemDescription: 'Stuck valve, needs cleaning',
itemDescription: 'Samsung Galaxy S24',
problemDescription: 'Cracked screen, touch not working',
conditionIn: 'fair',
})
t.assert.status(res, 201)
@@ -25,8 +25,8 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
const res = await t.api.post('/v1/repair-tickets', {
customerName: 'Repair Customer',
accountId: acct.data.id,
problemDescription: 'Broken bridge on violin',
instrumentDescription: 'Student Violin 4/4',
problemDescription: 'Screen flickering intermittently',
itemDescription: 'Dell XPS 15 Laptop',
conditionIn: 'poor',
})
t.assert.status(res, 201)
@@ -164,12 +164,12 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
t.assert.ok(res.data.data.some((t: { customerName: string }) => t.customerName === 'Searchable Trumpet Guy'))
})
t.test('searches tickets by instrument description', { tags: ['tickets', 'search'] }, async () => {
await t.api.post('/v1/repair-tickets', { customerName: 'Instrument Search', problemDescription: 'Test', instrumentDescription: 'Selmer Mark VI Saxophone' })
t.test('searches tickets by item description', { tags: ['tickets', 'search'] }, async () => {
await t.api.post('/v1/repair-tickets', { customerName: 'Item Search', problemDescription: 'Test', itemDescription: 'Samsung Galaxy S24 Ultra' })
const res = await t.api.get('/v1/repair-tickets', { q: 'Mark VI' })
const res = await t.api.get('/v1/repair-tickets', { q: 'Galaxy S24' })
t.assert.status(res, 200)
t.assert.ok(res.data.data.some((t: { instrumentDescription: string }) => t.instrumentDescription?.includes('Mark VI')))
t.assert.ok(res.data.data.some((t: { itemDescription: string }) => t.itemDescription?.includes('Galaxy S24')))
})
t.test('sorts tickets by customer name descending', { tags: ['tickets', 'sort'] }, async () => {
@@ -289,7 +289,7 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
const ticket = await t.api.post('/v1/repair-tickets', { customerName: 'Customer Note', problemDescription: 'Test' })
const res = await t.api.post(`/v1/repair-tickets/${ticket.data.id}/notes`, {
content: 'Your instrument is ready for pickup',
content: 'Your item is ready for pickup',
visibility: 'customer',
})
t.assert.status(res, 201)
@@ -341,17 +341,17 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
const res = await t.api.post('/v1/repair-batches', {
accountId: acct.data.id,
contactName: 'Band Director',
contactName: 'IT Director',
contactPhone: '555-0200',
instrumentCount: 15,
notes: 'Annual instrument checkup',
itemCount: 15,
notes: 'Annual equipment checkup',
})
t.assert.status(res, 201)
t.assert.ok(res.data.batchNumber)
t.assert.equal(res.data.batchNumber.length, 6)
t.assert.equal(res.data.status, 'intake')
t.assert.equal(res.data.approvalStatus, 'pending')
t.assert.equal(res.data.instrumentCount, 15)
t.assert.equal(res.data.itemCount, 15)
})
t.test('returns 404 for missing batch', { tags: ['batches', 'read'] }, async () => {
@@ -377,19 +377,19 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
t.test('updates a batch', { tags: ['batches', 'update'] }, async () => {
const acct = await t.api.post('/v1/accounts', { name: 'Update Batch School', billingMode: 'consolidated' })
const batch = await t.api.post('/v1/repair-batches', { accountId: acct.data.id, instrumentCount: 5 })
const batch = await t.api.post('/v1/repair-batches', { accountId: acct.data.id, itemCount: 5 })
const res = await t.api.patch(`/v1/repair-batches/${batch.data.id}`, { instrumentCount: 10, contactName: 'Updated Director' })
const res = await t.api.patch(`/v1/repair-batches/${batch.data.id}`, { itemCount: 10, contactName: 'Updated Director' })
t.assert.status(res, 200)
t.assert.equal(res.data.contactName, 'Updated Director')
})
t.test('adds tickets to a batch and lists them', { tags: ['batches', 'tickets'] }, async () => {
const acct = await t.api.post('/v1/accounts', { name: 'Batch Tickets School', billingMode: 'consolidated' })
const batch = await t.api.post('/v1/repair-batches', { accountId: acct.data.id, instrumentCount: 2 })
const batch = await t.api.post('/v1/repair-batches', { accountId: acct.data.id, itemCount: 2 })
await t.api.post('/v1/repair-tickets', { customerName: 'Batch Tickets School', repairBatchId: batch.data.id, problemDescription: 'Flute pads', instrumentDescription: 'Flute' })
await t.api.post('/v1/repair-tickets', { customerName: 'Batch Tickets School', repairBatchId: batch.data.id, problemDescription: 'Clarinet cork', instrumentDescription: 'Clarinet' })
await t.api.post('/v1/repair-tickets', { customerName: 'Batch Tickets School', repairBatchId: batch.data.id, problemDescription: 'Screen cracked', itemDescription: 'Chromebook #1' })
await t.api.post('/v1/repair-tickets', { customerName: 'Batch Tickets School', repairBatchId: batch.data.id, problemDescription: 'Battery dead', itemDescription: 'Chromebook #2' })
const tickets = await t.api.get(`/v1/repair-batches/${batch.data.id}/tickets`, { limit: 100 })
t.assert.status(tickets, 200)
@@ -437,36 +437,36 @@ suite('Repairs', { tags: ['repairs'] }, (t) => {
t.test('creates a service template', { tags: ['templates', 'create'] }, async () => {
const res = await t.api.post('/v1/repair-service-templates', {
name: 'Bow Rehair',
instrumentType: 'Violin',
size: '4/4',
name: 'Screen Repair',
itemCategory: 'Electronics',
size: 'Phone',
itemType: 'flat_rate',
defaultPrice: 65,
defaultCost: 15,
})
t.assert.status(res, 201)
t.assert.equal(res.data.name, 'Bow Rehair')
t.assert.equal(res.data.instrumentType, 'Violin')
t.assert.equal(res.data.size, '4/4')
t.assert.equal(res.data.name, 'Screen Repair')
t.assert.equal(res.data.itemCategory, 'Electronics')
t.assert.equal(res.data.size, 'Phone')
t.assert.equal(res.data.defaultPrice, '65.00')
t.assert.equal(res.data.defaultCost, '15.00')
})
t.test('lists service templates with search', { tags: ['templates', 'read'] }, async () => {
await t.api.post('/v1/repair-service-templates', { name: 'String Change', instrumentType: 'Guitar', defaultPrice: 25 })
await t.api.post('/v1/repair-service-templates', { name: 'Battery Replacement', itemCategory: 'Electronics', defaultPrice: 25 })
const res = await t.api.get('/v1/repair-service-templates', { q: 'String', limit: 100 })
const res = await t.api.get('/v1/repair-service-templates', { q: 'Battery', limit: 100 })
t.assert.status(res, 200)
t.assert.ok(res.data.data.some((t: { name: string }) => t.name === 'String Change'))
t.assert.ok(res.data.data.some((t: { name: string }) => t.name === 'Battery Replacement'))
t.assert.ok(res.data.pagination)
})
t.test('updates a service template', { tags: ['templates', 'update'] }, async () => {
const created = await t.api.post('/v1/repair-service-templates', { name: 'Pad Replace', defaultPrice: 30 })
const res = await t.api.patch(`/v1/repair-service-templates/${created.data.id}`, { defaultPrice: 35, instrumentType: 'Clarinet' })
const created = await t.api.post('/v1/repair-service-templates', { name: 'Tune-Up', defaultPrice: 30 })
const res = await t.api.patch(`/v1/repair-service-templates/${created.data.id}`, { defaultPrice: 35, itemCategory: 'Bicycles' })
t.assert.status(res, 200)
t.assert.equal(res.data.defaultPrice, '35.00')
t.assert.equal(res.data.instrumentType, 'Clarinet')
t.assert.equal(res.data.itemCategory, 'Bicycles')
})
t.test('soft-deletes a service template', { tags: ['templates', 'delete'] }, async () => {

View File

@@ -96,14 +96,14 @@ suite('Vault', { tags: ['vault'] }, (t) => {
const res = await t.api.post(`/v1/vault/categories/${catId}/entries`, {
name: 'Store WiFi',
username: 'ForteMusic',
username: 'DemoUser',
url: 'http://192.168.1.1',
notes: 'Router admin panel',
secret: 'supersecretpassword123',
})
t.assert.status(res, 201)
t.assert.equal(res.data.name, 'Store WiFi')
t.assert.equal(res.data.username, 'ForteMusic')
t.assert.equal(res.data.username, 'DemoUser')
t.assert.equal(res.data.hasSecret, true)
// Secret value should NOT be in the response
t.assert.falsy(res.data.encryptedValue)

View File

@@ -27,7 +27,7 @@ async function dav(
suite('WebDAV', { tags: ['webdav', 'storage'] }, (t) => {
// Use the same test user created by the test runner
const email = 'test@forte.dev'
const email = 'test@lunarfront.dev'
const password = 'testpassword1234'
const basicAuth = 'Basic ' + Buffer.from(`${email}:${password}`).toString('base64')
const badAuth = 'Basic ' + Buffer.from(`${email}:wrongpassword`).toString('base64')