Company table gains address and logo_file_id columns. New store settings API: GET/PATCH /store for company info, full CRUD for /locations. Settings page shows store name, phone, email, address, timezone with inline edit. Location cards with add/edit/delete. Settings link in admin sidebar. Fixes leftover company_id on location table and seed files.
104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
import type { FastifyInstance } from 'fastify'
|
|
import { buildApp } from '../main.js'
|
|
import { sql, eq } from 'drizzle-orm'
|
|
import { companies, locations } from '../db/schema/stores.js'
|
|
import { UnitStatusService, ItemConditionService } from '../services/lookup.service.js'
|
|
import { RbacService } from '../services/rbac.service.js'
|
|
import { roles } from '../db/schema/rbac.js'
|
|
import { users } from '../db/schema/users.js'
|
|
|
|
export const TEST_COMPANY_ID = '00000000-0000-0000-0000-000000000099'
|
|
export const TEST_LOCATION_ID = '00000000-0000-0000-0000-000000000099'
|
|
|
|
export async function createTestApp(): Promise<FastifyInstance> {
|
|
const app = await buildApp()
|
|
await app.ready()
|
|
return app
|
|
}
|
|
|
|
export async function cleanDb(app: FastifyInstance): Promise<void> {
|
|
await app.db.execute(sql`
|
|
DO $$ DECLARE
|
|
r RECORD;
|
|
BEGIN
|
|
SET client_min_messages TO WARNING;
|
|
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
|
|
EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE';
|
|
END LOOP;
|
|
RESET client_min_messages;
|
|
END $$
|
|
`)
|
|
}
|
|
|
|
export async function seedTestCompany(app: FastifyInstance): Promise<void> {
|
|
await app.db.insert(companies).values({
|
|
id: TEST_COMPANY_ID,
|
|
name: 'Test Music Co.',
|
|
timezone: 'America/Chicago',
|
|
})
|
|
await app.db.insert(locations).values({
|
|
id: TEST_LOCATION_ID,
|
|
name: 'Test Location',
|
|
})
|
|
|
|
await UnitStatusService.seedDefaults(app.db)
|
|
await ItemConditionService.seedDefaults(app.db)
|
|
|
|
// Seed RBAC permissions and default roles
|
|
await RbacService.seedPermissions(app.db)
|
|
await RbacService.seedDefaultRoles(app.db)
|
|
}
|
|
|
|
export async function registerAndLogin(
|
|
app: FastifyInstance,
|
|
overrides: {
|
|
email?: string
|
|
password?: string
|
|
firstName?: string
|
|
lastName?: string
|
|
role?: string
|
|
} = {},
|
|
): Promise<{ token: string; user: Record<string, unknown> }> {
|
|
const response = await app.inject({
|
|
method: 'POST',
|
|
url: '/v1/auth/register',
|
|
headers: { 'x-company-id': TEST_COMPANY_ID },
|
|
payload: {
|
|
email: overrides.email ?? 'test@forte.dev',
|
|
password: overrides.password ?? 'testpassword1234',
|
|
firstName: overrides.firstName ?? 'Test',
|
|
lastName: overrides.lastName ?? 'User',
|
|
role: overrides.role ?? 'admin',
|
|
},
|
|
})
|
|
|
|
const body = response.json()
|
|
|
|
// Assign the admin role to the test user so they have all permissions
|
|
if (body.user?.id) {
|
|
const [adminRole] = await app.db
|
|
.select()
|
|
.from(roles)
|
|
.where(eq(roles.slug, 'admin'))
|
|
.limit(1)
|
|
|
|
if (adminRole) {
|
|
await RbacService.assignRole(app.db, body.user.id, adminRole.id)
|
|
}
|
|
|
|
// Re-login to get a fresh token (permissions are loaded on authenticate)
|
|
const loginRes = await app.inject({
|
|
method: 'POST',
|
|
url: '/v1/auth/login',
|
|
payload: {
|
|
email: overrides.email ?? 'test@forte.dev',
|
|
password: overrides.password ?? 'testpassword1234',
|
|
},
|
|
})
|
|
const loginBody = loginRes.json()
|
|
return { token: loginBody.token, user: loginBody.user }
|
|
}
|
|
|
|
return { token: body.token, user: body.user }
|
|
}
|