@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/LICENSE +109 -0
- package/README.md +43 -0
- package/dist/booking-extension.d.ts +278 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +163 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/routes.d.ts +3674 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +819 -0
- package/dist/schema.d.ts +4156 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +655 -0
- package/dist/service.d.ts +2395 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +1501 -0
- package/dist/tasks/generate-pdf.d.ts +8 -0
- package/dist/tasks/generate-pdf.d.ts.map +1 -0
- package/dist/tasks/generate-pdf.js +102 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +1 -0
- package/dist/validation.d.ts +855 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +433 -0
- package/package.json +60 -0
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
|
+
});
|