import { z } from 'zod'; /** * Packing API contract — single source of truth for the * /api/trips/:tripId/packing endpoints (items, bags, templates, assignees). * * Trip-scoped: every endpoint verifies trip access (404 "Trip not found") and * mutations additionally check the 'packing_edit' permission (403 "No * permission"). The legacy route (server/src/routes/packing.ts) wraps * services/packingService.ts; rows are DB-shaped and kept as open records here. * Mutations broadcast over WebSocket using the forwarded X-Socket-Id. */ const open = z.record(z.string(), z.unknown()); /** * Packing item entity as returned by the packing endpoints * (server/src/services/packingService.ts -> SELECT * FROM packing_items). * `checked` is the raw SQLite INTEGER (0/1). Columns match the packing_items * table (see server DB): weight_grams/bag_id are nullable, quantity defaults 1. */ export const packingItemSchema = z.object({ id: z.number(), trip_id: z.number(), name: z.string(), checked: z.number(), category: z.string().nullable().optional(), sort_order: z.number(), weight_grams: z.number().nullable().optional(), bag_id: z.number().nullable().optional(), quantity: z.number().optional(), created_at: z.string().optional(), }); export type PackingItem = z.infer; /** * Packing bag member embedded on a bag (server packingService -> listBags). * `avatar` is the resolved avatar URL. */ export const packingBagMemberSchema = z.object({ user_id: z.number(), username: z.string(), avatar: z.string().nullable().optional(), }); export type PackingBagMember = z.infer; /** * Packing bag entity (server packingService -> listBags). Columns of the * packing_bags table plus the embedded `members` array (and the optional * `assigned_username` join present on updateBag). */ export const packingBagSchema = z.object({ id: z.number(), trip_id: z.number(), name: z.string(), color: z.string(), weight_limit_grams: z.number().nullable().optional(), sort_order: z.number(), user_id: z.number().nullable().optional(), assigned_username: z.string().nullable().optional(), created_at: z.string().optional(), members: z.array(packingBagMemberSchema).optional(), }); export type PackingBag = z.infer; export const packingCreateItemRequestSchema = z.object({ name: z.string().min(1), category: z.string().optional(), checked: z.boolean().optional(), }); export type PackingCreateItemRequest = z.infer; export const packingUpdateItemRequestSchema = z.object({ name: z.string().optional(), checked: z.boolean().optional(), category: z.string().optional(), weight_grams: z.number().nullable().optional(), bag_id: z.number().nullable().optional(), quantity: z.number().optional(), }); export type PackingUpdateItemRequest = z.infer; export const packingImportRequestSchema = z.object({ items: z.array(open), }); export type PackingImportRequest = z.infer; export const packingReorderRequestSchema = z.object({ orderedIds: z.array(z.number()), }); export type PackingReorderRequest = z.infer; export const packingCreateBagRequestSchema = z.object({ name: z.string().min(1), color: z.string().optional(), }); export type PackingCreateBagRequest = z.infer; export const packingUpdateBagRequestSchema = z.object({ name: z.string().optional(), color: z.string().optional(), weight_limit_grams: z.number().nullable().optional(), user_id: z.number().nullable().optional(), }); export type PackingUpdateBagRequest = z.infer; export const packingBagMembersRequestSchema = z.object({ user_ids: z.array(z.number()), }); export type PackingBagMembersRequest = z.infer; export const packingSaveTemplateRequestSchema = z.object({ name: z.string().min(1), }); export type PackingSaveTemplateRequest = z.infer; export const packingTemplateSummarySchema = z.object({ id: z.number(), name: z.string(), item_count: z.number(), }); export type PackingTemplateSummary = z.infer; export const packingTemplatesResponseSchema = z.object({ templates: z.array(packingTemplateSummarySchema), }); export type PackingTemplatesResponse = z.infer; export const packingCategoryAssigneesRequestSchema = z.object({ user_ids: z.array(z.number()), }); export type PackingCategoryAssigneesRequest = z.infer;