From 473868579b05738ddf1246bc814d09d1138e32ea Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Sat, 30 May 2026 03:07:21 +0000 Subject: [PATCH] =?UTF-8?q?GRO-1921:=20seedUatStaffAccounts()=20shared=20f?= =?UTF-8?q?n=20=E2=80=94=20full=20UAT=20seed=20honors=20numeric=20OIDC=20s?= =?UTF-8?q?ubs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract UAT staff account seeding into a shared async function so it runs in both seedKnownUsers() and the full seed() UAT branch. Before this change the full seed() UAT path never created the deterministic UAT staff (UAT Super/Staff/Groomer) with their numeric oidcSub values from SEED_UAT_*_OIDC_SUB env vars — seedKnownUsers() had that logic but was bypassed by SEED_KNOWN_USERS_ONLY=true in the UAT reset CronJob. seedUatStaffAccounts() handles: - UAT Super Staff (SEED_UAT_SUPER_OIDC_SUB) - UAT Staff Groomer (SEED_UAT_STAFF_OIDC_SUB) - UAT Groomer Personas (SEED_UAT_GROOMER_EMAILS + _NAMES) - Better Auth email+password credentials (SEED_UAT_*_PASSWORD) - UAT Customer client + 2 pets Co-Authored-By: Paperclip --- packages/db/src/seed.ts | 185 ++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 93 deletions(-) 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