medusa-plugin-ordinant 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.
Files changed (30) hide show
  1. package/.medusa/server/src/admin/index.js +1636 -0
  2. package/.medusa/server/src/admin/index.mjs +1635 -0
  3. package/.medusa/server/src/api/admin/ordinant/classifications/[sku]/confirm/route.js +25 -0
  4. package/.medusa/server/src/api/admin/ordinant/classifications/bulk-confirm/route.js +29 -0
  5. package/.medusa/server/src/api/admin/ordinant/classifications/route.js +23 -0
  6. package/.medusa/server/src/api/admin/ordinant/classify-all/route.js +108 -0
  7. package/.medusa/server/src/api/admin/ordinant/dictionary/route.js +17 -0
  8. package/.medusa/server/src/api/admin/ordinant/generate-all-skus/route.js +31 -0
  9. package/.medusa/server/src/api/admin/ordinant/overview/route.js +69 -0
  10. package/.medusa/server/src/api/admin/ordinant/products/[id]/classify/route.js +18 -0
  11. package/.medusa/server/src/api/admin/ordinant/products/[id]/generate-skus/route.js +34 -0
  12. package/.medusa/server/src/api/admin/ordinant/products/[id]/overview/route.js +59 -0
  13. package/.medusa/server/src/api/admin/ordinant/stats/route.js +17 -0
  14. package/.medusa/server/src/api/admin/ordinant/status/route.js +52 -0
  15. package/.medusa/server/src/api/middlewares.js +14 -0
  16. package/.medusa/server/src/api/store/ordinant/preview/route.js +110 -0
  17. package/.medusa/server/src/api/store/ordinant/preview/validators.js +34 -0
  18. package/.medusa/server/src/lib/client.js +136 -0
  19. package/.medusa/server/src/modules/ordinant/index.js +14 -0
  20. package/.medusa/server/src/modules/ordinant/service.js +27 -0
  21. package/.medusa/server/src/storefront.d.ts +61 -0
  22. package/.medusa/server/src/storefront.js +61 -0
  23. package/.medusa/server/src/subscribers/product-upsert.js +52 -0
  24. package/.medusa/server/src/workflows/bulk-operations.js +59 -0
  25. package/.medusa/server/src/workflows/classify-product.js +80 -0
  26. package/.medusa/server/src/workflows/confirm-classification.js +20 -0
  27. package/.medusa/server/src/workflows/generate-skus.js +81 -0
  28. package/.medusa/server/src/workflows/hooks/validate-cart-completion.js +127 -0
  29. package/README.md +495 -0
  30. package/package.json +92 -0
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const confirm_classification_1 = require("../../../../../../workflows/confirm-classification");
6
+ /**
7
+ * POST /admin/ordinant/classifications/{sku}/confirm
8
+ *
9
+ * Records the merchant's attestation for a SKU with the final attribute set
10
+ * (the proposal as-is, or edited in the widget). Ordinant validates the
11
+ * attributes against its dictionary; violations surface as 4xx errors the
12
+ * widget displays inline.
13
+ */
14
+ async function POST(req, res) {
15
+ const sku = req.params.sku;
16
+ const attributes = req.body?.attributes;
17
+ if (!attributes || Object.keys(attributes).length === 0) {
18
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "attributes is required — confirmation attests to a specific attribute set");
19
+ }
20
+ const { result } = await (0, confirm_classification_1.confirmClassificationWorkflow)(req.scope).run({
21
+ input: { sku, attributes },
22
+ });
23
+ res.json(result);
24
+ }
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2NsYXNzaWZpY2F0aW9ucy9bc2t1XS9jb25maXJtL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBZ0JBLG9CQWlCQztBQWhDRCxxREFBdUQ7QUFDdkQsK0ZBQWtHO0FBTWxHOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsSUFBSSxDQUN4QixHQUE0QyxFQUM1QyxHQUFtQjtJQUVuQixNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQTtJQUMxQixNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQTtJQUN2QyxJQUFJLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hELE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLDJFQUEyRSxDQUM1RSxDQUFBO0lBQ0gsQ0FBQztJQUVELE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLElBQUEsc0RBQTZCLEVBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQztRQUNwRSxLQUFLLEVBQUUsRUFBRSxHQUFHLEVBQUUsVUFBVSxFQUFFO0tBQzNCLENBQUMsQ0FBQTtJQUNGLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUE7QUFDbEIsQ0FBQyJ9
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const bulk_operations_1 = require("../../../../../workflows/bulk-operations");
6
+ /** Cap per request; the admin page chunks larger selections. */
7
+ const maxSkus = 100;
8
+ /**
9
+ * POST /admin/ordinant/classifications/bulk-confirm
10
+ *
11
+ * Confirms the listed SKUs' proposals AS PROPOSED — a bulk attestation.
12
+ * The admin page shows a category-grouped summary of exactly what is being
13
+ * attested before calling this; the merchant remains the classifier of
14
+ * record.
15
+ */
16
+ async function POST(req, res) {
17
+ const skus = req.body?.skus;
18
+ if (!Array.isArray(skus) || skus.length === 0) {
19
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "skus is required");
20
+ }
21
+ if (skus.length > maxSkus) {
22
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `at most ${maxSkus} skus per request — chunk larger selections`);
23
+ }
24
+ const { result } = await (0, bulk_operations_1.bulkConfirmWorkflow)(req.scope).run({
25
+ input: { skus },
26
+ });
27
+ res.json({ results: result });
28
+ }
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2NsYXNzaWZpY2F0aW9ucy9idWxrLWNvbmZpcm0vcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFtQkEsb0JBa0JDO0FBcENELHFEQUF1RDtBQUN2RCw4RUFBOEU7QUFNOUUsZ0VBQWdFO0FBQ2hFLE1BQU0sT0FBTyxHQUFHLEdBQUcsQ0FBQTtBQUVuQjs7Ozs7OztHQU9HO0FBQ0ksS0FBSyxVQUFVLElBQUksQ0FDeEIsR0FBZ0QsRUFDaEQsR0FBbUI7SUFFbkIsTUFBTSxJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUE7SUFDM0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUM5QyxNQUFNLElBQUksbUJBQVcsQ0FBQyxtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQTtJQUMzRSxDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sRUFBRSxDQUFDO1FBQzFCLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLFdBQVcsT0FBTyw2Q0FBNkMsQ0FDaEUsQ0FBQTtJQUNILENBQUM7SUFDRCxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxJQUFBLHFDQUFtQixFQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUM7UUFDMUQsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFO0tBQ2hCLENBQUMsQ0FBQTtJQUNGLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQTtBQUMvQixDQUFDIn0=
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const ordinant_1 = require("../../../../modules/ordinant");
5
+ /**
6
+ * GET /admin/ordinant/classifications?skus=a,b,c
7
+ *
8
+ * Proxies the merchant's Ordinant classifications for the admin widget,
9
+ * optionally filtered to a product's SKUs. The Ordinant API key stays
10
+ * server-side; the dashboard authenticates as an ordinary admin user.
11
+ */
12
+ async function GET(req, res) {
13
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
14
+ const skusParam = typeof req.query.skus === "string" ? req.query.skus : "";
15
+ const skus = new Set(skusParam
16
+ .split(",")
17
+ .map((s) => s.trim())
18
+ .filter(Boolean));
19
+ const { items } = await ordinant.client.listClassifications(undefined, 500);
20
+ const filtered = skus.size > 0 ? items.filter((c) => skus.has(c.sku)) : items;
21
+ res.json({ items: filtered });
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2NsYXNzaWZpY2F0aW9ucy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQVdBLGtCQWFDO0FBdkJELDJEQUE4RDtBQUc5RDs7Ozs7O0dBTUc7QUFDSSxLQUFLLFVBQVUsR0FBRyxDQUFDLEdBQStCLEVBQUUsR0FBbUI7SUFDNUUsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQXdCLDBCQUFlLENBQUMsQ0FBQTtJQUMxRSxNQUFNLFNBQVMsR0FBRyxPQUFPLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQTtJQUMxRSxNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FDbEIsU0FBUztTQUNOLEtBQUssQ0FBQyxHQUFHLENBQUM7U0FDVixHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztTQUNwQixNQUFNLENBQUMsT0FBTyxDQUFDLENBQ25CLENBQUE7SUFFRCxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsTUFBTSxRQUFRLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLFNBQVMsRUFBRSxHQUFHLENBQUMsQ0FBQTtJQUMzRSxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFBO0lBQzdFLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQTtBQUMvQixDQUFDIn0=
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const ordinant_1 = require("../../../../modules/ordinant");
6
+ const bulk_operations_1 = require("../../../../workflows/bulk-operations");
7
+ const client_1 = require("../../../../lib/client");
8
+ /** SKUs per invocation — kept modest so each request finishes in a few
9
+ * seconds even at real model latency, giving smooth progress and headroom
10
+ * under every timeout; the admin page loops until `remaining` reaches zero. */
11
+ const chunkSize = 20;
12
+ /**
13
+ * POST /admin/ordinant/classify-all
14
+ *
15
+ * Classifies the next chunk of UNCLASSIFIED SKUs (products that predate the
16
+ * plugin, or whose classification previously failed). Returns how many
17
+ * remain so the caller can keep going.
18
+ */
19
+ async function POST(req, res) {
20
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
21
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
22
+ const [{ data: products }, { items: classifications }] = await Promise.all([
23
+ query.graph({
24
+ entity: "product",
25
+ fields: [
26
+ "id",
27
+ "title",
28
+ "description",
29
+ "type.value",
30
+ "collection.title",
31
+ "variants.sku",
32
+ "variants.title",
33
+ ],
34
+ pagination: { take: 1000, skip: 0 },
35
+ }),
36
+ ordinant.client.listClassifications(undefined, 5000),
37
+ ]);
38
+ const classified = new Set(classifications.map((c) => c.sku));
39
+ const backlog = [];
40
+ for (const product of products) {
41
+ for (const variant of product.variants ?? []) {
42
+ if (!variant?.sku || classified.has(variant.sku)) {
43
+ continue;
44
+ }
45
+ backlog.push({
46
+ sku: variant.sku,
47
+ title: variant.title && variant.title !== "Default variant"
48
+ ? `${product.title} — ${variant.title}`
49
+ : product.title,
50
+ description: product.description ?? undefined,
51
+ product_type: product.type?.value ?? product.collection?.title ?? undefined,
52
+ });
53
+ }
54
+ }
55
+ const chunk = backlog.slice(0, chunkSize);
56
+ let proposed = 0;
57
+ let failed = 0;
58
+ if (chunk.length > 0) {
59
+ let result;
60
+ try {
61
+ ;
62
+ ({ result } = await (0, bulk_operations_1.bulkClassifyWorkflow)(req.scope).run({
63
+ input: { items: chunk },
64
+ }));
65
+ }
66
+ catch (error) {
67
+ // A whole-request failure (engine down, or classification disabled
68
+ // server-side) is a configuration problem the merchant can act on —
69
+ // surface it clearly instead of a generic 500. The workflow may wrap
70
+ // the OrdinantApiError, so match on the 503 signal in the message.
71
+ const cause = unwrap(error);
72
+ const message = cause instanceof Error ? cause.message : String(cause);
73
+ const disabled = (cause instanceof client_1.OrdinantApiError && cause.status === 503) ||
74
+ /not enabled|service_unavailable|503/i.test(message);
75
+ if (disabled) {
76
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, "Ordinant classification is not enabled. Set ORDINANT_CLASSIFY_ENABLED " +
77
+ "and ANTHROPIC_API_KEY on the Ordinant deployment.");
78
+ }
79
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Classification request failed: ${message}`);
80
+ }
81
+ for (const r of result) {
82
+ if (r.error) {
83
+ failed++;
84
+ }
85
+ else {
86
+ proposed++;
87
+ }
88
+ }
89
+ }
90
+ res.json({
91
+ processed: chunk.length,
92
+ proposed,
93
+ failed,
94
+ remaining: backlog.length - chunk.length,
95
+ });
96
+ }
97
+ /** unwrap peels one layer of error wrapping (Medusa workflows re-throw step
98
+ * errors) without relying on the ES2022 `Error.cause` typing. */
99
+ function unwrap(error) {
100
+ if (error && typeof error === "object" && "cause" in error) {
101
+ const cause = error.cause;
102
+ if (cause) {
103
+ return cause;
104
+ }
105
+ }
106
+ return error;
107
+ }
108
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2NsYXNzaWZ5LWFsbC9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQW1CQSxvQkFzRkM7QUF4R0QscURBQWtGO0FBQ2xGLDJEQUE4RDtBQUU5RCwyRUFBNEU7QUFDNUUsbURBQTRFO0FBRTVFOzsrRUFFK0U7QUFDL0UsTUFBTSxTQUFTLEdBQUcsRUFBRSxDQUFBO0FBRXBCOzs7Ozs7R0FNRztBQUNJLEtBQUssVUFBVSxJQUFJLENBQUMsR0FBK0IsRUFBRSxHQUFtQjtJQUM3RSxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQ0FBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUNoRSxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBd0IsMEJBQWUsQ0FBQyxDQUFBO0lBRTFFLE1BQU0sQ0FBQyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxlQUFlLEVBQUUsQ0FBQyxHQUFHLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQztRQUN6RSxLQUFLLENBQUMsS0FBSyxDQUFDO1lBQ1YsTUFBTSxFQUFFLFNBQVM7WUFDakIsTUFBTSxFQUFFO2dCQUNOLElBQUk7Z0JBQ0osT0FBTztnQkFDUCxhQUFhO2dCQUNiLFlBQVk7Z0JBQ1osa0JBQWtCO2dCQUNsQixjQUFjO2dCQUNkLGdCQUFnQjthQUNqQjtZQUNELFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRTtTQUNwQyxDQUFDO1FBQ0YsUUFBUSxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDO0tBQ3JELENBQUMsQ0FBQTtJQUVGLE1BQU0sVUFBVSxHQUFHLElBQUksR0FBRyxDQUFD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
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const ordinant_1 = require("../../../../modules/ordinant");
5
+ /**
6
+ * GET /admin/ordinant/dictionary
7
+ *
8
+ * The attribute dictionary the current ruleset decides against. The admin UI
9
+ * renders its classification dropdowns from this — the dictionary IS the
10
+ * form schema, so new legal attributes appear automatically with no UI code.
11
+ */
12
+ async function GET(req, res) {
13
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
14
+ const dictionary = await ordinant.client.getDictionary();
15
+ res.json(dictionary);
16
+ }
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2RpY3Rpb25hcnkvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFXQSxrQkFJQztBQWRELDJEQUE4RDtBQUc5RDs7Ozs7O0dBTUc7QUFDSSxLQUFLLFVBQVUsR0FBRyxDQUFDLEdBQStCLEVBQUUsR0FBbUI7SUFDNUUsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQXdCLDBCQUFlLENBQUMsQ0FBQTtJQUMxRSxNQUFNLFVBQVUsR0FBRyxNQUFNLFFBQVEsQ0FBQyxNQUFNLENBQUMsYUFBYSxFQUFFLENBQUE7SUFDeEQsR0FBRyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQTtBQUN0QixDQUFDIn0=
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const generate_skus_1 = require("../../../../workflows/generate-skus");
6
+ /**
7
+ * POST /admin/ordinant/generate-all-skus
8
+ *
9
+ * Generates SKUs for every product that has a SKU-less variant, in one
10
+ * action. SKU generation is DB-only (fast), so this runs synchronously;
11
+ * classification of the newly-SKU'd products is left to the caller's
12
+ * classify-all pass (the dashboard chains them behind one button).
13
+ */
14
+ async function POST(req, res) {
15
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
16
+ const { data: products } = await query.graph({
17
+ entity: "product",
18
+ fields: ["id", "variants.sku"],
19
+ pagination: { take: 1000, skip: 0 },
20
+ });
21
+ const needsSku = products.filter((p) => (p.variants ?? []).some((v) => !v?.sku));
22
+ let generated = 0;
23
+ for (const product of needsSku) {
24
+ const { result } = await (0, generate_skus_1.generateSkusWorkflow)(req.scope).run({
25
+ input: { product_id: product.id },
26
+ });
27
+ generated += result.length;
28
+ }
29
+ res.json({ products: needsSku.length, generated });
30
+ }
31
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2dlbmVyYXRlLWFsbC1za3VzL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBWUEsb0JBcUJDO0FBaENELHFEQUFxRTtBQUNyRSx1RUFBMEU7QUFFMUU7Ozs7Ozs7R0FPRztBQUNJLEtBQUssVUFBVSxJQUFJLENBQUMsR0FBK0IsRUFBRSxHQUFtQjtJQUM3RSxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQ0FBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUVoRSxNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxHQUFHLE1BQU0sS0FBSyxDQUFDLEtBQUssQ0FBQztRQUMzQyxNQUFNLEVBQUUsU0FBUztRQUNqQixNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDO1FBQzlCLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRTtLQUNwQyxDQUFDLENBQUE7SUFDRixNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FDckMsQ0FBQyxDQUFDLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQ3hDLENBQUE7SUFFRCxJQUFJLFNBQVMsR0FBRyxDQUFDLENBQUE7SUFDakIsS0FBSyxNQUFNLE9BQU8sSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUMvQixNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxJQUFBLG9DQUFvQixFQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUM7WUFDM0QsS0FBSyxFQUFFLEVBQUUsVUFBVSxFQUFFLE9BQU8sQ0FBQyxFQUFFLEVBQUU7U0FDbEMsQ0FBQyxDQUFBO1FBQ0YsU0FBUyxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUE7SUFDNUIsQ0FBQztJQUVELEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxRQUFRLEVBQUUsUUFBUSxDQUFDLE1BQU0sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFBO0FBQ3BELENBQUMifQ==
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const ordinant_1 = require("../../../../modules/ordinant");
6
+ /**
7
+ * GET /admin/ordinant/overview
8
+ *
9
+ * The Ordinant admin page's display query: every variant SKU in the store
10
+ * joined with its Ordinant classification state. Medusa is the source of
11
+ * "which SKUs exist"; Ordinant is the source of "which are classified" —
12
+ * the join is what makes unclassified SKUs visible at all.
13
+ */
14
+ async function GET(req, res) {
15
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
16
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
17
+ const [{ data: products }, { items: classifications }] = await Promise.all([
18
+ query.graph({
19
+ entity: "product",
20
+ fields: ["id", "title", "variants.id", "variants.sku", "variants.title"],
21
+ pagination: { take: 1000, skip: 0 },
22
+ }),
23
+ ordinant.client.listClassifications(undefined, 5000),
24
+ ]);
25
+ const bySku = new Map(classifications.map((c) => [c.sku, c]));
26
+ const rows = [];
27
+ for (const product of products) {
28
+ for (const variant of product.variants ?? []) {
29
+ if (!variant?.sku) {
30
+ // Surface SKU-less variants instead of hiding them — they can't be
31
+ // sold until they get a SKU, and the merchant fixes that here.
32
+ rows.push({
33
+ sku: "",
34
+ variant_id: variant?.id ?? "",
35
+ product_id: product.id,
36
+ product_title: product.title,
37
+ variant_title: variant?.title ?? null,
38
+ status: "needs_sku",
39
+ confidence: null,
40
+ attributes: null,
41
+ notes: null,
42
+ });
43
+ continue;
44
+ }
45
+ const record = bySku.get(variant.sku);
46
+ rows.push({
47
+ sku: variant.sku,
48
+ variant_id: variant.id ?? "",
49
+ product_id: product.id,
50
+ product_title: product.title,
51
+ variant_title: variant.title ?? null,
52
+ status: record ? record.status : "unclassified",
53
+ confidence: record ? record.confidence : null,
54
+ attributes: record ? record.attributes : null,
55
+ notes: record?.notes ?? null,
56
+ });
57
+ }
58
+ }
59
+ rows.sort((a, b) => (a.sku || "~").localeCompare(b.sku || "~"));
60
+ const summary = {
61
+ total: rows.length,
62
+ needs_sku: rows.filter((r) => r.status === "needs_sku").length,
63
+ unclassified: rows.filter((r) => r.status === "unclassified").length,
64
+ proposed: rows.filter((r) => r.status === "proposed").length,
65
+ confirmed: rows.filter((r) => r.status === "confirmed").length,
66
+ };
67
+ res.json({ rows, summary });
68
+ }
69
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L292ZXJ2aWV3L3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBMkJBLGtCQXlEQztBQW5GRCxxREFBcUU7QUFDckUsMkRBQThEO0FBaUI5RDs7Ozs7OztHQU9HO0FBQ0ksS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUErQixFQUFFLEdBQW1CO0lBQzVFLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ2hFLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUF3QiwwQkFBZSxDQUFDLENBQUE7SUFFMUUsTUFBTSxDQUFDLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxFQUFFLEVBQUUsS0FBSyxFQUFFLGVBQWUsRUFBRSxDQUFDLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDO1FBQ3pFLEtBQUssQ0FBQyxLQUFLLENBQUM7WUFDVixNQUFNLEVBQUUsU0FBUztZQUNqQixNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLENBQUM7WUFDeEUsVUFBVSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFO1NBQ3BDLENBQUM7UUFDRixRQUFRLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUM7S0FDckQsQ0FBQyxDQUFBO0lBRUYsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtJQUM3RCxN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
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const classify_product_1 = require("../../../../../../workflows/classify-product");
5
+ /**
6
+ * POST /admin/ordinant/products/{id}/classify
7
+ *
8
+ * Manually (re-)classifies a product's variants — the widget's "Classify"
9
+ * action, useful for products created before the plugin was installed or
10
+ * after editing product data.
11
+ */
12
+ async function POST(req, res) {
13
+ const { result } = await (0, classify_product_1.classifyProductWorkflow)(req.scope).run({
14
+ input: { product_id: req.params.id },
15
+ });
16
+ res.json({ results: result });
17
+ }
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L3Byb2R1Y3RzL1tpZF0vY2xhc3NpZnkvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFVQSxvQkFLQztBQWRELG1GQUFzRjtBQUV0Rjs7Ozs7O0dBTUc7QUFDSSxLQUFLLFVBQVUsSUFBSSxDQUFDLEdBQStCLEVBQUUsR0FBbUI7SUFDN0UsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLE1BQU0sSUFBQSwwQ0FBdUIsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDO1FBQzlELEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRTtLQUNyQyxDQUFDLENBQUE7SUFDRixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUE7QUFDL0IsQ0FBQyJ9
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const generate_skus_1 = require("../../../../../../workflows/generate-skus");
5
+ const classify_product_1 = require("../../../../../../workflows/classify-product");
6
+ /**
7
+ * POST /admin/ordinant/products/{id}/generate-skus
8
+ *
9
+ * Assigns readable, collision-free SKUs to the product's SKU-less variants,
10
+ * then immediately classifies the product — hands-off: one action takes a
11
+ * SKU-less product all the way to a proposed classification, so it lands in
12
+ * "ready to confirm", never stuck as unclassified needing manual work.
13
+ * Classification is best-effort (if the module is off or errors, the SKUs
14
+ * are still written and the product can be classified later).
15
+ */
16
+ async function POST(req, res) {
17
+ const { result: generated } = await (0, generate_skus_1.generateSkusWorkflow)(req.scope).run({
18
+ input: { product_id: req.params.id },
19
+ });
20
+ let classified = 0;
21
+ if (generated.length > 0) {
22
+ try {
23
+ const { result } = await (0, classify_product_1.classifyProductWorkflow)(req.scope).run({
24
+ input: { product_id: req.params.id },
25
+ });
26
+ classified = result.filter((r) => !r.error).length;
27
+ }
28
+ catch {
29
+ // SKUs are written; classification can be retried from the dashboard.
30
+ }
31
+ }
32
+ res.json({ generated, classified });
33
+ }
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L3Byb2R1Y3RzL1tpZF0vZ2VuZXJhdGUtc2t1cy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWNBLG9CQWtCQztBQS9CRCw2RUFBZ0Y7QUFDaEYsbUZBQXNGO0FBRXRGOzs7Ozs7Ozs7R0FTRztBQUNJLEtBQUssVUFBVSxJQUFJLENBQUMsR0FBK0IsRUFBRSxHQUFtQjtJQUM3RSxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLE1BQU0sSUFBQSxvQ0FBb0IsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDO1FBQ3RFLEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRTtLQUNyQyxDQUFDLENBQUE7SUFFRixJQUFJLFVBQVUsR0FBRyxDQUFDLENBQUE7SUFDbEIsSUFBSSxTQUFTLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3pCLElBQUksQ0FBQztZQUNILE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLElBQUEsMENBQXVCLEVBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQztnQkFDOUQsS0FBSyxFQUFFLEVBQUUsVUFBVSxFQUFFLEdBQUcsQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFO2FBQ3JDLENBQUMsQ0FBQTtZQUNGLFVBQVUsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUE7UUFDcEQsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLHNFQUFzRTtRQUN4RSxDQUFDO0lBQ0gsQ0FBQztJQUVELEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQTtBQUNyQyxDQUFDIn0=
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const ordinant_1 = require("../../../../../../modules/ordinant");
6
+ /**
7
+ * GET /admin/ordinant/products/{id}/overview
8
+ *
9
+ * The per-product widget's display query. Resolves the product's variant
10
+ * SKUs SERVER-SIDE via query.graph — the admin widget's injected `data`
11
+ * prop does not reliably carry variants with SKUs, so we never depend on it.
12
+ * Joins each SKU with its Ordinant classification state.
13
+ */
14
+ async function GET(req, res) {
15
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
16
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
17
+ const { data: products } = await query.graph({
18
+ entity: "product",
19
+ fields: ["id", "variants.id", "variants.sku", "variants.title"],
20
+ filters: { id: req.params.id },
21
+ });
22
+ const product = products[0];
23
+ const allVariants = product?.variants ?? [];
24
+ const withSku = allVariants.filter((v) => v?.sku);
25
+ // Only fetch classifications if there are SKUs to look up.
26
+ let bySku = new Map();
27
+ if (withSku.length > 0) {
28
+ const { items } = await ordinant.client.listClassifications(undefined, 5000);
29
+ const skuSet = new Set(withSku.map((v) => v.sku));
30
+ bySku = new Map(items.filter((c) => skuSet.has(c.sku)).map((c) => [c.sku, c]));
31
+ }
32
+ const rows = allVariants.map((variant) => {
33
+ if (!variant?.sku) {
34
+ return {
35
+ sku: "",
36
+ variant_id: variant?.id ?? "",
37
+ variant_title: variant?.title ?? null,
38
+ status: "needs_sku",
39
+ confidence: null,
40
+ attributes: null,
41
+ notes: null,
42
+ };
43
+ }
44
+ const record = bySku.get(variant.sku);
45
+ return {
46
+ sku: variant.sku,
47
+ variant_id: variant.id,
48
+ variant_title: variant.title ?? null,
49
+ status: record ? record.status : "unclassified",
50
+ confidence: record ? record.confidence : null,
51
+ attributes: record ? record.attributes : null,
52
+ notes: record?.notes ?? null,
53
+ };
54
+ });
55
+ const needsSku = rows.some((r) => r.status === "needs_sku");
56
+ // no_skus: every variant lacks a SKU (nothing classifiable at all).
57
+ res.json({ rows, no_skus: rows.length > 0 && rows.every((r) => r.status === "needs_sku"), needs_sku: needsSku });
58
+ }
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L3Byb2R1Y3RzL1tpZF0vb3ZlcnZpZXcvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF1QkEsa0JBZ0RDO0FBdEVELHFEQUFxRTtBQUNyRSxpRUFBb0U7QUFhcEU7Ozs7Ozs7R0FPRztBQUNJLEtBQUssVUFBVSxHQUFHLENBQUMsR0FBK0IsRUFBRSxHQUFtQjtJQUM1RSxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQ0FBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUNoRSxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBd0IsMEJBQWUsQ0FBQyxDQUFBO0lBRTFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO1FBQzNDLE1BQU0sRUFBRSxTQUFTO1FBQ2pCLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBRSxhQUFhLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixDQUFDO1FBQy9ELE9BQU8sRUFBRSxFQUFFLEVBQUUsRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRTtLQUMvQixDQUFDLENBQUE7SUFDRixNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDM0IsTUFBTSxXQUFXLEdBQUcsT0FBTyxFQUFFLFFBQVEsSUFBSSxFQUFFLENBQUE7SUFDM0MsTUFBTSxPQUFPLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFBO0lBRWpELDJEQUEyRDtJQUMzRCxJQUFJLEtBQUssR0FBRyxJQUFJLEdBQUcsRUFBNEYsQ0FBQTtJQUMvRyxJQUFJLE9BQU8sQ0FB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
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const ordinant_1 = require("../../../../modules/ordinant");
5
+ /**
6
+ * GET /admin/ordinant/stats
7
+ *
8
+ * Proxies the merchant's blocked-order metrics ("stopped illegal orders")
9
+ * from the engine's durable decision log. The dashboard's impact hero renders
10
+ * from this. Scoped server-side to the merchant behind the API key.
11
+ */
12
+ async function GET(req, res) {
13
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
14
+ const stats = await ordinant.client.blockStats();
15
+ res.json(stats);
16
+ }
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L3N0YXRzL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBV0Esa0JBSUM7QUFkRCwyREFBOEQ7QUFHOUQ7Ozs7OztHQU1HO0FBQ0ksS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUErQixFQUFFLEdBQW1CO0lBQzVFLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUF3QiwwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxLQUFLLEdBQUcsTUFBTSxRQUFRLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFBO0lBQ2hELEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUE7QUFDakIsQ0FBQyJ9
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const ordinant_1 = require("../../../../modules/ordinant");
5
+ const client_1 = require("../../../../lib/client");
6
+ /**
7
+ * GET /admin/ordinant/status
8
+ *
9
+ * Lightweight readiness probe for the dashboard. Never throws — a gated or
10
+ * offline state is a valid answer, not an error. `reason` tells the gate which
11
+ * message to show:
12
+ * - "not_activated" — no API key configured at all
13
+ * - "unauthorized" — a key is set but the engine rejected it (401/403)
14
+ * - "unreachable" — configured, but the engine didn't respond
15
+ * - null — healthy
16
+ */
17
+ async function GET(req, res) {
18
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
19
+ if (!ordinant.configured) {
20
+ res.json({
21
+ configured: false,
22
+ engine_reachable: false,
23
+ ruleset_version: null,
24
+ reason: "not_activated",
25
+ });
26
+ return;
27
+ }
28
+ // Configured: probe the engine with the cheapest authenticated call.
29
+ try {
30
+ const summary = await ordinant.client.currentRuleset();
31
+ res.json({
32
+ configured: true,
33
+ engine_reachable: true,
34
+ ruleset_version: summary.version,
35
+ rule_count: summary.rule_count,
36
+ reason: null,
37
+ });
38
+ }
39
+ catch (err) {
40
+ // A key that doesn't authenticate is a different problem (and a different
41
+ // message) than an engine that's simply down.
42
+ const httpStatus = err instanceof client_1.OrdinantApiError ? err.status : undefined;
43
+ const reason = httpStatus === 401 || httpStatus === 403 ? "unauthorized" : "unreachable";
44
+ res.json({
45
+ configured: true,
46
+ engine_reachable: false,
47
+ ruleset_version: null,
48
+ reason,
49
+ });
50
+ }
51
+ }
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L3N0YXR1cy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWdCQSxrQkFtQ0M7QUFsREQsMkRBQThEO0FBRTlELG1EQUF5RDtBQUV6RDs7Ozs7Ozs7OztHQVVHO0FBQ0ksS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUErQixFQUFFLEdBQW1CO0lBQzVFLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUF3QiwwQkFBZSxDQUFDLENBQUE7SUFFMUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUN6QixHQUFHLENBQUMsSUFBSSxDQUFDO1lBQ1AsVUFBVSxFQUFFLEtBQUs7WUFDakIsZ0JBQWdCLEVBQUUsS0FBSztZQUN2QixlQUFlLEVBQUUsSUFBSTtZQUNyQixNQUFNLEVBQUUsZUFBZTtTQUN4QixDQUFDLENBQUE7UUFDRixPQUFNO0lBQ1IsQ0FBQztJQUVELHFFQUFxRTtJQUNyRSxJQUFJLENBQUM7UUFDSCxNQUFNLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQyxNQUFNLENBQUMsY0FBYyxFQUFFLENBQUE7UUFDdEQsR0FBRyxDQUFDLElBQUksQ0FBQztZQUNQLFVBQVUsRUFBRSxJQUFJO1lBQ2hCLGdCQUFnQixFQUFFLElBQUk7WUFDdEIsZUFBZSxFQUFFLE9BQU8sQ0FBQyxPQUFPO1lBQ2hDLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVTtZQUM5QixNQUFNLEVBQUUsSUFBSTtTQUNiLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1FBQ2IsMEVBQTBFO1FBQzFFLDhDQUE4QztRQUM5QyxNQUFNLFVBQVUsR0FBRyxHQUFHLFlBQVkseUJBQWdCLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtRQUMzRSxNQUFNLE1BQU0sR0FBRyxVQUFVLEtBQUssR0FBRyxJQUFJLFVBQVUsS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFBO1FBQ3hGLEdBQUcsQ0FBQyxJQUFJLENBQUM7WUFDUCxVQUFVLEVBQUUsSUFBSTtZQUNoQixnQkFBZ0IsRUFBRSxLQUFLO1lBQ3ZCLGVBQWUsRUFBRSxJQUFJO1lBQ3JCLE1BQU07U0FDUCxDQUFDLENBQUE7SUFDSixDQUFDO0FBQ0gsQ0FBQyJ9
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const http_1 = require("@medusajs/framework/http");
4
+ const validators_1 = require("./store/ordinant/preview/validators");
5
+ exports.default = (0, http_1.defineMiddlewares)({
6
+ routes: [
7
+ {
8
+ matcher: "/store/ordinant/preview",
9
+ method: "POST",
10
+ middlewares: [(0, http_1.validateAndTransformBody)(validators_1.PreviewSchema)],
11
+ },
12
+ ],
13
+ });
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBpL21pZGRsZXdhcmVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbURBQXNGO0FBQ3RGLG9FQUFtRTtBQUVuRSxrQkFBZSxJQUFBLHdCQUFpQixFQUFDO0lBQy9CLE1BQU0sRUFBRTtRQUNOO1lBQ0UsT0FBTyxFQUFFLHlCQUF5QjtZQUNsQyxNQUFNLEVBQUUsTUFBTTtZQUNkLFdBQVcsRUFBRSxDQUFDLElBQUEsK0JBQXdCLEVBQUMsMEJBQWEsQ0FBQyxDQUFDO1NBQ3ZEO0tBQ0Y7Q0FDRixDQUFDLENBQUEifQ==