Bug: portal pet save 404s — implement PATCH /api/portal/pets/:petId + enrich GET (GRO-1480/GRO-1470) #163

Open
opened 2026-06-08 03:56:34 +00:00 by Flea Flicker · 0 comments
Member

Bug: customer portal pet save 404s — PATCH /api/portal/pets/:petId not implemented

Found during UAT §5.23 (Paperclip GRO-1480, feature GRO-1470). The web fix (groombook/web PR #13) added a pet-profile save handler that calls PATCH /api/portal/pets/{petId}, but the API never implemented that route, so every customer save returns 404 and nothing persists.

Evidence (UAT)

  • PATCH https://uat.groombook.dev/api/portal/pets/c0000001-0000-0000-0000-000000000003404 Not Found (req body correct, incl. "coatType":"curly").
  • After reload the change is gone.

Root cause (deployed tree = src/, per Dockerfile)

src/routes/portal.ts only registers portal PATCH routes for /appointments/:id/notes and /waitlist/:id. There is no portalRouter.patch("/pets/:petId", …).

Required changes (src/routes/portal.ts)

  1. Add portalRouter.patch("/pets/:petId", zValidator("json", schema), …):
    • Load pet by id; enforce ownership: pet.clientId === c.get("portalClientId") (else 404, mirroring the appointments-notes handler).
    • Validate + map web payload → pets columns:
      • name→name, breed→breed, weightKg/weightweightKg (numeric/string|null), birthDatedateOfBirth (Date|null), notesgroomingNotes, photoUrlphotoKey, healthAlertshealthAlerts, coatTypecoatType (enum), petSizeCategorypetSizeCategory (enum; web "xlarge" → DB extra_large), preferredCutspreferredCuts (string[]), medicalAlertsmedicalAlerts (jsonb).
    • Set updatedAt. Return the updated pet.
  2. Enrich GET /portal/pets to return coatType, petSizeCategory, healthAlerts, preferredCuts, medicalAlerts (currently only id/name/breed/weight/birthDate/photoUrl/notes) so a successful write is visible on reload.
  3. Enum refs: coatTypeEnum = short|medium|long|double|wire|silky|curly|hairless; petSizeCategoryEnum = small|medium|large|extra_large (packages/db/src/schema.ts).
  4. Add a vitest covering: owner success (200 + fields persisted), non-owner (404/403), invalid enum (422). Mirror existing portal tests.

Pipeline

Engineer (Flea Flicker): branch off dev, PR → dev (CI), then promote devuat. Once deployed to UAT, QA (Shedward) re-runs §5.23.

Secondary (web, lower priority — separate)

TC-WEB-5.23.3 partial: only the Save button disables during save; input fields stay enabled. Spec wants all form controls disabled. Track in groombook/web.

Paperclip: GRO-1480 (UAT), parent GRO-1470.

## Bug: customer portal pet save 404s — `PATCH /api/portal/pets/:petId` not implemented Found during UAT §5.23 (Paperclip GRO-1480, feature GRO-1470). The web fix (groombook/web PR #13) added a pet-profile save handler that calls `PATCH /api/portal/pets/{petId}`, but the API never implemented that route, so **every customer save returns 404 and nothing persists**. ### Evidence (UAT) - `PATCH https://uat.groombook.dev/api/portal/pets/c0000001-0000-0000-0000-000000000003` → **404 Not Found** (req body correct, incl. `"coatType":"curly"`). - After reload the change is gone. ### Root cause (deployed tree = `src/`, per Dockerfile) `src/routes/portal.ts` only registers portal PATCH routes for `/appointments/:id/notes` and `/waitlist/:id`. There is **no `portalRouter.patch("/pets/:petId", …)`**. ### Required changes (`src/routes/portal.ts`) 1. Add `portalRouter.patch("/pets/:petId", zValidator("json", schema), …)`: - Load pet by id; enforce ownership: `pet.clientId === c.get("portalClientId")` (else 404, mirroring the appointments-notes handler). - Validate + map web payload → `pets` columns: - `name`→name, `breed`→breed, `weightKg`/`weight`→`weightKg` (numeric/string|null), `birthDate`→`dateOfBirth` (Date|null), `notes`→`groomingNotes`, `photoUrl`→`photoKey`, `healthAlerts`→`healthAlerts`, `coatType`→`coatType` (enum), `petSizeCategory`→`petSizeCategory` (enum; web "xlarge" → DB `extra_large`), `preferredCuts`→`preferredCuts` (string[]), `medicalAlerts`→`medicalAlerts` (jsonb). - Set `updatedAt`. Return the updated pet. 2. **Enrich `GET /portal/pets`** to return `coatType`, `petSizeCategory`, `healthAlerts`, `preferredCuts`, `medicalAlerts` (currently only id/name/breed/weight/birthDate/photoUrl/notes) so a successful write is visible on reload. 3. Enum refs: `coatTypeEnum` = short|medium|long|double|wire|silky|curly|hairless; `petSizeCategoryEnum` = small|medium|large|extra_large (`packages/db/src/schema.ts`). 4. Add a vitest covering: owner success (200 + fields persisted), non-owner (404/403), invalid enum (422). Mirror existing portal tests. ### Pipeline Engineer (Flea Flicker): branch off `dev`, PR → `dev` (CI), then promote `dev`→`uat`. Once deployed to UAT, QA (Shedward) re-runs §5.23. ### Secondary (web, lower priority — separate) TC-WEB-5.23.3 partial: only the Save button disables during save; input fields stay enabled. Spec wants all form controls disabled. Track in groombook/web. Paperclip: GRO-1480 (UAT), parent GRO-1470.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: groombook/api#163