fix(db): idempotent services seed — no more duplicate services #185

Merged
groombook-engineer[bot] merged 3 commits from fix/gro-301-duplicate-services into main 2026-04-01 12:28:35 +00:00
groombook-engineer[bot] commented 2026-03-30 17:12:24 +00:00 (Migrated from github.com)

Summary

  • Root cause: seedKnownUsers() (used by POST /api/admin/seed) inserted demo services with defaultRandom() UUIDs and no idempotency guard. The main CLI seed uses deterministic IDs + ON CONFLICT DO UPDATE on id, so running seeds in any order or repetition created duplicate service rows.
  • Fix: Both seed paths now use deterministic UUIDs (a0000001-0000-0000-0000-000000000001004) with ON CONFLICT DO UPDATE on id, matching the pattern already applied to clients in 40143c4.

Changes

  • packages/db/src/seed.ts — replace naive existence check + random-UUID INSERT with deterministic-ID upsert
  • apps/api/src/routes/admin/seed.ts — same fix for the admin API seed endpoint

Test plan

  • Re-run both seed mechanisms in any order and verify no duplicate rows in services
  • Verify GRO-301 is resolved: /admin/services and /admin/book show each service exactly once

🤖 Generated with Claude Code

## Summary - **Root cause:** `seedKnownUsers()` (used by `POST /api/admin/seed`) inserted demo services with `defaultRandom()` UUIDs and no idempotency guard. The main CLI seed uses deterministic IDs + `ON CONFLICT DO UPDATE` on `id`, so running seeds in any order or repetition created duplicate service rows. - **Fix:** Both seed paths now use deterministic UUIDs (`a0000001-0000-0000-0000-000000000001`…`004`) with `ON CONFLICT DO UPDATE` on `id`, matching the pattern already applied to `clients` in `40143c4`. ## Changes - `packages/db/src/seed.ts` — replace naive existence check + random-UUID INSERT with deterministic-ID upsert - `apps/api/src/routes/admin/seed.ts` — same fix for the admin API seed endpoint ## Test plan - [ ] Re-run both seed mechanisms in any order and verify no duplicate rows in `services` - [ ] Verify GRO-301 is resolved: `/admin/services` and `/admin/book` show each service exactly once 🤖 Generated with [Claude Code](https://claude.com/claude-code)
groombook-engineer[bot] commented 2026-03-30 17:12:57 +00:00 (Migrated from github.com)

Summary

Fixed the duplicate services bug in both seed paths by using deterministic UUIDs + ON CONFLICT DO UPDATE on id.

cc @cpfarhood

Parent issue: GRO-299
This issue: GRO-301

## Summary Fixed the duplicate services bug in both seed paths by using deterministic UUIDs + `ON CONFLICT DO UPDATE` on `id`. cc @cpfarhood **Parent issue:** [GRO-299](/GRO/issues/GRO-299) **This issue:** [GRO-301](/GRO/issues/GRO-301)
github-actions[bot] commented 2026-03-30 17:17:53 +00:00 (Migrated from github.com)

Deployed to groombook-dev

Images: pr-185
URL: https://dev.groombook.farh.net

Ready for UAT validation.

## Deployed to groombook-dev **Images:** `pr-185` **URL:** https://dev.groombook.farh.net Ready for UAT validation.
the-dogfather-cto[bot] (Migrated from github.com) reviewed 2026-03-31 18:09:57 +00:00
the-dogfather-cto[bot] (Migrated from github.com) left a comment

CTO Code Review — PR #185

Assessment: Code is correct ✓

The fix replaces the "skip if any service exists" seed logic with idempotent upserts using deterministic UUIDs and onConflictDoUpdate. This is the right approach:

  1. Deterministic IDs (a0000001-...) ensure re-seeds update rather than duplicate
  2. onConflictDoUpdate on services.id is clean Drizzle ORM usage
  3. Both seed paths updated (apps/api/src/routes/admin/seed.ts and packages/db/src/seed.ts)

⚠️ Note: Overlapping changes with PR #186

PR #186 includes identical seed changes plus the reports UTC fix. These two PRs will conflict when one merges. Recommendation: merge one, rebase the other.

QA Test Steps

  1. Navigate to /admin → check services in the booking flow or admin panel
  2. Verify exactly 4 services appear: Bath & Brush, Full Groom — Small, Full Groom — Medium, Nail Trim
  3. Trigger /api/admin/seed again → verify still exactly 4 services (no duplicates created)
  4. Confirm services appear only once in the customer booking flow

Awaiting QA review before formal CTO approval.

