diff --git a/packages/db/src/seed.ts b/packages/db/src/seed.ts index 3e90a9d..375ffe1 100644 --- a/packages/db/src/seed.ts +++ b/packages/db/src/seed.ts @@ -385,78 +385,19 @@ const servicesDef = [ { id: "b0000001-0000-0000-0000-00000000000a", name: "Sanitary Trim", desc: "Hygienic trim of paw pads, face, and sanitary areas", price: 2500, dur: 20 }, ]; -// ── Known-users-only seed (prod/demo) ─────────────────────────────────────── +// ── UAT staff account seeding (shared between seed paths) ───────────────────── /** - * Seeds only the minimal known users for prod/demo environments. - * Creates: Demo Manager staff + Demo Client + Demo Dog + basic services. - * Idempotent: skips creation if records already exist. + * Seeds or upserts the deterministic UAT staff accounts with numeric OIDC subs + * from SEED_UAT_*_OIDC_SUB / SEED_UAT_GROOMER_OIDC_SUBS env vars. + * + * In the full seed path this must run AFTER random staff are created so the + * deterministic upserts land on the correct rows (groomers referenced by the + * UAT test-client appointment logic use groomers[0] etc.). + * + * In seedKnownUsers() this replaces the inline UAT-staff block. */ -async function seedKnownUsers() { - const url = process.env.DATABASE_URL; - if (!url) { - console.error("DATABASE_URL is not set"); - process.exit(1); - } - - const client = postgres(url, { max: 5 }); - const db = drizzle(client, { schema }); - - console.log("Seeding known users (prod/demo mode)...\n"); - - const KNOWN_STAFF_ID = "00000000-0000-0000-0000-000000000001"; - const DEMO_CLIENT_ID = "00000000-0000-0000-0000-000000000002"; - const DEMO_PET_ID = "00000000-0000-0000-0000-000000000003"; - - // ── Staff: Demo Manager ── - const [existingStaff] = await db - .select() - .from(schema.staff) - .where(eq(schema.staff.email, "demo-manager@groombook.dev")) - .limit(1); - - if (existingStaff) { - console.log(`✓ Staff '${existingStaff.name}' already exists — skipping`); - } else { - await db.insert(schema.staff).values({ - id: KNOWN_STAFF_ID, - name: "Demo Manager", - email: "demo-manager@groombook.dev", - oidcSub: "demo-manager-001", - role: "manager", - isSuperUser: true, - active: true, - }); - console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)"); - } - - // ── Staff: SEED_ADMIN_EMAIL admin ── - const adminEmail = process.env.SEED_ADMIN_EMAIL; - if (adminEmail) { - const adminName = process.env.SEED_ADMIN_NAME ?? "Admin"; - const ADMIN_STAFF_ID = "00000000-0000-0000-0000-000000000002"; - const [existingAdmin] = await db - .select() - .from(schema.staff) - .where(eq(schema.staff.email, adminEmail)) - .limit(1); - - if (existingAdmin) { - console.log(`✓ Staff admin '${existingAdmin.name}' already exists — skipping`); - } else { - await db.insert(schema.staff).values({ - id: ADMIN_STAFF_ID, - name: adminName, - email: adminEmail, - oidcSub: adminEmail, - role: "manager", - isSuperUser: true, - active: true, - }); - console.log(`✓ Created staff admin '${adminName}' (${adminEmail})`); - } - } - +async function seedUatStaffAccounts(db: ReturnType) { // ── Staff: UAT Super User (oidcSub from SEED_UAT_SUPER_OIDC_SUB env var) ── const uatSuperOidcSub = process.env.SEED_UAT_SUPER_OIDC_SUB; if (uatSuperOidcSub) { @@ -680,6 +621,84 @@ async function seedKnownUsers() { console.log(`✓ Created UAT pet '${pet.name}'`); } } +} + +// ── Known-users-only seed (prod/demo) ─────────────────────────────────────── + +/** + * Seeds only the minimal known users for prod/demo environments. + * Creates: Demo Manager staff + Demo Client + Demo Dog + basic services. + * Idempotent: skips creation if records already exist. + */ +async function seedKnownUsers() { + const url = process.env.DATABASE_URL; + if (!url) { + console.error("DATABASE_URL is not set"); + process.exit(1); + } + + const client = postgres(url, { max: 5 }); + const db = drizzle(client, { schema }); + + console.log("Seeding known users (prod/demo mode)...\n"); + + const KNOWN_STAFF_ID = "00000000-0000-0000-0000-000000000001"; + const DEMO_CLIENT_ID = "00000000-0000-0000-0000-000000000002"; + const DEMO_PET_ID = "00000000-0000-0000-0000-000000000003"; + + // ── Staff: Demo Manager ── + const [existingStaff] = await db + .select() + .from(schema.staff) + .where(eq(schema.staff.email, "demo-manager@groombook.dev")) + .limit(1); + + if (existingStaff) { + console.log(`✓ Staff '${existingStaff.name}' already exists — skipping`); + } else { + await db.insert(schema.staff).values({ + id: KNOWN_STAFF_ID, + name: "Demo Manager", + email: "demo-manager@groombook.dev", + oidcSub: "demo-manager-001", + role: "manager", + isSuperUser: true, + active: true, + }); + console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)"); + } + + // ── Staff: SEED_ADMIN_EMAIL admin ── + const adminEmail = process.env.SEED_ADMIN_EMAIL; + if (adminEmail) { + const adminName = process.env.SEED_ADMIN_NAME ?? "Admin"; + const ADMIN_STAFF_ID = "00000000-0000-0000-0000-000000000002"; + const [existingAdmin] = await db + .select() + .from(schema.staff) + .where(eq(schema.staff.email, adminEmail)) + .limit(1); + + if (existingAdmin) { + console.log(`✓ Staff admin '${existingAdmin.name}' already exists — skipping`); + } else { + await db.insert(schema.staff).values({ + id: ADMIN_STAFF_ID, + name: adminName, + email: adminEmail, + oidcSub: adminEmail, + role: "manager", + isSuperUser: true, + active: true, + }); + console.log(`✓ Created staff admin '${adminName}' (${adminEmail})`); + } + } + + // ── UAT staff accounts + Better Auth credentials (shared impl) ────────────── + // Extracted into seedUatStaffAccounts() so it runs in both seedKnownUsers() + // and the full seed() UAT branch. + await seedUatStaffAccounts(db); // ── Services: idempotent upsert using name as unique key ───────────────────── // UNIQUE constraint on services.name (migration 0020) must exist first. @@ -847,30 +866,10 @@ async function seed() { console.log(`✓ Upserted admin staff '${adminName}' (${adminEmail})`); } - // ── UAT Groomer Personas (SEED_UAT_GROOMER_EMAILS + SEED_UAT_GROOMER_NAMES) ── - const groomerEmails = process.env.SEED_UAT_GROOMER_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) ?? []; - const groomerNames = process.env.SEED_UAT_GROOMER_NAMES?.split(",").map((n) => n.trim()).filter(Boolean) ?? []; - const groomerCount = Math.min(groomerEmails.length, groomerNames.length); - for (let i = 0; i < groomerCount; i++) { - const email = groomerEmails[i]!; - const name = groomerNames[i]!; - const staffId = `00000000-0000-0000-0000-${String(5 + i).padStart(12, "0")}`; - await db.insert(schema.staff) - .values({ - id: staffId, - name, - email, - oidcSub: email, - role: "groomer", - isSuperUser: false, - active: true, - }) - .onConflictDoUpdate({ - target: schema.staff.email, - set: { id: staffId, name, role: "groomer", isSuperUser: false, active: true }, - }); - console.log(`✓ Upserted groomer '${name}' (${email})`); - } + // ── UAT staff accounts + Better Auth credentials (shared impl) ────────────── + // Seeds deterministic UAT staff with numeric OIDC subs and Better Auth credentials. + // Must run AFTER random staff are created so upserts land correctly. + await seedUatStaffAccounts(db); // ── Services ── // Upsert services using name as unique key. With deterministic IDs in