@voyant-travel/inventory 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 +201 -0
- package/dist/action-ledger-drift.d.ts +29 -0
- package/dist/action-ledger-drift.d.ts.map +1 -0
- package/dist/action-ledger-drift.js +338 -0
- package/dist/action-ledger.d.ts +104 -0
- package/dist/action-ledger.d.ts.map +1 -0
- package/dist/action-ledger.js +100 -0
- package/dist/authoring/builder.d.ts +37 -0
- package/dist/authoring/builder.d.ts.map +1 -0
- package/dist/authoring/builder.js +248 -0
- package/dist/authoring/clone-content.d.ts +38 -0
- package/dist/authoring/clone-content.d.ts.map +1 -0
- package/dist/authoring/clone-content.js +367 -0
- package/dist/authoring/clone-pricing.d.ts +9 -0
- package/dist/authoring/clone-pricing.d.ts.map +1 -0
- package/dist/authoring/clone-pricing.js +242 -0
- package/dist/authoring/clone.d.ts +45 -0
- package/dist/authoring/clone.d.ts.map +1 -0
- package/dist/authoring/clone.js +142 -0
- package/dist/authoring/errors.d.ts +21 -0
- package/dist/authoring/errors.d.ts.map +1 -0
- package/dist/authoring/errors.js +13 -0
- package/dist/authoring/extension.d.ts +248 -0
- package/dist/authoring/extension.d.ts.map +1 -0
- package/dist/authoring/extension.js +116 -0
- package/dist/authoring/index.d.ts +12 -0
- package/dist/authoring/index.d.ts.map +1 -0
- package/dist/authoring/index.js +11 -0
- package/dist/authoring/schema.d.ts +85 -0
- package/dist/authoring/schema.d.ts.map +1 -0
- package/dist/authoring/schema.js +16 -0
- package/dist/authoring/service.d.ts +28 -0
- package/dist/authoring/service.d.ts.map +1 -0
- package/dist/authoring/service.js +66 -0
- package/dist/authoring/spec.d.ts +524 -0
- package/dist/authoring/spec.d.ts.map +1 -0
- package/dist/authoring/spec.js +167 -0
- package/dist/authoring/validate.d.ts +17 -0
- package/dist/authoring/validate.d.ts.map +1 -0
- package/dist/authoring/validate.js +83 -0
- package/dist/authoring.d.ts +2 -0
- package/dist/authoring.d.ts.map +1 -0
- package/dist/authoring.js +1 -0
- package/dist/booking-engine/handler-support.d.ts +91 -0
- package/dist/booking-engine/handler-support.d.ts.map +1 -0
- package/dist/booking-engine/handler-support.js +355 -0
- package/dist/booking-engine/handler.d.ts +404 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +398 -0
- package/dist/booking-engine/index.d.ts +8 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +7 -0
- package/dist/booking-engine.d.ts +2 -0
- package/dist/booking-engine.d.ts.map +1 -0
- package/dist/booking-engine.js +1 -0
- package/dist/booking-extension.d.ts +278 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +161 -0
- package/dist/catalog-policy-departures.d.ts +52 -0
- package/dist/catalog-policy-departures.d.ts.map +1 -0
- package/dist/catalog-policy-departures.js +169 -0
- package/dist/catalog-policy-destinations.d.ts +43 -0
- package/dist/catalog-policy-destinations.d.ts.map +1 -0
- package/dist/catalog-policy-destinations.js +165 -0
- package/dist/catalog-policy-pricing.d.ts +55 -0
- package/dist/catalog-policy-pricing.d.ts.map +1 -0
- package/dist/catalog-policy-pricing.js +109 -0
- package/dist/catalog-policy-promotions.d.ts +52 -0
- package/dist/catalog-policy-promotions.d.ts.map +1 -0
- package/dist/catalog-policy-promotions.js +270 -0
- package/dist/catalog-policy-taxonomy.d.ts +51 -0
- package/dist/catalog-policy-taxonomy.d.ts.map +1 -0
- package/dist/catalog-policy-taxonomy.js +191 -0
- package/dist/catalog-policy.d.ts +33 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +733 -0
- package/dist/content-shape.d.ts +15 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +28 -0
- package/dist/draft-shape.d.ts +43 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +48 -0
- package/dist/events.d.ts +37 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +32 -0
- package/dist/extras/catalog-policy.d.ts +30 -0
- package/dist/extras/catalog-policy.d.ts.map +1 -0
- package/dist/extras/catalog-policy.js +319 -0
- package/dist/extras/content-shape.d.ts +5 -0
- package/dist/extras/content-shape.d.ts.map +1 -0
- package/dist/extras/content-shape.js +13 -0
- package/dist/extras/draft-shape.d.ts +34 -0
- package/dist/extras/draft-shape.d.ts.map +1 -0
- package/dist/extras/draft-shape.js +69 -0
- package/dist/extras/routes.d.ts +380 -0
- package/dist/extras/routes.d.ts.map +1 -0
- package/dist/extras/routes.js +59 -0
- package/dist/extras/schema-sourced-content.d.ts +254 -0
- package/dist/extras/schema-sourced-content.d.ts.map +1 -0
- package/dist/extras/schema-sourced-content.js +45 -0
- package/dist/extras/schema.d.ts +628 -0
- package/dist/extras/schema.d.ts.map +1 -0
- package/dist/extras/schema.js +87 -0
- package/dist/extras/service-catalog-plane.d.ts +77 -0
- package/dist/extras/service-catalog-plane.d.ts.map +1 -0
- package/dist/extras/service-catalog-plane.js +219 -0
- package/dist/extras/service-content-synthesizer.d.ts +41 -0
- package/dist/extras/service-content-synthesizer.d.ts.map +1 -0
- package/dist/extras/service-content-synthesizer.js +138 -0
- package/dist/extras/service-content.d.ts +48 -0
- package/dist/extras/service-content.d.ts.map +1 -0
- package/dist/extras/service-content.js +253 -0
- package/dist/extras/service.d.ts +185 -0
- package/dist/extras/service.d.ts.map +1 -0
- package/dist/extras/service.js +96 -0
- package/dist/extras/validation.d.ts +437 -0
- package/dist/extras/validation.d.ts.map +1 -0
- package/dist/extras/validation.js +149 -0
- package/dist/extras.d.ts +267 -0
- package/dist/extras.d.ts.map +1 -0
- package/dist/extras.js +19 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/interface.d.ts +5869 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +54 -0
- package/dist/public-routes.d.ts +2 -0
- package/dist/public-routes.d.ts.map +1 -0
- package/dist/public-routes.js +1 -0
- package/dist/public-validation.d.ts +2 -0
- package/dist/public-validation.d.ts.map +1 -0
- package/dist/public-validation.js +1 -0
- package/dist/read-model.d.ts +25 -0
- package/dist/read-model.d.ts.map +1 -0
- package/dist/read-model.js +99 -0
- package/dist/route-env.d.ts +22 -0
- package/dist/route-env.d.ts.map +1 -0
- package/dist/route-env.js +1 -0
- package/dist/routes-associations.d.ts +164 -0
- package/dist/routes-associations.d.ts.map +1 -0
- package/dist/routes-associations.js +100 -0
- package/dist/routes-catalog.d.ts +436 -0
- package/dist/routes-catalog.d.ts.map +1 -0
- package/dist/routes-catalog.js +104 -0
- package/dist/routes-configuration.d.ts +773 -0
- package/dist/routes-configuration.d.ts.map +1 -0
- package/dist/routes-configuration.js +364 -0
- package/dist/routes-content.d.ts +74 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +117 -0
- package/dist/routes-core.d.ts +331 -0
- package/dist/routes-core.d.ts.map +1 -0
- package/dist/routes-core.js +95 -0
- package/dist/routes-itinerary.d.ts +759 -0
- package/dist/routes-itinerary.d.ts.map +1 -0
- package/dist/routes-itinerary.js +387 -0
- package/dist/routes-maintenance.d.ts +32 -0
- package/dist/routes-maintenance.d.ts.map +1 -0
- package/dist/routes-maintenance.js +14 -0
- package/dist/routes-media.d.ts +634 -0
- package/dist/routes-media.d.ts.map +1 -0
- package/dist/routes-media.js +245 -0
- package/dist/routes-merchandising.d.ts +1120 -0
- package/dist/routes-merchandising.d.ts.map +1 -0
- package/dist/routes-merchandising.js +377 -0
- package/dist/routes-options.d.ts +363 -0
- package/dist/routes-options.d.ts.map +1 -0
- package/dist/routes-options.js +173 -0
- package/dist/routes-public.d.ts +776 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +119 -0
- package/dist/routes-translations.d.ts +489 -0
- package/dist/routes-translations.d.ts.map +1 -0
- package/dist/routes-translations.js +258 -0
- package/dist/routes.d.ts +5097 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +64 -0
- package/dist/schema-core.d.ts +1238 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +157 -0
- package/dist/schema-itinerary.d.ts +1169 -0
- package/dist/schema-itinerary.d.ts.map +1 -0
- package/dist/schema-itinerary.js +130 -0
- package/dist/schema-relations.d.ts +117 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +192 -0
- package/dist/schema-settings.d.ts +1800 -0
- package/dist/schema-settings.d.ts.map +1 -0
- package/dist/schema-settings.js +220 -0
- package/dist/schema-shared.d.ts +15 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +91 -0
- package/dist/schema-sourced-content.d.ts +262 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +69 -0
- package/dist/schema-taxonomy.d.ts +1363 -0
- package/dist/schema-taxonomy.d.ts.map +1 -0
- package/dist/schema-taxonomy.js +203 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +9 -0
- package/dist/service-aggregates.d.ts +29 -0
- package/dist/service-aggregates.d.ts.map +1 -0
- package/dist/service-aggregates.js +56 -0
- package/dist/service-catalog-plane-destinations.d.ts +30 -0
- package/dist/service-catalog-plane-destinations.d.ts.map +1 -0
- package/dist/service-catalog-plane-destinations.js +143 -0
- package/dist/service-catalog-plane-taxonomy.d.ts +73 -0
- package/dist/service-catalog-plane-taxonomy.d.ts.map +1 -0
- package/dist/service-catalog-plane-taxonomy.js +242 -0
- package/dist/service-catalog-plane.d.ts +179 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +431 -0
- package/dist/service-catalog.d.ts +251 -0
- package/dist/service-catalog.d.ts.map +1 -0
- package/dist/service-catalog.js +517 -0
- package/dist/service-configuration.d.ts +261 -0
- package/dist/service-configuration.d.ts.map +1 -0
- package/dist/service-configuration.js +343 -0
- package/dist/service-content-owned.d.ts +68 -0
- package/dist/service-content-owned.d.ts.map +1 -0
- package/dist/service-content-owned.js +329 -0
- package/dist/service-content-synthesizer.d.ts +90 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +178 -0
- package/dist/service-content.d.ts +106 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +388 -0
- package/dist/service-core.d.ts +194 -0
- package/dist/service-core.d.ts.map +1 -0
- package/dist/service-core.js +213 -0
- package/dist/service-delivery-formats.d.ts +58 -0
- package/dist/service-delivery-formats.d.ts.map +1 -0
- package/dist/service-delivery-formats.js +107 -0
- package/dist/service-destinations.d.ts +223 -0
- package/dist/service-destinations.d.ts.map +1 -0
- package/dist/service-destinations.js +310 -0
- package/dist/service-itinerary-history.d.ts +457 -0
- package/dist/service-itinerary-history.d.ts.map +1 -0
- package/dist/service-itinerary-history.js +135 -0
- package/dist/service-itinerary.d.ts +1149 -0
- package/dist/service-itinerary.d.ts.map +1 -0
- package/dist/service-itinerary.js +419 -0
- package/dist/service-media.d.ts +272 -0
- package/dist/service-media.d.ts.map +1 -0
- package/dist/service-media.js +320 -0
- package/dist/service-merchandising.d.ts +184 -0
- package/dist/service-merchandising.d.ts.map +1 -0
- package/dist/service-merchandising.js +181 -0
- package/dist/service-option-translations.d.ts +268 -0
- package/dist/service-option-translations.d.ts.map +1 -0
- package/dist/service-option-translations.js +300 -0
- package/dist/service-options.d.ts +181 -0
- package/dist/service-options.d.ts.map +1 -0
- package/dist/service-options.js +179 -0
- package/dist/service-product-destinations.d.ts +37 -0
- package/dist/service-product-destinations.d.ts.map +1 -0
- package/dist/service-product-destinations.js +94 -0
- package/dist/service-public.d.ts +664 -0
- package/dist/service-public.d.ts.map +1 -0
- package/dist/service-public.js +374 -0
- package/dist/service-taxonomy.d.ts +197 -0
- package/dist/service-taxonomy.d.ts.map +1 -0
- package/dist/service-taxonomy.js +221 -0
- package/dist/service.d.ts +3929 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +28 -0
- package/dist/tasks/brochure-printers.d.ts +31 -0
- package/dist/tasks/brochure-printers.d.ts.map +1 -0
- package/dist/tasks/brochure-printers.js +149 -0
- package/dist/tasks/brochure-templates.d.ts +36 -0
- package/dist/tasks/brochure-templates.d.ts.map +1 -0
- package/dist/tasks/brochure-templates.js +110 -0
- package/dist/tasks/brochures.d.ts +43 -0
- package/dist/tasks/brochures.d.ts.map +1 -0
- package/dist/tasks/brochures.js +72 -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 +106 -0
- package/dist/tasks/index.d.ts +5 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/pdf-text.d.ts +2 -0
- package/dist/tasks/pdf-text.d.ts.map +1 -0
- package/dist/tasks/pdf-text.js +40 -0
- package/dist/tasks.d.ts +2 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +1 -0
- package/dist/validation-catalog.d.ts +2 -0
- package/dist/validation-catalog.d.ts.map +1 -0
- package/dist/validation-catalog.js +3 -0
- package/dist/validation-config.d.ts +2 -0
- package/dist/validation-config.d.ts.map +1 -0
- package/dist/validation-config.js +3 -0
- package/dist/validation-content.d.ts +2 -0
- package/dist/validation-content.d.ts.map +1 -0
- package/dist/validation-content.js +3 -0
- package/dist/validation-core.d.ts +2 -0
- package/dist/validation-core.d.ts.map +1 -0
- package/dist/validation-core.js +3 -0
- package/dist/validation-public.d.ts +2 -0
- package/dist/validation-public.d.ts.map +1 -0
- package/dist/validation-public.js +3 -0
- package/dist/validation-shared.d.ts +2 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +3 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +3 -0
- package/package.json +204 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-configuration.d.ts","sourceRoot":"","sources":["../src/routes-configuration.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AAIzC,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAobnC,CAAA"}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { parseJsonBody, parseQuery } from "@voyant-travel/hono";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { appendProductMutationLedgerEntry, changedMutationFields } from "./action-ledger.js";
|
|
4
|
+
import { productsService } from "./service.js";
|
|
5
|
+
import * as validation from "./validation.js";
|
|
6
|
+
export const productConfigurationRoutes = new Hono()
|
|
7
|
+
// ==========================================================================
|
|
8
|
+
// Product operating configuration
|
|
9
|
+
// ==========================================================================
|
|
10
|
+
.get("/activation-settings", async (c) => {
|
|
11
|
+
const query = parseQuery(c, validation.productActivationSettingListQuerySchema);
|
|
12
|
+
return c.json(await productsService.listActivationSettings(c.get("db"), query));
|
|
13
|
+
})
|
|
14
|
+
.get("/activation-settings/:id", async (c) => {
|
|
15
|
+
const row = await productsService.getActivationSettingById(c.get("db"), c.req.param("id"));
|
|
16
|
+
if (!row) {
|
|
17
|
+
return c.json({ error: "Product activation setting not found" }, 404);
|
|
18
|
+
}
|
|
19
|
+
return c.json({ data: row });
|
|
20
|
+
})
|
|
21
|
+
.post("/:id/activation-settings", async (c) => {
|
|
22
|
+
const productId = c.req.param("id");
|
|
23
|
+
const body = await parseJsonBody(c, validation.insertProductActivationSettingSchema);
|
|
24
|
+
const before = await productsService.getActivationSettingByProductId(c.get("db"), productId);
|
|
25
|
+
const row = await productsService.upsertActivationSetting(c.get("db"), productId, body);
|
|
26
|
+
if (!row) {
|
|
27
|
+
return c.json({ error: "Product not found" }, 404);
|
|
28
|
+
}
|
|
29
|
+
const action = before ? "update" : "create";
|
|
30
|
+
await appendProductMutationLedgerEntry(c, {
|
|
31
|
+
action,
|
|
32
|
+
productId,
|
|
33
|
+
changedFields: changedMutationFields(body, before, row),
|
|
34
|
+
subject: "product activation settings",
|
|
35
|
+
actionName: `product.activation_settings.${action}`,
|
|
36
|
+
routeOrToolName: `products.activation_settings.${action}`,
|
|
37
|
+
});
|
|
38
|
+
return c.json({ data: row }, 201);
|
|
39
|
+
})
|
|
40
|
+
.patch("/activation-settings/:id", async (c) => {
|
|
41
|
+
const id = c.req.param("id");
|
|
42
|
+
const body = await parseJsonBody(c, validation.updateProductActivationSettingSchema);
|
|
43
|
+
const before = await productsService.getActivationSettingById(c.get("db"), id);
|
|
44
|
+
if (!before) {
|
|
45
|
+
return c.json({ error: "Product activation setting not found" }, 404);
|
|
46
|
+
}
|
|
47
|
+
const row = await productsService.updateActivationSetting(c.get("db"), id, body);
|
|
48
|
+
if (!row) {
|
|
49
|
+
return c.json({ error: "Product activation setting not found" }, 404);
|
|
50
|
+
}
|
|
51
|
+
await appendProductMutationLedgerEntry(c, {
|
|
52
|
+
action: "update",
|
|
53
|
+
productId: row.productId,
|
|
54
|
+
changedFields: changedMutationFields(body, before, row),
|
|
55
|
+
subject: "product activation settings",
|
|
56
|
+
actionName: "product.activation_settings.update",
|
|
57
|
+
routeOrToolName: "products.activation_settings.update",
|
|
58
|
+
});
|
|
59
|
+
return c.json({ data: row });
|
|
60
|
+
})
|
|
61
|
+
.delete("/activation-settings/:id", async (c) => {
|
|
62
|
+
const id = c.req.param("id");
|
|
63
|
+
const before = await productsService.getActivationSettingById(c.get("db"), id);
|
|
64
|
+
if (!before) {
|
|
65
|
+
return c.json({ error: "Product activation setting not found" }, 404);
|
|
66
|
+
}
|
|
67
|
+
const row = await productsService.deleteActivationSetting(c.get("db"), id);
|
|
68
|
+
if (!row) {
|
|
69
|
+
return c.json({ error: "Product activation setting not found" }, 404);
|
|
70
|
+
}
|
|
71
|
+
await appendProductMutationLedgerEntry(c, {
|
|
72
|
+
action: "delete",
|
|
73
|
+
productId: before.productId,
|
|
74
|
+
changedFields: [],
|
|
75
|
+
subject: "product activation settings",
|
|
76
|
+
actionName: "product.activation_settings.delete",
|
|
77
|
+
routeOrToolName: "products.activation_settings.delete",
|
|
78
|
+
});
|
|
79
|
+
return c.json({ success: true }, 200);
|
|
80
|
+
})
|
|
81
|
+
.get("/ticket-settings", async (c) => {
|
|
82
|
+
const query = parseQuery(c, validation.productTicketSettingListQuerySchema);
|
|
83
|
+
return c.json(await productsService.listTicketSettings(c.get("db"), query));
|
|
84
|
+
})
|
|
85
|
+
.get("/ticket-settings/:id", async (c) => {
|
|
86
|
+
const row = await productsService.getTicketSettingById(c.get("db"), c.req.param("id"));
|
|
87
|
+
if (!row) {
|
|
88
|
+
return c.json({ error: "Product ticket setting not found" }, 404);
|
|
89
|
+
}
|
|
90
|
+
return c.json({ data: row });
|
|
91
|
+
})
|
|
92
|
+
.post("/:id/ticket-settings", async (c) => {
|
|
93
|
+
const productId = c.req.param("id");
|
|
94
|
+
const body = await parseJsonBody(c, validation.insertProductTicketSettingSchema);
|
|
95
|
+
const before = await productsService.getTicketSettingByProductId(c.get("db"), productId);
|
|
96
|
+
const row = await productsService.upsertTicketSetting(c.get("db"), productId, body);
|
|
97
|
+
if (!row) {
|
|
98
|
+
return c.json({ error: "Product not found" }, 404);
|
|
99
|
+
}
|
|
100
|
+
const action = before ? "update" : "create";
|
|
101
|
+
await appendProductMutationLedgerEntry(c, {
|
|
102
|
+
action,
|
|
103
|
+
productId,
|
|
104
|
+
changedFields: changedMutationFields(body, before, row),
|
|
105
|
+
subject: "product ticket settings",
|
|
106
|
+
actionName: `product.ticket_settings.${action}`,
|
|
107
|
+
routeOrToolName: `products.ticket_settings.${action}`,
|
|
108
|
+
});
|
|
109
|
+
return c.json({ data: row }, 201);
|
|
110
|
+
})
|
|
111
|
+
.patch("/ticket-settings/:id", async (c) => {
|
|
112
|
+
const id = c.req.param("id");
|
|
113
|
+
const body = await parseJsonBody(c, validation.updateProductTicketSettingSchema);
|
|
114
|
+
const before = await productsService.getTicketSettingById(c.get("db"), id);
|
|
115
|
+
if (!before) {
|
|
116
|
+
return c.json({ error: "Product ticket setting not found" }, 404);
|
|
117
|
+
}
|
|
118
|
+
const row = await productsService.updateTicketSetting(c.get("db"), id, body);
|
|
119
|
+
if (!row) {
|
|
120
|
+
return c.json({ error: "Product ticket setting not found" }, 404);
|
|
121
|
+
}
|
|
122
|
+
await appendProductMutationLedgerEntry(c, {
|
|
123
|
+
action: "update",
|
|
124
|
+
productId: row.productId,
|
|
125
|
+
changedFields: changedMutationFields(body, before, row),
|
|
126
|
+
subject: "product ticket settings",
|
|
127
|
+
actionName: "product.ticket_settings.update",
|
|
128
|
+
routeOrToolName: "products.ticket_settings.update",
|
|
129
|
+
});
|
|
130
|
+
return c.json({ data: row });
|
|
131
|
+
})
|
|
132
|
+
.delete("/ticket-settings/:id", async (c) => {
|
|
133
|
+
const id = c.req.param("id");
|
|
134
|
+
const before = await productsService.getTicketSettingById(c.get("db"), id);
|
|
135
|
+
if (!before) {
|
|
136
|
+
return c.json({ error: "Product ticket setting not found" }, 404);
|
|
137
|
+
}
|
|
138
|
+
const row = await productsService.deleteTicketSetting(c.get("db"), id);
|
|
139
|
+
if (!row) {
|
|
140
|
+
return c.json({ error: "Product ticket setting not found" }, 404);
|
|
141
|
+
}
|
|
142
|
+
await appendProductMutationLedgerEntry(c, {
|
|
143
|
+
action: "delete",
|
|
144
|
+
productId: before.productId,
|
|
145
|
+
changedFields: [],
|
|
146
|
+
subject: "product ticket settings",
|
|
147
|
+
actionName: "product.ticket_settings.delete",
|
|
148
|
+
routeOrToolName: "products.ticket_settings.delete",
|
|
149
|
+
});
|
|
150
|
+
return c.json({ success: true }, 200);
|
|
151
|
+
})
|
|
152
|
+
.get("/visibility-settings", async (c) => {
|
|
153
|
+
const query = parseQuery(c, validation.productVisibilitySettingListQuerySchema);
|
|
154
|
+
return c.json(await productsService.listVisibilitySettings(c.get("db"), query));
|
|
155
|
+
})
|
|
156
|
+
.get("/visibility-settings/:id", async (c) => {
|
|
157
|
+
const row = await productsService.getVisibilitySettingById(c.get("db"), c.req.param("id"));
|
|
158
|
+
if (!row) {
|
|
159
|
+
return c.json({ error: "Product visibility setting not found" }, 404);
|
|
160
|
+
}
|
|
161
|
+
return c.json({ data: row });
|
|
162
|
+
})
|
|
163
|
+
.post("/:id/visibility-settings", async (c) => {
|
|
164
|
+
const productId = c.req.param("id");
|
|
165
|
+
const body = await parseJsonBody(c, validation.insertProductVisibilitySettingSchema);
|
|
166
|
+
const before = await productsService.getVisibilitySettingByProductId(c.get("db"), productId);
|
|
167
|
+
const row = await productsService.upsertVisibilitySetting(c.get("db"), productId, body);
|
|
168
|
+
if (!row) {
|
|
169
|
+
return c.json({ error: "Product not found" }, 404);
|
|
170
|
+
}
|
|
171
|
+
const action = before ? "update" : "create";
|
|
172
|
+
await appendProductMutationLedgerEntry(c, {
|
|
173
|
+
action,
|
|
174
|
+
productId,
|
|
175
|
+
changedFields: changedMutationFields(body, before, row),
|
|
176
|
+
subject: "product visibility settings",
|
|
177
|
+
actionName: `product.visibility_settings.${action}`,
|
|
178
|
+
routeOrToolName: `products.visibility_settings.${action}`,
|
|
179
|
+
});
|
|
180
|
+
return c.json({ data: row }, 201);
|
|
181
|
+
})
|
|
182
|
+
.patch("/visibility-settings/:id", async (c) => {
|
|
183
|
+
const id = c.req.param("id");
|
|
184
|
+
const body = await parseJsonBody(c, validation.updateProductVisibilitySettingSchema);
|
|
185
|
+
const before = await productsService.getVisibilitySettingById(c.get("db"), id);
|
|
186
|
+
if (!before) {
|
|
187
|
+
return c.json({ error: "Product visibility setting not found" }, 404);
|
|
188
|
+
}
|
|
189
|
+
const row = await productsService.updateVisibilitySetting(c.get("db"), id, body);
|
|
190
|
+
if (!row) {
|
|
191
|
+
return c.json({ error: "Product visibility setting not found" }, 404);
|
|
192
|
+
}
|
|
193
|
+
await appendProductMutationLedgerEntry(c, {
|
|
194
|
+
action: "update",
|
|
195
|
+
productId: row.productId,
|
|
196
|
+
changedFields: changedMutationFields(body, before, row),
|
|
197
|
+
subject: "product visibility settings",
|
|
198
|
+
actionName: "product.visibility_settings.update",
|
|
199
|
+
routeOrToolName: "products.visibility_settings.update",
|
|
200
|
+
});
|
|
201
|
+
return c.json({ data: row });
|
|
202
|
+
})
|
|
203
|
+
.delete("/visibility-settings/:id", async (c) => {
|
|
204
|
+
const id = c.req.param("id");
|
|
205
|
+
const before = await productsService.getVisibilitySettingById(c.get("db"), id);
|
|
206
|
+
if (!before) {
|
|
207
|
+
return c.json({ error: "Product visibility setting not found" }, 404);
|
|
208
|
+
}
|
|
209
|
+
const row = await productsService.deleteVisibilitySetting(c.get("db"), id);
|
|
210
|
+
if (!row) {
|
|
211
|
+
return c.json({ error: "Product visibility setting not found" }, 404);
|
|
212
|
+
}
|
|
213
|
+
await appendProductMutationLedgerEntry(c, {
|
|
214
|
+
action: "delete",
|
|
215
|
+
productId: before.productId,
|
|
216
|
+
changedFields: [],
|
|
217
|
+
subject: "product visibility settings",
|
|
218
|
+
actionName: "product.visibility_settings.delete",
|
|
219
|
+
routeOrToolName: "products.visibility_settings.delete",
|
|
220
|
+
});
|
|
221
|
+
return c.json({ success: true }, 200);
|
|
222
|
+
})
|
|
223
|
+
.get("/capabilities", async (c) => {
|
|
224
|
+
const query = parseQuery(c, validation.productCapabilityListQuerySchema);
|
|
225
|
+
return c.json(await productsService.listCapabilities(c.get("db"), query));
|
|
226
|
+
})
|
|
227
|
+
.get("/capabilities/:id", async (c) => {
|
|
228
|
+
const row = await productsService.getCapabilityById(c.get("db"), c.req.param("id"));
|
|
229
|
+
if (!row) {
|
|
230
|
+
return c.json({ error: "Product capability not found" }, 404);
|
|
231
|
+
}
|
|
232
|
+
return c.json({ data: row });
|
|
233
|
+
})
|
|
234
|
+
.post("/:id/capabilities", async (c) => {
|
|
235
|
+
const productId = c.req.param("id");
|
|
236
|
+
const body = await parseJsonBody(c, validation.insertProductCapabilitySchema);
|
|
237
|
+
const before = await productsService.getCapabilityByProductAndName(c.get("db"), productId, body.capability);
|
|
238
|
+
const row = await productsService.createCapability(c.get("db"), productId, body);
|
|
239
|
+
if (!row) {
|
|
240
|
+
return c.json({ error: "Product not found" }, 404);
|
|
241
|
+
}
|
|
242
|
+
const action = before ? "update" : "create";
|
|
243
|
+
await appendProductMutationLedgerEntry(c, {
|
|
244
|
+
action,
|
|
245
|
+
productId,
|
|
246
|
+
changedFields: changedMutationFields(body, before, row),
|
|
247
|
+
subject: "product capability",
|
|
248
|
+
actionName: `product.capability.${action}`,
|
|
249
|
+
routeOrToolName: `products.capability.${action}`,
|
|
250
|
+
});
|
|
251
|
+
return c.json({ data: row }, 201);
|
|
252
|
+
})
|
|
253
|
+
.patch("/capabilities/:id", async (c) => {
|
|
254
|
+
const id = c.req.param("id");
|
|
255
|
+
const body = await parseJsonBody(c, validation.updateProductCapabilitySchema);
|
|
256
|
+
const before = await productsService.getCapabilityById(c.get("db"), id);
|
|
257
|
+
if (!before) {
|
|
258
|
+
return c.json({ error: "Product capability not found" }, 404);
|
|
259
|
+
}
|
|
260
|
+
const row = await productsService.updateCapability(c.get("db"), id, body);
|
|
261
|
+
if (!row) {
|
|
262
|
+
return c.json({ error: "Product capability not found" }, 404);
|
|
263
|
+
}
|
|
264
|
+
await appendProductMutationLedgerEntry(c, {
|
|
265
|
+
action: "update",
|
|
266
|
+
productId: row.productId,
|
|
267
|
+
changedFields: changedMutationFields(body, before, row),
|
|
268
|
+
subject: "product capability",
|
|
269
|
+
actionName: "product.capability.update",
|
|
270
|
+
routeOrToolName: "products.capability.update",
|
|
271
|
+
});
|
|
272
|
+
return c.json({ data: row });
|
|
273
|
+
})
|
|
274
|
+
.delete("/capabilities/:id", async (c) => {
|
|
275
|
+
const id = c.req.param("id");
|
|
276
|
+
const before = await productsService.getCapabilityById(c.get("db"), id);
|
|
277
|
+
if (!before) {
|
|
278
|
+
return c.json({ error: "Product capability not found" }, 404);
|
|
279
|
+
}
|
|
280
|
+
const row = await productsService.deleteCapability(c.get("db"), id);
|
|
281
|
+
if (!row) {
|
|
282
|
+
return c.json({ error: "Product capability not found" }, 404);
|
|
283
|
+
}
|
|
284
|
+
await appendProductMutationLedgerEntry(c, {
|
|
285
|
+
action: "delete",
|
|
286
|
+
productId: before.productId,
|
|
287
|
+
changedFields: [],
|
|
288
|
+
subject: "product capability",
|
|
289
|
+
actionName: "product.capability.delete",
|
|
290
|
+
routeOrToolName: "products.capability.delete",
|
|
291
|
+
});
|
|
292
|
+
return c.json({ success: true }, 200);
|
|
293
|
+
})
|
|
294
|
+
.get("/delivery-formats", async (c) => {
|
|
295
|
+
const query = parseQuery(c, validation.productDeliveryFormatListQuerySchema);
|
|
296
|
+
return c.json(await productsService.listDeliveryFormats(c.get("db"), query));
|
|
297
|
+
})
|
|
298
|
+
.get("/delivery-formats/:id", async (c) => {
|
|
299
|
+
const row = await productsService.getDeliveryFormatById(c.get("db"), c.req.param("id"));
|
|
300
|
+
if (!row) {
|
|
301
|
+
return c.json({ error: "Product delivery format not found" }, 404);
|
|
302
|
+
}
|
|
303
|
+
return c.json({ data: row });
|
|
304
|
+
})
|
|
305
|
+
.post("/:id/delivery-formats", async (c) => {
|
|
306
|
+
const productId = c.req.param("id");
|
|
307
|
+
const body = await parseJsonBody(c, validation.insertProductDeliveryFormatSchema);
|
|
308
|
+
const before = await productsService.getDeliveryFormatByProductAndFormat(c.get("db"), productId, body.format);
|
|
309
|
+
const row = await productsService.createDeliveryFormat(c.get("db"), productId, body);
|
|
310
|
+
if (!row) {
|
|
311
|
+
return c.json({ error: "Product not found" }, 404);
|
|
312
|
+
}
|
|
313
|
+
const action = before ? "update" : "create";
|
|
314
|
+
await appendProductMutationLedgerEntry(c, {
|
|
315
|
+
action,
|
|
316
|
+
productId,
|
|
317
|
+
changedFields: changedMutationFields(body, before, row),
|
|
318
|
+
subject: "product delivery format",
|
|
319
|
+
actionName: `product.delivery_format.${action}`,
|
|
320
|
+
routeOrToolName: `products.delivery_format.${action}`,
|
|
321
|
+
});
|
|
322
|
+
return c.json({ data: row }, 201);
|
|
323
|
+
})
|
|
324
|
+
.patch("/delivery-formats/:id", async (c) => {
|
|
325
|
+
const id = c.req.param("id");
|
|
326
|
+
const body = await parseJsonBody(c, validation.updateProductDeliveryFormatSchema);
|
|
327
|
+
const before = await productsService.getDeliveryFormatById(c.get("db"), id);
|
|
328
|
+
if (!before) {
|
|
329
|
+
return c.json({ error: "Product delivery format not found" }, 404);
|
|
330
|
+
}
|
|
331
|
+
const row = await productsService.updateDeliveryFormat(c.get("db"), id, body);
|
|
332
|
+
if (!row) {
|
|
333
|
+
return c.json({ error: "Product delivery format not found" }, 404);
|
|
334
|
+
}
|
|
335
|
+
await appendProductMutationLedgerEntry(c, {
|
|
336
|
+
action: "update",
|
|
337
|
+
productId: row.productId,
|
|
338
|
+
changedFields: changedMutationFields(body, before, row),
|
|
339
|
+
subject: "product delivery format",
|
|
340
|
+
actionName: "product.delivery_format.update",
|
|
341
|
+
routeOrToolName: "products.delivery_format.update",
|
|
342
|
+
});
|
|
343
|
+
return c.json({ data: row });
|
|
344
|
+
})
|
|
345
|
+
.delete("/delivery-formats/:id", async (c) => {
|
|
346
|
+
const id = c.req.param("id");
|
|
347
|
+
const before = await productsService.getDeliveryFormatById(c.get("db"), id);
|
|
348
|
+
if (!before) {
|
|
349
|
+
return c.json({ error: "Product delivery format not found" }, 404);
|
|
350
|
+
}
|
|
351
|
+
const row = await productsService.deleteDeliveryFormat(c.get("db"), id);
|
|
352
|
+
if (!row) {
|
|
353
|
+
return c.json({ error: "Product delivery format not found" }, 404);
|
|
354
|
+
}
|
|
355
|
+
await appendProductMutationLedgerEntry(c, {
|
|
356
|
+
action: "delete",
|
|
357
|
+
productId: before.productId,
|
|
358
|
+
changedFields: [],
|
|
359
|
+
subject: "product delivery format",
|
|
360
|
+
actionName: "product.delivery_format.delete",
|
|
361
|
+
routeOrToolName: "products.delivery_format.delete",
|
|
362
|
+
});
|
|
363
|
+
return c.json({ success: true }, 200);
|
|
364
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product content routes — unified owned + sourced detail endpoint.
|
|
3
|
+
*
|
|
4
|
+
* GET /:id/content
|
|
5
|
+
*
|
|
6
|
+
* Returns the full `ProductContent` payload for ANY product:
|
|
7
|
+
* - **Sourced**: cache hit → cached row + overlay merge; cache miss
|
|
8
|
+
* with rich adapter → adapter fetch + write-through; cache miss
|
|
9
|
+
* with thin adapter → synthesizer fallback (sourced-content §3.3,
|
|
10
|
+
* §3.4, §3.6).
|
|
11
|
+
* - **Owned**: read from the products module's own tables and
|
|
12
|
+
* project to ProductContent. Overlay merge applies the same way.
|
|
13
|
+
* Marked `source: "owned"` in the response.
|
|
14
|
+
*
|
|
15
|
+
* 404 only when the entity doesn't exist (no sourced-entry row AND
|
|
16
|
+
* no owned product row). The catalog detail sheet calls this on
|
|
17
|
+
* click to enrich the indexed projection with itinerary, media,
|
|
18
|
+
* options, and policies.
|
|
19
|
+
*
|
|
20
|
+
* Templates mount this router under their preferred prefix; the
|
|
21
|
+
* factory takes a `resolveRegistry` callback so the catalog
|
|
22
|
+
* `SourceAdapterRegistry` stays starter-owned (singleton lifetime,
|
|
23
|
+
* adapters carry HTTP clients).
|
|
24
|
+
*
|
|
25
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.3.
|
|
26
|
+
*/
|
|
27
|
+
import type { SourceAdapterRegistry } from "@voyant-travel/catalog/booking-engine";
|
|
28
|
+
import type { AnyDrizzleDb } from "@voyant-travel/db";
|
|
29
|
+
import type { Context } from "hono";
|
|
30
|
+
import { Hono } from "hono";
|
|
31
|
+
export interface ProductContentRoutesEnv {
|
|
32
|
+
Variables: {
|
|
33
|
+
db: AnyDrizzleDb;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface CreateProductContentRoutesOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the catalog `SourceAdapterRegistry` for the current
|
|
39
|
+
* request. Templates typically return a process-local singleton
|
|
40
|
+
* built lazily from env (mirroring the booking-engine registry
|
|
41
|
+
* pattern).
|
|
42
|
+
*/
|
|
43
|
+
resolveRegistry: (c: Context) => SourceAdapterRegistry;
|
|
44
|
+
/**
|
|
45
|
+
* Optional sink for overlay-merge diagnostics. When set, called
|
|
46
|
+
* once per overlay that fails to apply. Defaults to silent (the
|
|
47
|
+
* read still succeeds; the bad overlay is skipped).
|
|
48
|
+
*/
|
|
49
|
+
onOverlayError?: (event: {
|
|
50
|
+
field_path: string;
|
|
51
|
+
reason: string;
|
|
52
|
+
}) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Optional override for `acceptMachineTranslated`. Defaults to
|
|
55
|
+
* `true` — storefront-friendly. Operator surfaces typically set
|
|
56
|
+
* `false` so ops sees authored content before deciding to override.
|
|
57
|
+
*/
|
|
58
|
+
defaultAcceptMachineTranslated?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the product content router. Returns a Hono instance that
|
|
62
|
+
* exposes a single `GET /:id/content` route. Templates mount it under
|
|
63
|
+
* `/v1/admin/products` or `/v1/public/products` as appropriate.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createProductContentRoutes(options: CreateProductContentRoutesOptions): Hono<ProductContentRoutesEnv>;
|
|
66
|
+
/**
|
|
67
|
+
* Parse an `Accept-Language` header into an ordered list of BCP 47
|
|
68
|
+
* tags. Quality factors are honored — higher-q first; ties keep
|
|
69
|
+
* insertion order. Lifted out of the route handler so it's testable
|
|
70
|
+
* in isolation.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseAcceptLanguage(header: string): string[];
|
|
73
|
+
export type ProductContentRoutes = ReturnType<typeof createProductContentRoutes>;
|
|
74
|
+
//# sourceMappingURL=routes-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-content.d.ts","sourceRoot":"","sources":["../src/routes-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI3B,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE;QACT,EAAE,EAAE,YAAY,CAAA;KACjB,CAAA;CACF;AAED,MAAM,WAAW,iCAAiC;IAChD;;;;;OAKG;IACH,eAAe,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,qBAAqB,CAAA;IACtD;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IACxE;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAA;CACzC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,IAAI,CAAC,uBAAuB,CAAC,CA+D/B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB5D;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAA"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product content routes — unified owned + sourced detail endpoint.
|
|
3
|
+
*
|
|
4
|
+
* GET /:id/content
|
|
5
|
+
*
|
|
6
|
+
* Returns the full `ProductContent` payload for ANY product:
|
|
7
|
+
* - **Sourced**: cache hit → cached row + overlay merge; cache miss
|
|
8
|
+
* with rich adapter → adapter fetch + write-through; cache miss
|
|
9
|
+
* with thin adapter → synthesizer fallback (sourced-content §3.3,
|
|
10
|
+
* §3.4, §3.6).
|
|
11
|
+
* - **Owned**: read from the products module's own tables and
|
|
12
|
+
* project to ProductContent. Overlay merge applies the same way.
|
|
13
|
+
* Marked `source: "owned"` in the response.
|
|
14
|
+
*
|
|
15
|
+
* 404 only when the entity doesn't exist (no sourced-entry row AND
|
|
16
|
+
* no owned product row). The catalog detail sheet calls this on
|
|
17
|
+
* click to enrich the indexed projection with itinerary, media,
|
|
18
|
+
* options, and policies.
|
|
19
|
+
*
|
|
20
|
+
* Templates mount this router under their preferred prefix; the
|
|
21
|
+
* factory takes a `resolveRegistry` callback so the catalog
|
|
22
|
+
* `SourceAdapterRegistry` stays starter-owned (singleton lifetime,
|
|
23
|
+
* adapters carry HTTP clients).
|
|
24
|
+
*
|
|
25
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.3.
|
|
26
|
+
*/
|
|
27
|
+
import { Hono } from "hono";
|
|
28
|
+
import { getProductContent } from "./service-content.js";
|
|
29
|
+
/**
|
|
30
|
+
* Build the product content router. Returns a Hono instance that
|
|
31
|
+
* exposes a single `GET /:id/content` route. Templates mount it under
|
|
32
|
+
* `/v1/admin/products` or `/v1/public/products` as appropriate.
|
|
33
|
+
*/
|
|
34
|
+
export function createProductContentRoutes(options) {
|
|
35
|
+
return new Hono().get("/:id/content", async (c) => {
|
|
36
|
+
const entityId = c.req.param("id");
|
|
37
|
+
const scope = parseScope(c);
|
|
38
|
+
const registry = options.resolveRegistry(c);
|
|
39
|
+
const result = await getProductContent(c.var.db, entityId, scope, {
|
|
40
|
+
registry,
|
|
41
|
+
onOverlayError: options.onOverlayError,
|
|
42
|
+
});
|
|
43
|
+
if (!result) {
|
|
44
|
+
return c.json({
|
|
45
|
+
error: "not_found",
|
|
46
|
+
detail: `Product ${entityId} not found (no owned row + no sourced-entry row).`,
|
|
47
|
+
}, 404);
|
|
48
|
+
}
|
|
49
|
+
return c.json({
|
|
50
|
+
data: {
|
|
51
|
+
content: result.content,
|
|
52
|
+
served_locale: result.resolution.served_locale,
|
|
53
|
+
match_kind: result.resolution.match_kind,
|
|
54
|
+
source: result.source,
|
|
55
|
+
served_stale: result.served_stale,
|
|
56
|
+
synthesized: result.synthesized,
|
|
57
|
+
machine_translated: result.machine_translated,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
function parseScope(c) {
|
|
62
|
+
// Locale priority: explicit query param > Accept-Language header
|
|
63
|
+
// > en-GB fallback. Multiple `?locale=` query params are joined
|
|
64
|
+
// into the preference chain.
|
|
65
|
+
const localeParams = c.req.queries("locale") ?? c.req.queries("locales") ?? [];
|
|
66
|
+
const headerLocale = c.req.header("accept-language");
|
|
67
|
+
const acceptLanguageList = headerLocale ? parseAcceptLanguage(headerLocale) : [];
|
|
68
|
+
const preferredLocales = localeParams.length > 0
|
|
69
|
+
? localeParams
|
|
70
|
+
: acceptLanguageList.length > 0
|
|
71
|
+
? acceptLanguageList
|
|
72
|
+
: ["en-GB"];
|
|
73
|
+
const market = c.req.query("market") ?? undefined;
|
|
74
|
+
const currency = c.req.query("currency") ?? undefined;
|
|
75
|
+
const acceptMTQuery = c.req.query("accept_mt");
|
|
76
|
+
const acceptMachineTranslated = acceptMTQuery != null
|
|
77
|
+
? acceptMTQuery !== "false" && acceptMTQuery !== "0"
|
|
78
|
+
: (options.defaultAcceptMachineTranslated ?? true);
|
|
79
|
+
return {
|
|
80
|
+
preferredLocales,
|
|
81
|
+
market,
|
|
82
|
+
currency,
|
|
83
|
+
acceptMachineTranslated,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse an `Accept-Language` header into an ordered list of BCP 47
|
|
89
|
+
* tags. Quality factors are honored — higher-q first; ties keep
|
|
90
|
+
* insertion order. Lifted out of the route handler so it's testable
|
|
91
|
+
* in isolation.
|
|
92
|
+
*/
|
|
93
|
+
export function parseAcceptLanguage(header) {
|
|
94
|
+
const parts = header.split(",");
|
|
95
|
+
const ranked = [];
|
|
96
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
97
|
+
const part = parts[i].trim();
|
|
98
|
+
if (!part)
|
|
99
|
+
continue;
|
|
100
|
+
const [tagRaw, ...params] = part.split(";");
|
|
101
|
+
const tag = tagRaw.trim();
|
|
102
|
+
if (!tag || tag === "*")
|
|
103
|
+
continue;
|
|
104
|
+
let q = 1;
|
|
105
|
+
for (const p of params) {
|
|
106
|
+
const [k, v] = p.split("=").map((s) => s.trim());
|
|
107
|
+
if (k === "q" && v) {
|
|
108
|
+
const parsed = Number.parseFloat(v);
|
|
109
|
+
if (Number.isFinite(parsed))
|
|
110
|
+
q = parsed;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
ranked.push({ tag, q, idx: i });
|
|
114
|
+
}
|
|
115
|
+
ranked.sort((a, b) => b.q - a.q || a.idx - b.idx);
|
|
116
|
+
return ranked.map((r) => r.tag);
|
|
117
|
+
}
|