## CTO Code Review — PR #185 ### Assessment: Code is correct ✓ The fix replaces the "skip if any service exists" seed logic with idempotent upserts using deterministic UUIDs and `onConflictDoUpdate`. This is the right approach: 1. **Deterministic IDs** (`a0000001-...`) ensure re-seeds update rather than duplicate 2. **`onConflictDoUpdate`** on `services.id` is clean Drizzle ORM usage 3. Both seed paths updated (`apps/api/src/routes/admin/seed.ts` and `packages/db/src/seed.ts`) ### ⚠️ Note: Overlapping changes with PR #186 PR #186 includes **identical** seed changes plus the reports UTC fix. These two PRs will conflict when one merges. Recommendation: merge one, rebase the other. ### QA Test Steps 1. Navigate to `/admin` → check services in the booking flow or admin panel 2. Verify exactly 4 services appear: Bath & Brush, Full Groom — Small, Full Groom — Medium, Nail Trim 3. Trigger `/api/admin/seed` again → verify still exactly 4 services (no duplicates created) 4. Confirm services appear only once in the customer booking flow Awaiting QA review before formal CTO approval.
github-actions[bot] commented 2026-03-31 18:46:35 +00:00 (Migrated from github.com)

Deployed to groombook-dev

Images: pr-185
URL: https://dev.groombook.farh.net

Ready for UAT validation.

## Deployed to groombook-dev **Images:** `pr-185` **URL:** https://dev.groombook.farh.net Ready for UAT validation.
the-dogfather-cto[bot] (Migrated from github.com) approved these changes 2026-03-31 21:19:58 +00:00
the-dogfather-cto[bot] (Migrated from github.com) left a comment

CTO Approval

Idempotent services seed fix is correct. Deterministic UUIDs, proper onConflictDoUpdate, dedup cleanup. All CI green.

cc @cpfarhood

## CTO Approval Idempotent services seed fix is correct. Deterministic UUIDs, proper onConflictDoUpdate, dedup cleanup. All CI green. cc @cpfarhood
github-actions[bot] commented 2026-03-31 21:30:04 +00:00 (Migrated from github.com)

Deployed to groombook-dev

Images: pr-185
URL: https://dev.groombook.farh.net

Ready for UAT validation.

## Deployed to groombook-dev **Images:** `pr-185` **URL:** https://dev.groombook.farh.net Ready for UAT validation.
lint-roller-qa[bot] (Migrated from github.com) approved these changes 2026-03-31 22:30:43 +00:00
lint-roller-qa[bot] (Migrated from github.com) left a comment

QA Approval — PR #185

Tester: Lint Roller

CI Verification

All CI checks green:

  • Lint & Typecheck:
  • Test:
  • E2E Tests:
  • Build:
  • Build & Push Docker Images:
  • Deploy PR to groombook-dev:

Code Review

The fix correctly addresses the duplicate services bug:

  1. Deterministic IDs — Both seed paths now use pre-assigned deterministic UUIDs (a0000001... for admin seed, b0000001... for main seed) instead of random uuid() calls
  2. onConflictDoUpdate on services.id — True idempotent upserts regardless of seed order or repetition
  3. Deduplication query — Main seed includes a DELETE ... WHERE id NOT IN (SELECT MIN(id) GROUP BY name) cleanup that removes existing duplicates before re-inserting
  4. Preserved serviceIds array — Main seed appointment lookup code still has the service IDs it needs

Files Changed

  • apps/api/src/routes/admin/seed.ts — Admin seed path (4 services, idempotent upsert)
  • packages/db/src/seed.ts — Main seed path (10 services, deterministic IDs + dedup)

Recommendation

APPROVE — Fix is correct, CI is green. Ready for merge.

cc @cpfarhood

## QA Approval — PR #185 ✅ **Tester:** Lint Roller ### CI Verification All CI checks green: - Lint & Typecheck: ✅ - Test: ✅ - E2E Tests: ✅ - Build: ✅ - Build & Push Docker Images: ✅ - Deploy PR to groombook-dev: ✅ ### Code Review The fix correctly addresses the duplicate services bug: 1. **Deterministic IDs** — Both seed paths now use pre-assigned deterministic UUIDs (a0000001... for admin seed, b0000001... for main seed) instead of random uuid() calls 2. **onConflictDoUpdate on services.id** — True idempotent upserts regardless of seed order or repetition 3. **Deduplication query** — Main seed includes a DELETE ... WHERE id NOT IN (SELECT MIN(id) GROUP BY name) cleanup that removes existing duplicates before re-inserting 4. **Preserved serviceIds array** — Main seed appointment lookup code still has the service IDs it needs ### Files Changed - apps/api/src/routes/admin/seed.ts — Admin seed path (4 services, idempotent upsert) - packages/db/src/seed.ts — Main seed path (10 services, deterministic IDs + dedup) ### Recommendation **APPROVE** — Fix is correct, CI is green. Ready for merge. cc @cpfarhood
github-actions[bot] commented 2026-04-01 12:28:17 +00:00 (Migrated from github.com)

Deployed to groombook-dev

Images: pr-185
URL: https://dev.groombook.farh.net

Ready for UAT validation.

## Deployed to groombook-dev **Images:** `pr-185` **URL:** https://dev.groombook.farh.net Ready for UAT validation.
This repo is archived. You cannot comment on pull requests.