File size: 4,827 Bytes
cb43fbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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<typeof packingItemSchema>;

/**
 * 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<typeof packingBagMemberSchema>;

/**
 * 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<typeof packingBagSchema>;

export const packingCreateItemRequestSchema = z.object({
  name: z.string().min(1),
  category: z.string().optional(),
  checked: z.boolean().optional(),
});
export type PackingCreateItemRequest = z.infer<
  typeof packingCreateItemRequestSchema
>;

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<
  typeof packingUpdateItemRequestSchema
>;

export const packingImportRequestSchema = z.object({
  items: z.array(open),
});
export type PackingImportRequest = z.infer<typeof packingImportRequestSchema>;

export const packingReorderRequestSchema = z.object({
  orderedIds: z.array(z.number()),
});
export type PackingReorderRequest = z.infer<typeof packingReorderRequestSchema>;

export const packingCreateBagRequestSchema = z.object({
  name: z.string().min(1),
  color: z.string().optional(),
});
export type PackingCreateBagRequest = z.infer<
  typeof packingCreateBagRequestSchema
>;

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<
  typeof packingUpdateBagRequestSchema
>;

export const packingBagMembersRequestSchema = z.object({
  user_ids: z.array(z.number()),
});
export type PackingBagMembersRequest = z.infer<
  typeof packingBagMembersRequestSchema
>;

export const packingSaveTemplateRequestSchema = z.object({
  name: z.string().min(1),
});
export type PackingSaveTemplateRequest = z.infer<
  typeof packingSaveTemplateRequestSchema
>;

export const packingTemplateSummarySchema = z.object({
  id: z.number(),
  name: z.string(),
  item_count: z.number(),
});
export type PackingTemplateSummary = z.infer<
  typeof packingTemplateSummarySchema
>;

export const packingTemplatesResponseSchema = z.object({
  templates: z.array(packingTemplateSummarySchema),
});
export type PackingTemplatesResponse = z.infer<
  typeof packingTemplatesResponseSchema
>;

export const packingCategoryAssigneesRequestSchema = z.object({
  user_ids: z.array(z.number()),
});
export type PackingCategoryAssigneesRequest = z.infer<
  typeof packingCategoryAssigneesRequestSchema
>;