diff --git a/apps/api/src/db/seed.ts b/apps/api/src/db/seed.ts index a20239d..004213b 100644 --- a/apps/api/src/db/seed.ts +++ b/apps/api/src/db/seed.ts @@ -4,10 +4,23 @@ import { db } from './client'; import { activities, user } from './schema'; import { auth } from '../auth'; -// Dev-only test account so `db:seed` yields ready-made login credentials for local -// testing / phone demos. Created through better-auth (password is properly hashed). -// NEVER seeded when NODE_ENV=production โ€” see seedDevUser(). -const DEV_USER = { email: 'worker@solelog.local', password: 'werkplaats123', name: 'Test Werker' }; +// Dev-only accounts so `db:seed` yields ready-made logins for local testing / phone demos. +// Created through better-auth's admin createUser (hashes the password, sets the role, and works +// even though public sign-up is disabled). NEVER seeded when NODE_ENV=production. +const DEV_ACCOUNTS = [ + { + email: 'worker@solelog.local', + password: 'werkplaats123', + name: 'Test Werker', + role: 'worker' as const, + }, + { + email: 'admin@solelog.local', + password: 'werkplaats-admin', + name: 'Test Beheerder', + role: 'admin' as const, + }, +]; // Reference activities (realistic Dutch handeling names) โ€” see // docs/reference/legacy-mobile-app.md ยง6.2 and the Phase 1 plan. @@ -20,24 +33,22 @@ const REFERENCE_ACTIVITIES: { name: string; insoleTypes: string[] }[] = [ { name: 'Printen', insoleTypes: ['3D'] }, ]; -// Idempotent: create the dev account once, and only outside production (it has a -// known password). Uses better-auth's server API so the user + hashed-password -// account rows are created exactly as a real sign-up would. -async function seedDevUser(): Promise { +async function seedDevUsers(): Promise { if (process.env.NODE_ENV === 'production') return; - const existing = await db.select().from(user).where(eq(user.email, DEV_USER.email)); - if (existing.length > 0) return; - await auth.api.signUpEmail({ body: DEV_USER }); - console.log(`Seeded dev account: ${DEV_USER.email} / ${DEV_USER.password}`); + for (const acc of DEV_ACCOUNTS) { + const existing = await db.select().from(user).where(eq(user.email, acc.email)); + if (existing.length > 0) continue; + await auth.api.createUser({ + body: { email: acc.email, password: acc.password, name: acc.name, role: acc.role }, + }); + console.log(`Seeded dev ${acc.role}: ${acc.email} / ${acc.password}`); + } } // Idempotent: insert each reference activity only if no activity with that name exists. export async function seed(): Promise { for (const activity of REFERENCE_ACTIVITIES) { - const existing = await db - .select() - .from(activities) - .where(eq(activities.name, activity.name)); + const existing = await db.select().from(activities).where(eq(activities.name, activity.name)); if (existing.length === 0) { await db.insert(activities).values({ name: activity.name, @@ -45,7 +56,7 @@ export async function seed(): Promise { }); } } - await seedDevUser(); + await seedDevUsers(); } // Allow running directly: `tsx src/db/seed.ts` (cross-platform โ€” pathToFileURL diff --git a/apps/api/test/seed.test.ts b/apps/api/test/seed.test.ts index e29b457..e1b8ba8 100644 --- a/apps/api/test/seed.test.ts +++ b/apps/api/test/seed.test.ts @@ -9,36 +9,31 @@ const SEED_NAMES = ['Leerrand', 'Frezen', 'Slijpen', 'Bekleden', 'Afwerken', 'Pr describe('seed', () => { it('seeds the reference activities idempotently', async () => { await seed(); - const first = await db - .select() - .from(activities) - .where(inArray(activities.name, SEED_NAMES)); + const first = await db.select().from(activities).where(inArray(activities.name, SEED_NAMES)); const countFirst = first.length; await seed(); - const second = await db - .select() - .from(activities) - .where(inArray(activities.name, SEED_NAMES)); + const second = await db.select().from(activities).where(inArray(activities.name, SEED_NAMES)); expect(second.length).toBe(countFirst); expect(countFirst).toBe(SEED_NAMES.length); - const printen = await db - .select() - .from(activities) - .where(eq(activities.name, 'Printen')); + const printen = await db.select().from(activities).where(eq(activities.name, 'Printen')); expect(printen).toHaveLength(1); expect(printen[0]?.insoleTypes).toEqual(['3D']); }); - it('seeds the dev test account idempotently (and only once)', async () => { + it('seeds the dev worker + dev admin idempotently with correct roles', async () => { await seed(); - const first = await db.select().from(user).where(eq(user.email, 'worker@solelog.local')); - expect(first).toHaveLength(1); + const w = await db.select().from(user).where(eq(user.email, 'worker@solelog.local')); + const a = await db.select().from(user).where(eq(user.email, 'admin@solelog.local')); + expect(w).toHaveLength(1); + expect(a).toHaveLength(1); + expect((a[0] as { role?: string }).role).toBe('admin'); await seed(); - const second = await db.select().from(user).where(eq(user.email, 'worker@solelog.local')); - expect(second).toHaveLength(1); + expect(await db.select().from(user).where(eq(user.email, 'admin@solelog.local'))).toHaveLength( + 1 + ); }); });