@voyantjs/products 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/routes.js ADDED
@@ -0,0 +1,819 @@
1
+ import { Hono } from "hono";
2
+ import { productsService } from "./service.js";
3
+ import { insertProductMediaSchema, productMediaListQuerySchema, reorderProductMediaSchema, updateProductMediaSchema, insertDaySchema, insertDayServiceSchema, insertOptionUnitSchema, insertOptionUnitTranslationSchema, insertProductActivationSettingSchema, insertProductCapabilitySchema, insertProductCategorySchema, insertProductDeliveryFormatSchema, insertProductFaqSchema, insertProductFeatureSchema, insertProductLocationSchema, insertProductNoteSchema, insertProductOptionSchema, insertProductOptionTranslationSchema, insertProductSchema, insertProductTagSchema, insertProductTicketSettingSchema, insertProductTranslationSchema, insertProductTypeSchema, insertProductVisibilitySettingSchema, insertVersionSchema, optionUnitListQuerySchema, optionUnitTranslationListQuerySchema, productActivationSettingListQuerySchema, productCapabilityListQuerySchema, productCategoryListQuerySchema, productDeliveryFormatListQuerySchema, productFaqListQuerySchema, productFeatureListQuerySchema, productListQuerySchema, productLocationListQuerySchema, productOptionListQuerySchema, productOptionTranslationListQuerySchema, productTagListQuerySchema, productTicketSettingListQuerySchema, productTranslationListQuerySchema, productTypeListQuerySchema, productVisibilitySettingListQuerySchema, updateDaySchema, updateDayServiceSchema, updateOptionUnitSchema, updateOptionUnitTranslationSchema, updateProductActivationSettingSchema, updateProductCapabilitySchema, updateProductCategorySchema, updateProductDeliveryFormatSchema, updateProductFaqSchema, updateProductFeatureSchema, updateProductLocationSchema, updateProductOptionSchema, updateProductOptionTranslationSchema, updateProductSchema, updateProductTagSchema, updateProductTicketSettingSchema, updateProductTranslationSchema, updateProductTypeSchema, updateProductVisibilitySettingSchema, } from "./validation.js";
4
+ // ==========================================================================
5
+ // Products — method-chained routes for Hono RPC type inference
6
+ // ==========================================================================
7
+ export const productRoutes = new Hono()
8
+ // GET / — List products
9
+ .get("/", async (c) => {
10
+ const query = productListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
11
+ return c.json(await productsService.listProducts(c.get("db"), query));
12
+ })
13
+ // POST / — Create product
14
+ .post("/", async (c) => {
15
+ return c.json({
16
+ data: await productsService.createProduct(c.get("db"), insertProductSchema.parse(await c.req.json())),
17
+ }, 201);
18
+ })
19
+ // ==========================================================================
20
+ // Product operating configuration
21
+ // ==========================================================================
22
+ .get("/activation-settings", async (c) => {
23
+ const query = productActivationSettingListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
24
+ return c.json(await productsService.listActivationSettings(c.get("db"), query));
25
+ })
26
+ .get("/activation-settings/:id", async (c) => {
27
+ const row = await productsService.getActivationSettingById(c.get("db"), c.req.param("id"));
28
+ if (!row) {
29
+ return c.json({ error: "Product activation setting not found" }, 404);
30
+ }
31
+ return c.json({ data: row });
32
+ })
33
+ .post("/:id/activation-settings", async (c) => {
34
+ const row = await productsService.upsertActivationSetting(c.get("db"), c.req.param("id"), insertProductActivationSettingSchema.parse(await c.req.json()));
35
+ if (!row) {
36
+ return c.json({ error: "Product not found" }, 404);
37
+ }
38
+ return c.json({ data: row }, 201);
39
+ })
40
+ .patch("/activation-settings/:id", async (c) => {
41
+ const row = await productsService.updateActivationSetting(c.get("db"), c.req.param("id"), updateProductActivationSettingSchema.parse(await c.req.json()));
42
+ if (!row) {
43
+ return c.json({ error: "Product activation setting not found" }, 404);
44
+ }
45
+ return c.json({ data: row });
46
+ })
47
+ .delete("/activation-settings/:id", async (c) => {
48
+ const row = await productsService.deleteActivationSetting(c.get("db"), c.req.param("id"));
49
+ if (!row) {
50
+ return c.json({ error: "Product activation setting not found" }, 404);
51
+ }
52
+ return c.json({ success: true }, 200);
53
+ })
54
+ .get("/ticket-settings", async (c) => {
55
+ const query = productTicketSettingListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
56
+ return c.json(await productsService.listTicketSettings(c.get("db"), query));
57
+ })
58
+ .get("/ticket-settings/:id", async (c) => {
59
+ const row = await productsService.getTicketSettingById(c.get("db"), c.req.param("id"));
60
+ if (!row) {
61
+ return c.json({ error: "Product ticket setting not found" }, 404);
62
+ }
63
+ return c.json({ data: row });
64
+ })
65
+ .post("/:id/ticket-settings", async (c) => {
66
+ const row = await productsService.upsertTicketSetting(c.get("db"), c.req.param("id"), insertProductTicketSettingSchema.parse(await c.req.json()));
67
+ if (!row) {
68
+ return c.json({ error: "Product not found" }, 404);
69
+ }
70
+ return c.json({ data: row }, 201);
71
+ })
72
+ .patch("/ticket-settings/:id", async (c) => {
73
+ const row = await productsService.updateTicketSetting(c.get("db"), c.req.param("id"), updateProductTicketSettingSchema.parse(await c.req.json()));
74
+ if (!row) {
75
+ return c.json({ error: "Product ticket setting not found" }, 404);
76
+ }
77
+ return c.json({ data: row });
78
+ })
79
+ .delete("/ticket-settings/:id", async (c) => {
80
+ const row = await productsService.deleteTicketSetting(c.get("db"), c.req.param("id"));
81
+ if (!row) {
82
+ return c.json({ error: "Product ticket setting not found" }, 404);
83
+ }
84
+ return c.json({ success: true }, 200);
85
+ })
86
+ .get("/visibility-settings", async (c) => {
87
+ const query = productVisibilitySettingListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
88
+ return c.json(await productsService.listVisibilitySettings(c.get("db"), query));
89
+ })
90
+ .get("/visibility-settings/:id", async (c) => {
91
+ const row = await productsService.getVisibilitySettingById(c.get("db"), c.req.param("id"));
92
+ if (!row) {
93
+ return c.json({ error: "Product visibility setting not found" }, 404);
94
+ }
95
+ return c.json({ data: row });
96
+ })
97
+ .post("/:id/visibility-settings", async (c) => {
98
+ const row = await productsService.upsertVisibilitySetting(c.get("db"), c.req.param("id"), insertProductVisibilitySettingSchema.parse(await c.req.json()));
99
+ if (!row) {
100
+ return c.json({ error: "Product not found" }, 404);
101
+ }
102
+ return c.json({ data: row }, 201);
103
+ })
104
+ .patch("/visibility-settings/:id", async (c) => {
105
+ const row = await productsService.updateVisibilitySetting(c.get("db"), c.req.param("id"), updateProductVisibilitySettingSchema.parse(await c.req.json()));
106
+ if (!row) {
107
+ return c.json({ error: "Product visibility setting not found" }, 404);
108
+ }
109
+ return c.json({ data: row });
110
+ })
111
+ .delete("/visibility-settings/:id", async (c) => {
112
+ const row = await productsService.deleteVisibilitySetting(c.get("db"), c.req.param("id"));
113
+ if (!row) {
114
+ return c.json({ error: "Product visibility setting not found" }, 404);
115
+ }
116
+ return c.json({ success: true }, 200);
117
+ })
118
+ .get("/capabilities", async (c) => {
119
+ const query = productCapabilityListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
120
+ return c.json(await productsService.listCapabilities(c.get("db"), query));
121
+ })
122
+ .get("/capabilities/:id", async (c) => {
123
+ const row = await productsService.getCapabilityById(c.get("db"), c.req.param("id"));
124
+ if (!row) {
125
+ return c.json({ error: "Product capability not found" }, 404);
126
+ }
127
+ return c.json({ data: row });
128
+ })
129
+ .post("/:id/capabilities", async (c) => {
130
+ const row = await productsService.createCapability(c.get("db"), c.req.param("id"), insertProductCapabilitySchema.parse(await c.req.json()));
131
+ if (!row) {
132
+ return c.json({ error: "Product not found" }, 404);
133
+ }
134
+ return c.json({ data: row }, 201);
135
+ })
136
+ .patch("/capabilities/:id", async (c) => {
137
+ const row = await productsService.updateCapability(c.get("db"), c.req.param("id"), updateProductCapabilitySchema.parse(await c.req.json()));
138
+ if (!row) {
139
+ return c.json({ error: "Product capability not found" }, 404);
140
+ }
141
+ return c.json({ data: row });
142
+ })
143
+ .delete("/capabilities/:id", async (c) => {
144
+ const row = await productsService.deleteCapability(c.get("db"), c.req.param("id"));
145
+ if (!row) {
146
+ return c.json({ error: "Product capability not found" }, 404);
147
+ }
148
+ return c.json({ success: true }, 200);
149
+ })
150
+ .get("/delivery-formats", async (c) => {
151
+ const query = productDeliveryFormatListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
152
+ return c.json(await productsService.listDeliveryFormats(c.get("db"), query));
153
+ })
154
+ .get("/delivery-formats/:id", async (c) => {
155
+ const row = await productsService.getDeliveryFormatById(c.get("db"), c.req.param("id"));
156
+ if (!row) {
157
+ return c.json({ error: "Product delivery format not found" }, 404);
158
+ }
159
+ return c.json({ data: row });
160
+ })
161
+ .post("/:id/delivery-formats", async (c) => {
162
+ const row = await productsService.createDeliveryFormat(c.get("db"), c.req.param("id"), insertProductDeliveryFormatSchema.parse(await c.req.json()));
163
+ if (!row) {
164
+ return c.json({ error: "Product not found" }, 404);
165
+ }
166
+ return c.json({ data: row }, 201);
167
+ })
168
+ .patch("/delivery-formats/:id", async (c) => {
169
+ const row = await productsService.updateDeliveryFormat(c.get("db"), c.req.param("id"), updateProductDeliveryFormatSchema.parse(await c.req.json()));
170
+ if (!row) {
171
+ return c.json({ error: "Product delivery format not found" }, 404);
172
+ }
173
+ return c.json({ data: row });
174
+ })
175
+ .delete("/delivery-formats/:id", async (c) => {
176
+ const row = await productsService.deleteDeliveryFormat(c.get("db"), c.req.param("id"));
177
+ if (!row) {
178
+ return c.json({ error: "Product delivery format not found" }, 404);
179
+ }
180
+ return c.json({ success: true }, 200);
181
+ })
182
+ .get("/features", async (c) => {
183
+ const query = productFeatureListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
184
+ return c.json(await productsService.listFeatures(c.get("db"), query));
185
+ })
186
+ .get("/features/:id", async (c) => {
187
+ const row = await productsService.getFeatureById(c.get("db"), c.req.param("id"));
188
+ if (!row) {
189
+ return c.json({ error: "Product feature not found" }, 404);
190
+ }
191
+ return c.json({ data: row });
192
+ })
193
+ .post("/:id/features", async (c) => {
194
+ const row = await productsService.createFeature(c.get("db"), c.req.param("id"), insertProductFeatureSchema.parse(await c.req.json()));
195
+ if (!row) {
196
+ return c.json({ error: "Product not found" }, 404);
197
+ }
198
+ return c.json({ data: row }, 201);
199
+ })
200
+ .patch("/features/:id", async (c) => {
201
+ const row = await productsService.updateFeature(c.get("db"), c.req.param("id"), updateProductFeatureSchema.parse(await c.req.json()));
202
+ if (!row) {
203
+ return c.json({ error: "Product feature not found" }, 404);
204
+ }
205
+ return c.json({ data: row });
206
+ })
207
+ .delete("/features/:id", async (c) => {
208
+ const row = await productsService.deleteFeature(c.get("db"), c.req.param("id"));
209
+ if (!row) {
210
+ return c.json({ error: "Product feature not found" }, 404);
211
+ }
212
+ return c.json({ success: true }, 200);
213
+ })
214
+ .get("/faqs", async (c) => {
215
+ const query = productFaqListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
216
+ return c.json(await productsService.listFaqs(c.get("db"), query));
217
+ })
218
+ .get("/faqs/:id", async (c) => {
219
+ const row = await productsService.getFaqById(c.get("db"), c.req.param("id"));
220
+ if (!row) {
221
+ return c.json({ error: "Product FAQ not found" }, 404);
222
+ }
223
+ return c.json({ data: row });
224
+ })
225
+ .post("/:id/faqs", async (c) => {
226
+ const row = await productsService.createFaq(c.get("db"), c.req.param("id"), insertProductFaqSchema.parse(await c.req.json()));
227
+ if (!row) {
228
+ return c.json({ error: "Product not found" }, 404);
229
+ }
230
+ return c.json({ data: row }, 201);
231
+ })
232
+ .patch("/faqs/:id", async (c) => {
233
+ const row = await productsService.updateFaq(c.get("db"), c.req.param("id"), updateProductFaqSchema.parse(await c.req.json()));
234
+ if (!row) {
235
+ return c.json({ error: "Product FAQ not found" }, 404);
236
+ }
237
+ return c.json({ data: row });
238
+ })
239
+ .delete("/faqs/:id", async (c) => {
240
+ const row = await productsService.deleteFaq(c.get("db"), c.req.param("id"));
241
+ if (!row) {
242
+ return c.json({ error: "Product FAQ not found" }, 404);
243
+ }
244
+ return c.json({ success: true }, 200);
245
+ })
246
+ .get("/locations", async (c) => {
247
+ const query = productLocationListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
248
+ return c.json(await productsService.listLocations(c.get("db"), query));
249
+ })
250
+ .get("/locations/:id", async (c) => {
251
+ const row = await productsService.getLocationById(c.get("db"), c.req.param("id"));
252
+ if (!row) {
253
+ return c.json({ error: "Product location not found" }, 404);
254
+ }
255
+ return c.json({ data: row });
256
+ })
257
+ .post("/:id/locations", async (c) => {
258
+ const row = await productsService.createLocation(c.get("db"), c.req.param("id"), insertProductLocationSchema.parse(await c.req.json()));
259
+ if (!row) {
260
+ return c.json({ error: "Product not found" }, 404);
261
+ }
262
+ return c.json({ data: row }, 201);
263
+ })
264
+ .patch("/locations/:id", async (c) => {
265
+ const row = await productsService.updateLocation(c.get("db"), c.req.param("id"), updateProductLocationSchema.parse(await c.req.json()));
266
+ if (!row) {
267
+ return c.json({ error: "Product location not found" }, 404);
268
+ }
269
+ return c.json({ data: row });
270
+ })
271
+ .delete("/locations/:id", async (c) => {
272
+ const row = await productsService.deleteLocation(c.get("db"), c.req.param("id"));
273
+ if (!row) {
274
+ return c.json({ error: "Product location not found" }, 404);
275
+ }
276
+ return c.json({ success: true }, 200);
277
+ })
278
+ // ==========================================================================
279
+ // Options
280
+ // ==========================================================================
281
+ // GET /options — List options
282
+ .get("/options", async (c) => {
283
+ const query = productOptionListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
284
+ return c.json(await productsService.listOptions(c.get("db"), query));
285
+ })
286
+ // GET /options/:optionId — Get single option
287
+ .get("/options/:optionId", async (c) => {
288
+ const row = await productsService.getOptionById(c.get("db"), c.req.param("optionId"));
289
+ if (!row) {
290
+ return c.json({ error: "Product option not found" }, 404);
291
+ }
292
+ return c.json({ data: row });
293
+ })
294
+ // POST /:id/options — Create option for product
295
+ .post("/:id/options", async (c) => {
296
+ const row = await productsService.createOption(c.get("db"), c.req.param("id"), insertProductOptionSchema.parse(await c.req.json()));
297
+ if (!row) {
298
+ return c.json({ error: "Product not found" }, 404);
299
+ }
300
+ return c.json({ data: row }, 201);
301
+ })
302
+ // PATCH /options/:optionId — Update option
303
+ .patch("/options/:optionId", async (c) => {
304
+ const row = await productsService.updateOption(c.get("db"), c.req.param("optionId"), updateProductOptionSchema.parse(await c.req.json()));
305
+ if (!row) {
306
+ return c.json({ error: "Product option not found" }, 404);
307
+ }
308
+ return c.json({ data: row });
309
+ })
310
+ // DELETE /options/:optionId — Delete option
311
+ .delete("/options/:optionId", async (c) => {
312
+ const row = await productsService.deleteOption(c.get("db"), c.req.param("optionId"));
313
+ if (!row) {
314
+ return c.json({ error: "Product option not found" }, 404);
315
+ }
316
+ return c.json({ success: true }, 200);
317
+ })
318
+ // ==========================================================================
319
+ // Option Units
320
+ // ==========================================================================
321
+ // GET /units — List units
322
+ .get("/units", async (c) => {
323
+ const query = optionUnitListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
324
+ return c.json(await productsService.listUnits(c.get("db"), query));
325
+ })
326
+ // GET /units/:unitId — Get single unit
327
+ .get("/units/:unitId", async (c) => {
328
+ const row = await productsService.getUnitById(c.get("db"), c.req.param("unitId"));
329
+ if (!row) {
330
+ return c.json({ error: "Option unit not found" }, 404);
331
+ }
332
+ return c.json({ data: row });
333
+ })
334
+ // POST /options/:optionId/units — Create unit for option
335
+ .post("/options/:optionId/units", async (c) => {
336
+ const row = await productsService.createUnit(c.get("db"), c.req.param("optionId"), insertOptionUnitSchema.parse(await c.req.json()));
337
+ if (!row) {
338
+ return c.json({ error: "Product option not found" }, 404);
339
+ }
340
+ return c.json({ data: row }, 201);
341
+ })
342
+ // PATCH /units/:unitId — Update unit
343
+ .patch("/units/:unitId", async (c) => {
344
+ const row = await productsService.updateUnit(c.get("db"), c.req.param("unitId"), updateOptionUnitSchema.parse(await c.req.json()));
345
+ if (!row) {
346
+ return c.json({ error: "Option unit not found" }, 404);
347
+ }
348
+ return c.json({ data: row });
349
+ })
350
+ // DELETE /units/:unitId — Delete unit
351
+ .delete("/units/:unitId", async (c) => {
352
+ const row = await productsService.deleteUnit(c.get("db"), c.req.param("unitId"));
353
+ if (!row) {
354
+ return c.json({ error: "Option unit not found" }, 404);
355
+ }
356
+ return c.json({ success: true }, 200);
357
+ })
358
+ // ==========================================================================
359
+ // Translations
360
+ // ==========================================================================
361
+ .get("/translations", async (c) => {
362
+ const query = productTranslationListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
363
+ return c.json(await productsService.listProductTranslations(c.get("db"), query));
364
+ })
365
+ .get("/translations/:translationId", async (c) => {
366
+ const row = await productsService.getProductTranslationById(c.get("db"), c.req.param("translationId"));
367
+ if (!row) {
368
+ return c.json({ error: "Product translation not found" }, 404);
369
+ }
370
+ return c.json({ data: row });
371
+ })
372
+ .post("/:id/translations", async (c) => {
373
+ const row = await productsService.createProductTranslation(c.get("db"), c.req.param("id"), insertProductTranslationSchema.parse(await c.req.json()));
374
+ if (!row) {
375
+ return c.json({ error: "Product not found" }, 404);
376
+ }
377
+ return c.json({ data: row }, 201);
378
+ })
379
+ .patch("/translations/:translationId", async (c) => {
380
+ const row = await productsService.updateProductTranslation(c.get("db"), c.req.param("translationId"), updateProductTranslationSchema.parse(await c.req.json()));
381
+ if (!row) {
382
+ return c.json({ error: "Product translation not found" }, 404);
383
+ }
384
+ return c.json({ data: row });
385
+ })
386
+ .delete("/translations/:translationId", async (c) => {
387
+ const row = await productsService.deleteProductTranslation(c.get("db"), c.req.param("translationId"));
388
+ if (!row) {
389
+ return c.json({ error: "Product translation not found" }, 404);
390
+ }
391
+ return c.json({ success: true }, 200);
392
+ })
393
+ .get("/option-translations", async (c) => {
394
+ const query = productOptionTranslationListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
395
+ return c.json(await productsService.listOptionTranslations(c.get("db"), query));
396
+ })
397
+ .get("/option-translations/:translationId", async (c) => {
398
+ const row = await productsService.getOptionTranslationById(c.get("db"), c.req.param("translationId"));
399
+ if (!row) {
400
+ return c.json({ error: "Option translation not found" }, 404);
401
+ }
402
+ return c.json({ data: row });
403
+ })
404
+ .post("/options/:optionId/translations", async (c) => {
405
+ const row = await productsService.createOptionTranslation(c.get("db"), c.req.param("optionId"), insertProductOptionTranslationSchema.parse(await c.req.json()));
406
+ if (!row) {
407
+ return c.json({ error: "Product option not found" }, 404);
408
+ }
409
+ return c.json({ data: row }, 201);
410
+ })
411
+ .patch("/option-translations/:translationId", async (c) => {
412
+ const row = await productsService.updateOptionTranslation(c.get("db"), c.req.param("translationId"), updateProductOptionTranslationSchema.parse(await c.req.json()));
413
+ if (!row) {
414
+ return c.json({ error: "Option translation not found" }, 404);
415
+ }
416
+ return c.json({ data: row });
417
+ })
418
+ .delete("/option-translations/:translationId", async (c) => {
419
+ const row = await productsService.deleteOptionTranslation(c.get("db"), c.req.param("translationId"));
420
+ if (!row) {
421
+ return c.json({ error: "Option translation not found" }, 404);
422
+ }
423
+ return c.json({ success: true }, 200);
424
+ })
425
+ .get("/unit-translations", async (c) => {
426
+ const query = optionUnitTranslationListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
427
+ return c.json(await productsService.listUnitTranslations(c.get("db"), query));
428
+ })
429
+ .get("/unit-translations/:translationId", async (c) => {
430
+ const row = await productsService.getUnitTranslationById(c.get("db"), c.req.param("translationId"));
431
+ if (!row) {
432
+ return c.json({ error: "Unit translation not found" }, 404);
433
+ }
434
+ return c.json({ data: row });
435
+ })
436
+ .post("/units/:unitId/translations", async (c) => {
437
+ const row = await productsService.createUnitTranslation(c.get("db"), c.req.param("unitId"), insertOptionUnitTranslationSchema.parse(await c.req.json()));
438
+ if (!row) {
439
+ return c.json({ error: "Option unit not found" }, 404);
440
+ }
441
+ return c.json({ data: row }, 201);
442
+ })
443
+ .patch("/unit-translations/:translationId", async (c) => {
444
+ const row = await productsService.updateUnitTranslation(c.get("db"), c.req.param("translationId"), updateOptionUnitTranslationSchema.parse(await c.req.json()));
445
+ if (!row) {
446
+ return c.json({ error: "Unit translation not found" }, 404);
447
+ }
448
+ return c.json({ data: row });
449
+ })
450
+ .delete("/unit-translations/:translationId", async (c) => {
451
+ const row = await productsService.deleteUnitTranslation(c.get("db"), c.req.param("translationId"));
452
+ if (!row) {
453
+ return c.json({ error: "Unit translation not found" }, 404);
454
+ }
455
+ return c.json({ success: true }, 200);
456
+ })
457
+ // ==========================================================================
458
+ // Product Types
459
+ // ==========================================================================
460
+ .get("/product-types", async (c) => {
461
+ const query = productTypeListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
462
+ return c.json(await productsService.listProductTypes(c.get("db"), query));
463
+ })
464
+ .get("/product-types/:typeId", async (c) => {
465
+ const row = await productsService.getProductTypeById(c.get("db"), c.req.param("typeId"));
466
+ if (!row) {
467
+ return c.json({ error: "Product type not found" }, 404);
468
+ }
469
+ return c.json({ data: row });
470
+ })
471
+ .post("/product-types", async (c) => {
472
+ return c.json({
473
+ data: await productsService.createProductType(c.get("db"), insertProductTypeSchema.parse(await c.req.json())),
474
+ }, 201);
475
+ })
476
+ .patch("/product-types/:typeId", async (c) => {
477
+ const row = await productsService.updateProductType(c.get("db"), c.req.param("typeId"), updateProductTypeSchema.parse(await c.req.json()));
478
+ if (!row) {
479
+ return c.json({ error: "Product type not found" }, 404);
480
+ }
481
+ return c.json({ data: row });
482
+ })
483
+ .delete("/product-types/:typeId", async (c) => {
484
+ const row = await productsService.deleteProductType(c.get("db"), c.req.param("typeId"));
485
+ if (!row) {
486
+ return c.json({ error: "Product type not found" }, 404);
487
+ }
488
+ return c.json({ success: true }, 200);
489
+ })
490
+ // ==========================================================================
491
+ // Product Categories
492
+ // ==========================================================================
493
+ .get("/product-categories", async (c) => {
494
+ const query = productCategoryListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
495
+ return c.json(await productsService.listProductCategories(c.get("db"), query));
496
+ })
497
+ .get("/product-categories/:categoryId", async (c) => {
498
+ const row = await productsService.getProductCategoryById(c.get("db"), c.req.param("categoryId"));
499
+ if (!row) {
500
+ return c.json({ error: "Product category not found" }, 404);
501
+ }
502
+ return c.json({ data: row });
503
+ })
504
+ .post("/product-categories", async (c) => {
505
+ return c.json({
506
+ data: await productsService.createProductCategory(c.get("db"), insertProductCategorySchema.parse(await c.req.json())),
507
+ }, 201);
508
+ })
509
+ .patch("/product-categories/:categoryId", async (c) => {
510
+ const row = await productsService.updateProductCategory(c.get("db"), c.req.param("categoryId"), updateProductCategorySchema.parse(await c.req.json()));
511
+ if (!row) {
512
+ return c.json({ error: "Product category not found" }, 404);
513
+ }
514
+ return c.json({ data: row });
515
+ })
516
+ .delete("/product-categories/:categoryId", async (c) => {
517
+ const row = await productsService.deleteProductCategory(c.get("db"), c.req.param("categoryId"));
518
+ if (!row) {
519
+ return c.json({ error: "Product category not found" }, 404);
520
+ }
521
+ return c.json({ success: true }, 200);
522
+ })
523
+ // ==========================================================================
524
+ // Product Tags
525
+ // ==========================================================================
526
+ .get("/product-tags", async (c) => {
527
+ const query = productTagListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
528
+ return c.json(await productsService.listProductTags(c.get("db"), query));
529
+ })
530
+ .get("/product-tags/:tagId", async (c) => {
531
+ const row = await productsService.getProductTagById(c.get("db"), c.req.param("tagId"));
532
+ if (!row) {
533
+ return c.json({ error: "Product tag not found" }, 404);
534
+ }
535
+ return c.json({ data: row });
536
+ })
537
+ .post("/product-tags", async (c) => {
538
+ return c.json({
539
+ data: await productsService.createProductTag(c.get("db"), insertProductTagSchema.parse(await c.req.json())),
540
+ }, 201);
541
+ })
542
+ .patch("/product-tags/:tagId", async (c) => {
543
+ const row = await productsService.updateProductTag(c.get("db"), c.req.param("tagId"), updateProductTagSchema.parse(await c.req.json()));
544
+ if (!row) {
545
+ return c.json({ error: "Product tag not found" }, 404);
546
+ }
547
+ return c.json({ data: row });
548
+ })
549
+ .delete("/product-tags/:tagId", async (c) => {
550
+ const row = await productsService.deleteProductTag(c.get("db"), c.req.param("tagId"));
551
+ if (!row) {
552
+ return c.json({ error: "Product tag not found" }, 404);
553
+ }
554
+ return c.json({ success: true }, 200);
555
+ })
556
+ // ==========================================================================
557
+ // Media
558
+ // ==========================================================================
559
+ // GET /media/:mediaId — Get single media item
560
+ .get("/media/:mediaId", async (c) => {
561
+ const row = await productsService.getMediaById(c.get("db"), c.req.param("mediaId"));
562
+ if (!row) {
563
+ return c.json({ error: "Media not found" }, 404);
564
+ }
565
+ return c.json({ data: row });
566
+ })
567
+ // PATCH /media/:mediaId — Update media metadata
568
+ .patch("/media/:mediaId", async (c) => {
569
+ const row = await productsService.updateMedia(c.get("db"), c.req.param("mediaId"), updateProductMediaSchema.parse(await c.req.json()));
570
+ if (!row) {
571
+ return c.json({ error: "Media not found" }, 404);
572
+ }
573
+ return c.json({ data: row });
574
+ })
575
+ // PATCH /media/:mediaId/set-cover — Set as cover image
576
+ .patch("/media/:mediaId/set-cover", async (c) => {
577
+ const media = await productsService.getMediaById(c.get("db"), c.req.param("mediaId"));
578
+ if (!media) {
579
+ return c.json({ error: "Media not found" }, 404);
580
+ }
581
+ const row = await productsService.setCoverMedia(c.get("db"), media.productId, media.id, media.dayId);
582
+ if (!row) {
583
+ return c.json({ error: "Failed to set cover" }, 500);
584
+ }
585
+ return c.json({ data: row });
586
+ })
587
+ // DELETE /media/:mediaId — Delete media
588
+ .delete("/media/:mediaId", async (c) => {
589
+ const row = await productsService.deleteMedia(c.get("db"), c.req.param("mediaId"));
590
+ if (!row) {
591
+ return c.json({ error: "Media not found" }, 404);
592
+ }
593
+ return c.json({ data: row });
594
+ })
595
+ // GET /:id — Get single product
596
+ .get("/:id", async (c) => {
597
+ const row = await productsService.getProductById(c.get("db"), c.req.param("id"));
598
+ if (!row) {
599
+ return c.json({ error: "Product not found" }, 404);
600
+ }
601
+ return c.json({ data: row });
602
+ })
603
+ // PATCH /:id — Update product
604
+ .patch("/:id", async (c) => {
605
+ const row = await productsService.updateProduct(c.get("db"), c.req.param("id"), updateProductSchema.parse(await c.req.json()));
606
+ if (!row) {
607
+ return c.json({ error: "Product not found" }, 404);
608
+ }
609
+ return c.json({ data: row });
610
+ })
611
+ // DELETE /:id — Delete product
612
+ .delete("/:id", async (c) => {
613
+ const row = await productsService.deleteProduct(c.get("db"), c.req.param("id"));
614
+ if (!row) {
615
+ return c.json({ error: "Product not found" }, 404);
616
+ }
617
+ return c.json({ success: true }, 200);
618
+ })
619
+ // ==========================================================================
620
+ // Days
621
+ // ==========================================================================
622
+ // GET /:id/days — List days for product
623
+ .get("/:id/days", async (c) => {
624
+ return c.json({ data: await productsService.listDays(c.get("db"), c.req.param("id")) });
625
+ })
626
+ // POST /:id/days — Add day to product
627
+ .post("/:id/days", async (c) => {
628
+ const row = await productsService.createDay(c.get("db"), c.req.param("id"), insertDaySchema.parse(await c.req.json()));
629
+ if (!row) {
630
+ return c.json({ error: "Product not found" }, 404);
631
+ }
632
+ return c.json({ data: row }, 201);
633
+ })
634
+ // PATCH /:id/days/:dayId — Update day
635
+ .patch("/:id/days/:dayId", async (c) => {
636
+ const row = await productsService.updateDay(c.get("db"), c.req.param("dayId"), updateDaySchema.parse(await c.req.json()));
637
+ if (!row) {
638
+ return c.json({ error: "Day not found" }, 404);
639
+ }
640
+ return c.json({ data: row });
641
+ })
642
+ // DELETE /:id/days/:dayId — Delete day
643
+ .delete("/:id/days/:dayId", async (c) => {
644
+ const row = await productsService.deleteDay(c.get("db"), c.req.param("dayId"));
645
+ if (!row) {
646
+ return c.json({ error: "Day not found" }, 404);
647
+ }
648
+ return c.json({ success: true }, 200);
649
+ })
650
+ // ==========================================================================
651
+ // Day Services
652
+ // ==========================================================================
653
+ // GET /:id/days/:dayId/services — List services for a day
654
+ .get("/:id/days/:dayId/services", async (c) => {
655
+ return c.json({
656
+ data: await productsService.listDayServices(c.get("db"), c.req.param("dayId")),
657
+ });
658
+ })
659
+ // POST /:id/days/:dayId/services — Add service to day
660
+ .post("/:id/days/:dayId/services", async (c) => {
661
+ const row = await productsService.createDayService(c.get("db"), c.req.param("id"), c.req.param("dayId"), insertDayServiceSchema.parse(await c.req.json()));
662
+ if (!row) {
663
+ return c.json({ error: "Day not found" }, 404);
664
+ }
665
+ return c.json({ data: row }, 201);
666
+ })
667
+ // PATCH /:id/days/:dayId/services/:serviceId — Update service
668
+ .patch("/:id/days/:dayId/services/:serviceId", async (c) => {
669
+ const row = await productsService.updateDayService(c.get("db"), c.req.param("id"), c.req.param("serviceId"), updateDayServiceSchema.parse(await c.req.json()));
670
+ if (!row) {
671
+ return c.json({ error: "Service not found" }, 404);
672
+ }
673
+ return c.json({ data: row });
674
+ })
675
+ // DELETE /:id/days/:dayId/services/:serviceId — Delete service
676
+ .delete("/:id/days/:dayId/services/:serviceId", async (c) => {
677
+ const row = await productsService.deleteDayService(c.get("db"), c.req.param("id"), c.req.param("serviceId"));
678
+ if (!row) {
679
+ return c.json({ error: "Service not found" }, 404);
680
+ }
681
+ return c.json({ success: true }, 200);
682
+ })
683
+ // ==========================================================================
684
+ // Versions
685
+ // ==========================================================================
686
+ // GET /:id/versions — List versions for product
687
+ .get("/:id/versions", async (c) => {
688
+ return c.json({ data: await productsService.listVersions(c.get("db"), c.req.param("id")) });
689
+ })
690
+ // POST /:id/versions — Create version snapshot
691
+ .post("/:id/versions", async (c) => {
692
+ const userId = c.get("userId");
693
+ if (!userId) {
694
+ return c.json({ error: "User ID required to create versions" }, 400);
695
+ }
696
+ const row = await productsService.createVersion(c.get("db"), c.req.param("id"), userId, insertVersionSchema.parse(await c.req.json().catch(() => ({}))));
697
+ if (!row) {
698
+ return c.json({ error: "Product not found" }, 404);
699
+ }
700
+ return c.json({ data: row }, 201);
701
+ })
702
+ // ==========================================================================
703
+ // Notes
704
+ // ==========================================================================
705
+ // GET /:id/notes — List notes for product
706
+ .get("/:id/notes", async (c) => {
707
+ return c.json({ data: await productsService.listNotes(c.get("db"), c.req.param("id")) });
708
+ })
709
+ // POST /:id/notes — Add note to product
710
+ .post("/:id/notes", async (c) => {
711
+ const userId = c.get("userId");
712
+ if (!userId) {
713
+ return c.json({ error: "User ID required to create notes" }, 400);
714
+ }
715
+ const row = await productsService.createNote(c.get("db"), c.req.param("id"), userId, insertProductNoteSchema.parse(await c.req.json()));
716
+ if (!row) {
717
+ return c.json({ error: "Product not found" }, 404);
718
+ }
719
+ return c.json({ data: row }, 201);
720
+ })
721
+ // ==========================================================================
722
+ // Product <-> Category associations
723
+ // ==========================================================================
724
+ .get("/:id/categories", async (c) => {
725
+ return c.json({
726
+ data: await productsService.listProductCategories_(c.get("db"), c.req.param("id")),
727
+ });
728
+ })
729
+ .post("/:id/categories", async (c) => {
730
+ const { categoryId, sortOrder } = (await c.req.json());
731
+ const row = await productsService.addProductToCategory(c.get("db"), c.req.param("id"), categoryId, sortOrder);
732
+ if (!row) {
733
+ return c.json({ error: "Already assigned or not found" }, 409);
734
+ }
735
+ return c.json({ success: true }, 201);
736
+ })
737
+ .delete("/:id/categories/:categoryId", async (c) => {
738
+ const row = await productsService.removeProductFromCategory(c.get("db"), c.req.param("id"), c.req.param("categoryId"));
739
+ if (!row) {
740
+ return c.json({ error: "Association not found" }, 404);
741
+ }
742
+ return c.json({ success: true }, 200);
743
+ })
744
+ // ==========================================================================
745
+ // Product <-> Tag associations
746
+ // ==========================================================================
747
+ .get("/:id/tags", async (c) => {
748
+ return c.json({
749
+ data: await productsService.listProductTags_(c.get("db"), c.req.param("id")),
750
+ });
751
+ })
752
+ .post("/:id/tags", async (c) => {
753
+ const { tagId } = (await c.req.json());
754
+ const row = await productsService.addProductTag(c.get("db"), c.req.param("id"), tagId);
755
+ if (!row) {
756
+ return c.json({ error: "Already assigned or not found" }, 409);
757
+ }
758
+ return c.json({ success: true }, 201);
759
+ })
760
+ .delete("/:id/tags/:tagId", async (c) => {
761
+ const row = await productsService.removeProductTag(c.get("db"), c.req.param("id"), c.req.param("tagId"));
762
+ if (!row) {
763
+ return c.json({ error: "Association not found" }, 404);
764
+ }
765
+ return c.json({ success: true }, 200);
766
+ })
767
+ // ==========================================================================
768
+ // Product Media (nested under product)
769
+ // ==========================================================================
770
+ // GET /:id/media — List product-level media
771
+ .get("/:id/media", async (c) => {
772
+ const query = productMediaListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
773
+ return c.json(await productsService.listProductLevelMedia(c.get("db"), c.req.param("id"), query));
774
+ })
775
+ // POST /:id/media — Create media for product
776
+ .post("/:id/media", async (c) => {
777
+ const row = await productsService.createMedia(c.get("db"), c.req.param("id"), insertProductMediaSchema.parse(await c.req.json()));
778
+ if (!row) {
779
+ return c.json({ error: "Product not found or invalid dayId" }, 404);
780
+ }
781
+ return c.json({ data: row }, 201);
782
+ })
783
+ // POST /:id/media/reorder — Batch reorder media
784
+ .post("/:id/media/reorder", async (c) => {
785
+ const data = reorderProductMediaSchema.parse(await c.req.json());
786
+ const results = await productsService.reorderMedia(c.get("db"), data);
787
+ return c.json({ data: results });
788
+ })
789
+ // GET /:id/days/:dayId/media — List day media
790
+ .get("/:id/days/:dayId/media", async (c) => {
791
+ const query = productMediaListQuerySchema.parse(Object.fromEntries(new URL(c.req.url).searchParams));
792
+ return c.json(await productsService.listMedia(c.get("db"), c.req.param("id"), {
793
+ ...query,
794
+ dayId: c.req.param("dayId"),
795
+ }));
796
+ })
797
+ // POST /:id/days/:dayId/media — Create day media
798
+ .post("/:id/days/:dayId/media", async (c) => {
799
+ const body = insertProductMediaSchema.parse(await c.req.json());
800
+ const row = await productsService.createMedia(c.get("db"), c.req.param("id"), {
801
+ ...body,
802
+ dayId: c.req.param("dayId"),
803
+ });
804
+ if (!row) {
805
+ return c.json({ error: "Product or day not found" }, 404);
806
+ }
807
+ return c.json({ data: row }, 201);
808
+ })
809
+ // ==========================================================================
810
+ // Recalculate
811
+ // ==========================================================================
812
+ // POST /:id/recalculate — Recalculate product cost and margin
813
+ .post("/:id/recalculate", async (c) => {
814
+ const result = await productsService.recalculate(c.get("db"), c.req.param("id"));
815
+ if (!result) {
816
+ return c.json({ error: "Product not found" }, 404);
817
+ }
818
+ return c.json({ data: result });
819
+ });