From c8f23e953aa4515b4de1928ce9b2796f7d4a6278 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Sat, 16 May 2026 15:53:56 -0600 Subject: [PATCH] fix(api): address remaining CodeRabbit review comments on #2414 - expo/current-pack: switch PackItem import to expo features type so pack.items matches without the misleading `as unknown as` double cast. - api/weather: handle ZodError separately when forecast response fails validation, surfacing the invalid paths instead of a generic throw. - api/catalog: remove redundant manual validations on POST/PUT (now fully enforced by Create/UpdateCatalogItemRequestSchema). Make UpdateCatalogItemRequestSchema.weight positive to match Create. Clarify config-error message when OPENAI_API_KEY is missing. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/expo/app/(app)/current-pack/[id].tsx | 7 ++----- packages/api/src/routes/catalog/index.ts | 15 ++++----------- packages/api/src/routes/weather.ts | 6 ++++++ packages/api/src/schemas/catalog.ts | 2 +- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/expo/app/(app)/current-pack/[id].tsx b/apps/expo/app/(app)/current-pack/[id].tsx index 0aabb315b2..f5fe095587 100644 --- a/apps/expo/app/(app)/current-pack/[id].tsx +++ b/apps/expo/app/(app)/current-pack/[id].tsx @@ -1,4 +1,3 @@ -import type { PackItem } from '@packrat/api/types/constants'; import { Avatar, AvatarFallback, @@ -8,6 +7,7 @@ import { } from '@packrat/ui/nativewindui'; import { userStore } from 'expo-app/features/auth/store'; import { usePackDetailsFromStore } from 'expo-app/features/packs/hooks/usePackDetailsFromStore'; +import type { PackItem } from 'expo-app/features/packs/types'; import { type CategorySummary, computeCategorySummaries } from 'expo-app/features/packs/utils'; import { cn } from 'expo-app/lib/cn'; import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme'; @@ -192,10 +192,7 @@ export default function CurrentPackScreen() { index.toString()} - renderItem={(item, index) => ( - // safe-cast: Treaty response type has createdAt?: string but PackItem schema requires string - - )} + renderItem={(item, index) => } /> diff --git a/packages/api/src/routes/catalog/index.ts b/packages/api/src/routes/catalog/index.ts index a6f5eb9549..5a90d95bd7 100644 --- a/packages/api/src/routes/catalog/index.ts +++ b/packages/api/src/routes/catalog/index.ts @@ -246,17 +246,12 @@ export const catalogRoutes = new Elysia({ prefix: '/catalog' }) async ({ body }) => { const db = createDb(); const data = body; - if (!data.name || data.weight === undefined || data.weight === null || !data.weightUnit) { - throw new Error('name, weight, and weightUnit are required'); - } - if (data.weight <= 0) { - throw new Error('weight must be a positive number'); - } const { OPENAI_API_KEY, AI_PROVIDER, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_AI_GATEWAY_ID, AI } = getEnv(); if (!OPENAI_API_KEY) { - throw new Error('OpenAI API key not configured'); + // Configuration error: surface as a 500 with a clear message + throw new Error('Service unavailable: OpenAI API key not configured'); } const embeddingText = getEmbeddingText(data); @@ -439,14 +434,12 @@ export const catalogRoutes = new Elysia({ prefix: '/catalog' }) throw new NotFoundError('Catalog item not found'); } const data = body; - if (data.weight !== undefined && data.weight !== null && data.weight <= 0) { - throw new Error('weight must be a positive number'); - } const { OPENAI_API_KEY, AI_PROVIDER, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_AI_GATEWAY_ID, AI } = getEnv(); if (!OPENAI_API_KEY) { - throw new Error('OpenAI API key not configured'); + // Configuration error: surface as a 500 with a clear message + throw new Error('Service unavailable: OpenAI API key not configured'); } const existingItem = await db.query.catalogItems.findFirst({ diff --git a/packages/api/src/routes/weather.ts b/packages/api/src/routes/weather.ts index c6bbd628ed..46a0e7ed21 100644 --- a/packages/api/src/routes/weather.ts +++ b/packages/api/src/routes/weather.ts @@ -11,6 +11,7 @@ import { import { getEnv } from '@packrat/api/utils/env-validation'; import { isString } from '@packrat/guards'; import { Elysia, status } from 'elysia'; +import { ZodError } from 'zod'; const WEATHER_API_BASE_URL = 'https://api.weatherapi.com/v1'; @@ -153,6 +154,11 @@ export const weatherRoutes = new Elysia({ prefix: '/weather' }) }, }); } catch (error) { + if (error instanceof ZodError) { + const invalidPaths = error.errors.map((e) => e.path.join('.')).join(', '); + console.error('Weather forecast response failed schema validation:', error.errors); + throw new Error(`Weather forecast response failed schema validation at: ${invalidPaths}`); + } console.error('Error fetching weather forecast:', error); throw error; } diff --git a/packages/api/src/schemas/catalog.ts b/packages/api/src/schemas/catalog.ts index 3b9aff1550..c8bf1d82d0 100644 --- a/packages/api/src/schemas/catalog.ts +++ b/packages/api/src/schemas/catalog.ts @@ -237,7 +237,7 @@ export const UpdateCatalogItemRequestSchema = z.object({ name: z.string().min(1).max(255).optional(), productUrl: z.string().url().optional(), sku: z.string().optional(), - weight: z.number().optional(), + weight: z.number().positive().optional(), weightUnit: z.enum(WEIGHT_UNITS).optional(), description: z.string().optional(), categories: z.array(z.string()).optional(),