fix(db): use deterministic IDs + ON CONFLICT DO UPDATE for services seed
seedKnownUsers() inserted demo services with random UUIDs and no
idempotency guard, causing duplicates when the seed ran alongside or
after the main seed (which uses deterministic IDs + ON CONFLICT DO
UPDATE on id). The admin seed API had the same issue.
Now both paths use deterministic UUIDs with ON CONFLICT DO UPDATE on
id, making the upsert idempotent regardless of seed order or repetition.
Same pattern already used for clients (40143c4).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -37,10 +37,10 @@ const DEMO_PET = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEMO_SERVICES = [
|
const DEMO_SERVICES = [
|
||||||
{ name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
{ id: "a0000001-0000-0000-0000-000000000001", name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
||||||
{ name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
{ id: "a0000001-0000-0000-0000-000000000002", name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
||||||
{ name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
{ id: "a0000001-0000-0000-0000-000000000003", name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
||||||
{ name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
{ id: "a0000001-0000-0000-0000-000000000004", name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
||||||
];
|
];
|
||||||
|
|
||||||
adminSeedRouter.post("/seed", async (c) => {
|
adminSeedRouter.post("/seed", async (c) => {
|
||||||
@@ -71,18 +71,16 @@ adminSeedRouter.post("/seed", async (c) => {
|
|||||||
results.push(`Created staff '${KNOWN_STAFF.name}' (id: ${created!.id}, oidcSub: ${KNOWN_STAFF.oidcSub})`);
|
results.push(`Created staff '${KNOWN_STAFF.name}' (id: ${created!.id}, oidcSub: ${KNOWN_STAFF.oidcSub})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Services: only seed if none exist ─────────────────────────────────────
|
// ── Services: idempotent upsert ─────────────────────────────────────────────
|
||||||
const existingServices = await db.select().from(services).limit(1);
|
for (const svc of DEMO_SERVICES) {
|
||||||
if (existingServices.length > 0) {
|
await db.insert(services)
|
||||||
results.push("Services already exist — skipping");
|
.values({ ...svc, active: true })
|
||||||
} else {
|
.onConflictDoUpdate({
|
||||||
const created: { id: string; name: string }[] = [];
|
target: services.id,
|
||||||
for (const svc of DEMO_SERVICES) {
|
set: { name: svc.name, description: svc.description, basePriceCents: svc.basePriceCents, durationMinutes: svc.durationMinutes, active: true },
|
||||||
const [row] = await db.insert(services).values({ ...svc, active: true }).returning();
|
});
|
||||||
created.push(row!);
|
|
||||||
}
|
|
||||||
results.push(`Created ${created.length} services: ${created.map((s) => s.name).join(", ")}`);
|
|
||||||
}
|
}
|
||||||
|
results.push(`Upserted ${DEMO_SERVICES.length} services`);
|
||||||
|
|
||||||
// ── Client: Demo Client ───────────────────────────────────────────────────
|
// ── Client: Demo Client ───────────────────────────────────────────────────
|
||||||
const [existingClient] = await db
|
const [existingClient] = await db
|
||||||
|
|||||||
+15
-15
@@ -293,22 +293,22 @@ async function seedKnownUsers() {
|
|||||||
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
console.log("✓ Created staff 'Demo Manager' (oidcSub: demo-manager-001)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Services: only seed if none exist ──
|
// ── Services: idempotent upsert using deterministic IDs ──
|
||||||
const existingServices = await db.select().from(schema.services).limit(1);
|
const demoSvcs = [
|
||||||
if (existingServices.length > 0) {
|
{ id: "a0000001-0000-0000-0000-000000000001", name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
||||||
console.log("✓ Services already exist — skipping");
|
{ id: "a0000001-0000-0000-0000-000000000002", name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
||||||
} else {
|
{ id: "a0000001-0000-0000-0000-000000000003", name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
||||||
const demoSvcs = [
|
{ id: "a0000001-0000-0000-0000-000000000004", name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
||||||
{ name: "Bath & Brush", description: "Full bath, blow-dry, brush out, and ear cleaning", basePriceCents: 4500, durationMinutes: 45 },
|
];
|
||||||
{ name: "Full Groom — Small", description: "Complete grooming for dogs under 25 lbs", basePriceCents: 6500, durationMinutes: 60 },
|
for (const svc of demoSvcs) {
|
||||||
{ name: "Full Groom — Medium", description: "Complete grooming for dogs 25-50 lbs", basePriceCents: 8000, durationMinutes: 75 },
|
await db.insert(schema.services)
|
||||||
{ name: "Nail Trim", description: "Nail clipping and filing", basePriceCents: 1500, durationMinutes: 15 },
|
.values({ ...svc, active: true })
|
||||||
];
|
.onConflictDoUpdate({
|
||||||
for (const svc of demoSvcs) {
|
target: schema.services.id,
|
||||||
await db.insert(schema.services).values({ ...svc, active: true });
|
set: { name: svc.name, description: svc.description, basePriceCents: svc.basePriceCents, durationMinutes: svc.durationMinutes, active: true },
|
||||||
}
|
});
|
||||||
console.log(`✓ Created ${demoSvcs.length} services`);
|
|
||||||
}
|
}
|
||||||
|
console.log(`✓ Seeded ${demoSvcs.length} services`);
|
||||||
|
|
||||||
// ── Client: Demo Client ──
|
// ── Client: Demo Client ──
|
||||||
const [existingClient] = await db
|
const [existingClient] = await db
|
||||||
|
|||||||
Reference in New Issue
Block a user