fix(api): GRO-2014 — profile-summary 500 → 404/401/JSON-500 (#137)
CI / Lint & Typecheck (push) Successful in 16s
CI / Test (push) Successful in 16s
CI / Build & Push Docker Images (push) Successful in 46s
CI / Test (pull_request) Successful in 12s
CI / Lint & Typecheck (pull_request) Successful in 14s
CI / Build & Push Docker Images (pull_request) Failing after 18s
CI / Lint & Typecheck (push) Successful in 16s
CI / Test (push) Successful in 16s
CI / Build & Push Docker Images (push) Successful in 46s
CI / Test (pull_request) Successful in 12s
CI / Lint & Typecheck (pull_request) Successful in 14s
CI / Build & Push Docker Images (pull_request) Failing after 18s
This commit was merged in pull request #137.
This commit is contained in:
+34
-1
@@ -23,6 +23,23 @@ import {
|
||||
|
||||
export const petsRouter = new Hono<AppEnv>();
|
||||
|
||||
// Convert Zod validation errors from 422 to 400 and ensure any thrown error
|
||||
// returns a structured JSON body rather than Hono's default empty-body 500.
|
||||
// GRO-2014: profile-summary previously bubbled unhandled errors and produced
|
||||
// an empty-body 500. Mirror the onError pattern already used in invoices.ts
|
||||
// and reports.ts so every error has a JSON envelope.
|
||||
petsRouter.onError((err, c) => {
|
||||
if (err instanceof z.ZodError) {
|
||||
return c.json({ error: "Validation failed", issues: err.issues }, 400);
|
||||
}
|
||||
console.error("[pets] unhandled error", err);
|
||||
return c.json({ error: "Internal Server Error" }, 500);
|
||||
});
|
||||
|
||||
// UUID format used by all pet routes — guards path params against malformed
|
||||
// values before they hit Drizzle / Postgres uuid columns (which would throw).
|
||||
const uuidSchema = z.string().uuid();
|
||||
|
||||
const createPetSchema = z.object({
|
||||
clientId: z.string().uuid(),
|
||||
name: z.string().min(1).max(200),
|
||||
@@ -112,8 +129,24 @@ petsRouter.get("/:id", async (c) => {
|
||||
petsRouter.get("/:id/profile-summary", async (c) => {
|
||||
const db = getDb();
|
||||
const petId = c.req.param("id");
|
||||
|
||||
// GRO-2014: validate UUID format before hitting Postgres. Passing a non-UUID
|
||||
// string to a uuid column makes the driver throw, which previously surfaced
|
||||
// as an empty-body 500 to clients.
|
||||
const parsedId = uuidSchema.safeParse(petId);
|
||||
if (!parsedId.success) {
|
||||
return c.json({ error: "Not found" }, 404);
|
||||
}
|
||||
|
||||
// Defense in depth: resolveStaffMiddleware should always populate `staff`
|
||||
// for protected routes (or short-circuit with 401/403 of its own). Guard
|
||||
// anyway so a misconfigured route mount can't trigger a TypeError on
|
||||
// staffRow.id when the linkage check runs.
|
||||
const staffRow = c.get("staff");
|
||||
const isGroomer = staffRow?.role === "groomer";
|
||||
if (!staffRow) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
const isGroomer = staffRow.role === "groomer";
|
||||
|
||||
// Fetch the pet
|
||||
const [pet] = await db.select().from(pets).where(eq(pets.id, petId));
|
||||
|
||||
Reference in New Issue
Block a user