uat→main (PROD): GRO-2172 pet extended-field schema fix (frozen @c4385617)
CI / Test (pull_request) Successful in 30s
CI / Lint & Typecheck (pull_request) Successful in 34s
CI / Build & Push Docker Images (pull_request) Successful in 1m21s

Promote GRO-2172 from uat to main. Pins src/routes/pets.ts to its exact
content at uat merge commit c4385617 (PR #200), adding the extended pet
profile fields to createPetSchema/updatePetSchema and wiring medicalAlerts
into POST/PATCH /pets:

- temperamentScore: int 1–5
- temperamentFlags: string[] (≤20, each ≤100 chars)
- medicalAlerts: {type,description,severity}[] (≤50)
- preferredCuts: string[] (≤20, each ≤200 chars)
- coatType already present on main; schema now references all 5 fields

Based on main HEAD (03f79a37) so the PR diff is limited to src/routes/pets.ts.
GRO-2311 (uat HEAD 807ccb45) is intentionally excluded.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Flea Flicker
2026-06-09 10:19:25 +00:00
parent 03f79a3701
commit 4bbb0c9fc5
+26 -2
View File
@@ -57,6 +57,23 @@ const createPetSchema = z.object({
customFields: z.record(z.string(), z.string()).optional(), customFields: z.record(z.string(), z.string()).optional(),
petSizeCategory: z.enum(["small", "medium", "large", "extra_large"]).optional(), petSizeCategory: z.enum(["small", "medium", "large", "extra_large"]).optional(),
coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(), coatType: z.enum(["short", "medium", "long", "double", "wire", "silky", "curly", "hairless"]).optional(),
// Extended pet profile fields (api/#39, GRO-1178).
// GRO-2172: these were missing from the schema, causing POST/PATCH to
// silently drop them even though migrations 0034/0036 and seed data
// populate them. GRO-1472 was the original UAT regression.
temperamentScore: z.number().int().min(1).max(5).optional(),
temperamentFlags: z.array(z.string().max(100)).max(20).optional(),
medicalAlerts: z
.array(
z.object({
type: z.string().max(100),
description: z.string().max(1000),
severity: z.enum(["low", "medium", "high"]),
})
)
.max(50)
.optional(),
preferredCuts: z.array(z.string().max(200)).max(20).optional(),
}); });
const updatePetSchema = createPetSchema.partial().omit({ clientId: true }); const updatePetSchema = createPetSchema.partial().omit({ clientId: true });
@@ -333,7 +350,8 @@ petsRouter.get("/:id/profile-summary", async (c) => {
petsRouter.post("/", zValidator("json", createPetSchema), async (c) => { petsRouter.post("/", zValidator("json", createPetSchema), async (c) => {
const db = getDb(); const db = getDb();
const { weightKg, dateOfBirth, customFields, ...rest } = c.req.valid("json"); const { weightKg, dateOfBirth, customFields, medicalAlerts, ...rest } =
c.req.valid("json");
const [row] = await db const [row] = await db
.insert(pets) .insert(pets)
.values({ .values({
@@ -341,6 +359,10 @@ petsRouter.post("/", zValidator("json", createPetSchema), async (c) => {
weightKg: weightKg?.toString(), weightKg: weightKg?.toString(),
dateOfBirth: dateOfBirth ? new Date(dateOfBirth) : undefined, dateOfBirth: dateOfBirth ? new Date(dateOfBirth) : undefined,
customFields: customFields ?? {}, customFields: customFields ?? {},
// GRO-2172: medicalAlerts shape from the API request is
// { type, description, severity } — the @groombook/types MedicalAlert
// has an optional server-generated `id`, so cast for the jsonb column.
medicalAlerts: medicalAlerts as never,
}) })
.returning(); .returning();
return c.json(row, 201); return c.json(row, 201);
@@ -351,7 +373,8 @@ petsRouter.patch(
zValidator("json", updatePetSchema), zValidator("json", updatePetSchema),
async (c) => { async (c) => {
const db = getDb(); const db = getDb();
const { weightKg, dateOfBirth, customFields, ...rest } = c.req.valid("json"); const { weightKg, dateOfBirth, customFields, medicalAlerts, ...rest } =
c.req.valid("json");
const [row] = await db const [row] = await db
.update(pets) .update(pets)
.set({ .set({
@@ -359,6 +382,7 @@ petsRouter.patch(
weightKg: weightKg?.toString(), weightKg: weightKg?.toString(),
dateOfBirth: dateOfBirth ? new Date(dateOfBirth) : undefined, dateOfBirth: dateOfBirth ? new Date(dateOfBirth) : undefined,
...(customFields !== undefined ? { customFields } : {}), ...(customFields !== undefined ? { customFields } : {}),
medicalAlerts: medicalAlerts as never,
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(pets.id, c.req.param("id"))) .where(eq(pets.id, c.req.param("id")))