@porulle/core 0.10.8 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/auth-schema-guard.d.ts +2444 -0
- package/dist/auth/auth-schema-guard.d.ts.map +1 -0
- package/dist/auth/auth-schema-guard.js +113 -0
- package/dist/auth/auth-schema.d.ts +228 -0
- package/dist/auth/auth-schema.d.ts.map +1 -1
- package/dist/auth/auth-schema.js +24 -2
- package/dist/auth/middleware.d.ts +1 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +13 -5
- package/dist/auth/org.d.ts +8 -2
- package/dist/auth/org.d.ts.map +1 -1
- package/dist/auth/org.js +12 -4
- package/dist/auth/organization-guard.d.ts +5 -0
- package/dist/auth/organization-guard.d.ts.map +1 -0
- package/dist/auth/organization-guard.js +124 -0
- package/dist/auth/permissions.d.ts +10 -0
- package/dist/auth/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +31 -7
- package/dist/auth/role-authority.d.ts +44 -0
- package/dist/auth/role-authority.d.ts.map +1 -0
- package/dist/auth/role-authority.js +85 -0
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +17 -0
- package/dist/auth/strict-org-resolution.d.ts.map +1 -1
- package/dist/auth/strict-org-resolution.js +9 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +33 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +10 -9
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
- package/dist/interfaces/rest/audit-middleware.js +2 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/customer-portal.js +9 -8
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +4 -0
- package/dist/interfaces/rest/route-coverage.d.ts +14 -0
- package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
- package/dist/interfaces/rest/route-coverage.js +81 -0
- package/dist/interfaces/rest/router.d.ts +7 -2
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/router.js +8 -4
- package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
- package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/staff.js +126 -66
- package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
- package/dist/interfaces/rest/routes/audit.js +3 -3
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +13 -10
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +47 -4
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +30 -35
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/documents.js +10 -5
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +2 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +7 -3
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +26 -8
- package/dist/interfaces/rest/routes/pricing.js +2 -2
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +13 -10
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +14 -1
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +86 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +1 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.js +1 -0
- package/dist/interfaces/rest/utils.d.ts +15 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.js +45 -2
- package/dist/kernel/compensation/executor.js +2 -2
- package/dist/kernel/database/tx-context.d.ts +8 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -1
- package/dist/kernel/database/tx-context.js +13 -0
- package/dist/kernel/hooks/create-context.d.ts +2 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -1
- package/dist/kernel/hooks/create-context.js +1 -0
- package/dist/kernel/hooks/types.d.ts +2 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +9 -5
- package/dist/modules/analytics/service.js +2 -2
- package/dist/modules/audit/service.js +3 -3
- package/dist/modules/cart/repository/index.d.ts +13 -7
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +16 -10
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +4 -0
- package/dist/modules/cart/service.d.ts +22 -9
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +57 -33
- package/dist/modules/catalog/brand-service.js +7 -7
- package/dist/modules/catalog/category-service.js +7 -7
- package/dist/modules/catalog/entity-service.d.ts +19 -10
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +180 -51
- package/dist/modules/catalog/module.d.ts +6 -1
- package/dist/modules/catalog/module.d.ts.map +1 -1
- package/dist/modules/catalog/module.js +6 -1
- package/dist/modules/catalog/ownership.d.ts +5 -0
- package/dist/modules/catalog/ownership.d.ts.map +1 -0
- package/dist/modules/catalog/ownership.js +11 -0
- package/dist/modules/catalog/read-policy.d.ts +9 -0
- package/dist/modules/catalog/read-policy.d.ts.map +1 -0
- package/dist/modules/catalog/read-policy.js +8 -0
- package/dist/modules/catalog/repository/index.d.ts +55 -3
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +335 -6
- package/dist/modules/catalog/revision.d.ts +4 -0
- package/dist/modules/catalog/revision.d.ts.map +1 -0
- package/dist/modules/catalog/revision.js +33 -0
- package/dist/modules/catalog/schema.d.ts +979 -110
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +112 -2
- package/dist/modules/catalog/schemas.d.ts +2 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +2 -0
- package/dist/modules/catalog/service.d.ts +80 -15
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +522 -14
- package/dist/modules/channels/adapter.d.ts +85 -0
- package/dist/modules/channels/adapter.d.ts.map +1 -1
- package/dist/modules/channels/adapter.js +9 -1
- package/dist/modules/customers/module.d.ts.map +1 -1
- package/dist/modules/customers/module.js +1 -0
- package/dist/modules/customers/service.d.ts +2 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +21 -20
- package/dist/modules/documents/module.d.ts.map +1 -1
- package/dist/modules/documents/module.js +1 -0
- package/dist/modules/documents/service.d.ts +18 -4
- package/dist/modules/documents/service.d.ts.map +1 -1
- package/dist/modules/documents/service.js +54 -16
- package/dist/modules/fulfillment/module.d.ts.map +1 -1
- package/dist/modules/fulfillment/module.js +1 -0
- package/dist/modules/fulfillment/service.d.ts +2 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +15 -6
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +15 -14
- package/dist/modules/media/module.d.ts +68 -0
- package/dist/modules/media/module.d.ts.map +1 -1
- package/dist/modules/media/module.js +2 -0
- package/dist/modules/media/schema.d.ts +70 -0
- package/dist/modules/media/schema.d.ts.map +1 -1
- package/dist/modules/media/schema.js +20 -2
- package/dist/modules/media/service.d.ts +8 -1
- package/dist/modules/media/service.d.ts.map +1 -1
- package/dist/modules/media/service.js +71 -23
- package/dist/modules/orders/guest-access.d.ts +27 -0
- package/dist/modules/orders/guest-access.d.ts.map +1 -0
- package/dist/modules/orders/guest-access.js +46 -0
- package/dist/modules/orders/module.d.ts.map +1 -1
- package/dist/modules/orders/module.js +1 -0
- package/dist/modules/orders/quote.d.ts.map +1 -1
- package/dist/modules/orders/quote.js +1 -0
- package/dist/modules/orders/repository/index.d.ts +1 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +2 -2
- package/dist/modules/orders/schema.d.ts +17 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +5 -3
- package/dist/modules/orders/service.d.ts +16 -7
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +184 -55
- package/dist/modules/pricing/module.d.ts.map +1 -1
- package/dist/modules/pricing/module.js +1 -0
- package/dist/modules/pricing/schema.d.ts +17 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -1
- package/dist/modules/pricing/schema.js +1 -0
- package/dist/modules/pricing/schemas.d.ts +1 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -1
- package/dist/modules/pricing/schemas.js +1 -0
- package/dist/modules/pricing/service.d.ts +2 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +18 -12
- package/dist/modules/promotions/module.d.ts.map +1 -1
- package/dist/modules/promotions/module.js +1 -0
- package/dist/modules/promotions/service.d.ts +3 -0
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +19 -11
- package/dist/modules/search/adapter.d.ts +8 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +3 -0
- package/dist/modules/search/service.d.ts +5 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +131 -11
- package/dist/modules/settings/module.d.ts.map +1 -1
- package/dist/modules/settings/module.js +1 -0
- package/dist/modules/settings/service.d.ts +3 -0
- package/dist/modules/settings/service.d.ts.map +1 -1
- package/dist/modules/settings/service.js +6 -4
- package/dist/modules/shipping/service.js +9 -9
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +1 -0
- package/dist/modules/tax/service.d.ts +3 -0
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +11 -9
- package/dist/modules/webhooks/hook.js +2 -2
- package/dist/modules/webhooks/module.d.ts.map +1 -1
- package/dist/modules/webhooks/module.js +1 -0
- package/dist/modules/webhooks/service.d.ts +2 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -1
- package/dist/modules/webhooks/service.js +4 -4
- package/dist/runtime/kernel-modules.d.ts +73 -0
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +10 -2
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +35 -13
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +4 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +2 -0
- package/dist/test-utils/test-actors.js +2 -2
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +11 -0
- package/package.json +6 -5
- package/src/auth/auth-schema-guard.ts +143 -0
- package/src/auth/auth-schema.ts +50 -19
- package/src/auth/middleware.ts +17 -5
- package/src/auth/org.ts +15 -3
- package/src/auth/organization-guard.ts +158 -0
- package/src/auth/permissions.ts +31 -8
- package/src/auth/role-authority.ts +107 -0
- package/src/auth/setup.ts +17 -0
- package/src/auth/strict-org-resolution.ts +7 -1
- package/src/auth/types.ts +1 -1
- package/src/config/defaults.ts +2 -0
- package/src/config/types.ts +32 -4
- package/src/hooks/checkout.ts +19 -12
- package/src/index.ts +30 -6
- package/src/interfaces/rest/audit-middleware.ts +2 -0
- package/src/interfaces/rest/customer-portal.ts +31 -24
- package/src/interfaces/rest/index.ts +4 -0
- package/src/interfaces/rest/route-coverage.ts +103 -0
- package/src/interfaces/rest/router.ts +16 -10
- package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
- package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
- package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
- package/src/interfaces/rest/routes/admin/staff.ts +161 -72
- package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
- package/src/interfaces/rest/routes/audit.ts +3 -3
- package/src/interfaces/rest/routes/carts.ts +17 -7
- package/src/interfaces/rest/routes/catalog.ts +96 -0
- package/src/interfaces/rest/routes/checkout.ts +41 -38
- package/src/interfaces/rest/routes/documents.ts +15 -1
- package/src/interfaces/rest/routes/inventory.ts +2 -0
- package/src/interfaces/rest/routes/media.ts +8 -3
- package/src/interfaces/rest/routes/orders.ts +27 -8
- package/src/interfaces/rest/routes/pricing.ts +2 -2
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/routes/search.ts +19 -1
- package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
- package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
- package/src/interfaces/rest/schemas/catalog.ts +92 -0
- package/src/interfaces/rest/schemas/search.ts +2 -0
- package/src/interfaces/rest/utils.ts +61 -2
- package/src/kernel/compensation/executor.ts +2 -2
- package/src/kernel/database/tx-context.ts +25 -0
- package/src/kernel/hooks/create-context.ts +3 -0
- package/src/kernel/hooks/types.ts +2 -0
- package/src/kernel/plugin/manifest.ts +9 -5
- package/src/modules/analytics/service.ts +2 -2
- package/src/modules/audit/service.ts +3 -3
- package/src/modules/cart/repository/index.ts +22 -8
- package/src/modules/cart/schema.ts +4 -0
- package/src/modules/cart/service.ts +62 -27
- package/src/modules/catalog/brand-service.ts +7 -7
- package/src/modules/catalog/category-service.ts +7 -7
- package/src/modules/catalog/entity-service.ts +188 -53
- package/src/modules/catalog/module.ts +15 -0
- package/src/modules/catalog/ownership.ts +17 -0
- package/src/modules/catalog/read-policy.ts +16 -0
- package/src/modules/catalog/repository/index.ts +566 -4
- package/src/modules/catalog/revision.ts +35 -0
- package/src/modules/catalog/schema.ts +169 -1
- package/src/modules/catalog/schemas.ts +2 -0
- package/src/modules/catalog/service.ts +766 -26
- package/src/modules/channels/adapter.ts +107 -1
- package/src/modules/customers/module.ts +2 -0
- package/src/modules/customers/service.ts +23 -22
- package/src/modules/documents/module.ts +2 -0
- package/src/modules/documents/service.ts +74 -11
- package/src/modules/fulfillment/module.ts +2 -0
- package/src/modules/fulfillment/service.ts +17 -6
- package/src/modules/inventory/service.ts +15 -24
- package/src/modules/media/module.ts +2 -0
- package/src/modules/media/schema.ts +23 -2
- package/src/modules/media/service.ts +89 -28
- package/src/modules/orders/guest-access.ts +55 -0
- package/src/modules/orders/module.ts +1 -0
- package/src/modules/orders/quote.ts +1 -0
- package/src/modules/orders/repository/index.ts +2 -0
- package/src/modules/orders/schema.ts +5 -3
- package/src/modules/orders/service.ts +302 -52
- package/src/modules/pricing/module.ts +2 -0
- package/src/modules/pricing/schema.ts +1 -0
- package/src/modules/pricing/schemas.ts +1 -0
- package/src/modules/pricing/service.ts +21 -18
- package/src/modules/promotions/module.ts +2 -0
- package/src/modules/promotions/service.ts +25 -16
- package/src/modules/search/adapter.ts +8 -0
- package/src/modules/search/module.ts +3 -0
- package/src/modules/search/service.ts +142 -12
- package/src/modules/settings/module.ts +2 -0
- package/src/modules/settings/service.ts +8 -4
- package/src/modules/shipping/service.ts +9 -9
- package/src/modules/tax/module.ts +1 -0
- package/src/modules/tax/service.ts +13 -9
- package/src/modules/webhooks/hook.ts +2 -2
- package/src/modules/webhooks/module.ts +2 -0
- package/src/modules/webhooks/service.ts +6 -4
- package/src/runtime/server.ts +16 -2
- package/src/test-utils/create-pglite-adapter.ts +34 -12
- package/src/test-utils/create-test-config.ts +10 -6
- package/src/test-utils/rest-api-test-utils.ts +2 -0
- package/src/test-utils/test-actors.ts +2 -2
- package/src/utils/id.ts +20 -0
|
@@ -22,8 +22,8 @@ import type { PluginDb } from "../../../kernel/database/plugin-types.js";
|
|
|
22
22
|
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
23
23
|
import { isCommerceError } from "../../../kernel/errors.js";
|
|
24
24
|
import { assertPermission } from "../../../auth/permissions.js";
|
|
25
|
-
import {
|
|
26
|
-
import { makeDeterministicId, makeId } from "../../../utils/id.js";
|
|
25
|
+
import { resolveOrgIdForCommerce } from "../../../auth/org.js";
|
|
26
|
+
import { makeDeterministicId, makeId, makeIdempotencyScope } from "../../../utils/id.js";
|
|
27
27
|
import type { ShippingAddress } from "../../../modules/shipping/calculator.js";
|
|
28
28
|
import type { Actor } from "../../../auth/types.js";
|
|
29
29
|
|
|
@@ -85,50 +85,48 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
85
85
|
const body = c.req.valid("json");
|
|
86
86
|
|
|
87
87
|
const actor = c.get("actor");
|
|
88
|
+
const cartSecret = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
88
89
|
|
|
89
|
-
// Idempotent replay
|
|
90
|
-
//
|
|
91
|
-
//
|
|
90
|
+
// Idempotent replay is authorized by the order service before payment runs.
|
|
91
|
+
// This preflight only preserves the no-double-payment fast path; the create
|
|
92
|
+
// primitive repeats the same binding for direct callers and race losers.
|
|
93
|
+
let replay: Awaited<ReturnType<typeof kernel.services.orders.getByIdempotencyKey>> | undefined;
|
|
92
94
|
if (body.idempotencyKey) {
|
|
93
|
-
|
|
95
|
+
replay = await kernel.services.orders.getByIdempotencyKey(
|
|
94
96
|
body.idempotencyKey,
|
|
95
97
|
actor,
|
|
98
|
+
undefined,
|
|
99
|
+
cartSecret,
|
|
96
100
|
);
|
|
97
|
-
if (replay.ok
|
|
98
|
-
|
|
99
|
-
// order. Without this, a same-org customer who supplies/guesses another
|
|
100
|
-
// customer's key would receive that customer's order (PII, totals, lines).
|
|
101
|
-
let canActForOthers = false;
|
|
102
|
-
try {
|
|
103
|
-
assertPermission(actor, "customers:read");
|
|
104
|
-
canActForOthers = true;
|
|
105
|
-
} catch {
|
|
106
|
-
canActForOthers = false;
|
|
107
|
-
}
|
|
108
|
-
const ownCustomer = actor
|
|
109
|
-
? await resolveCheckoutCustomerUuid(kernel.services.customers, actor, undefined)
|
|
110
|
-
: undefined;
|
|
111
|
-
const replayCustomer = replay.value.customerId ?? null;
|
|
112
|
-
const ownsOrder =
|
|
113
|
-
(replayCustomer !== null && replayCustomer === ownCustomer) ||
|
|
114
|
-
(replayCustomer === null && !ownCustomer);
|
|
115
|
-
if (canActForOthers || ownsOrder) {
|
|
116
|
-
return c.json({ data: replay.value }, 201);
|
|
117
|
-
}
|
|
118
|
-
return c.json(
|
|
119
|
-
{
|
|
120
|
-
error: {
|
|
121
|
-
code: "IDEMPOTENCY_CONFLICT",
|
|
122
|
-
message: "Idempotency key does not belong to this account.",
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
409,
|
|
126
|
-
);
|
|
101
|
+
if (!replay.ok) {
|
|
102
|
+
return c.json(mapErrorToResponse(replay.error), mapErrorToStatus(replay.error));
|
|
127
103
|
}
|
|
104
|
+
if (replay.value) return c.json({ data: replay.value }, 201);
|
|
128
105
|
}
|
|
129
106
|
|
|
107
|
+
// Authenticate the cart once at the HTTP boundary. Guest checkout is
|
|
108
|
+
// authorized by the cart secret; customer checkout is authorized by the
|
|
109
|
+
// customer cart ownership check. The hook pipeline uses its explicit
|
|
110
|
+
// internal cart-read path instead of carrying this credential inward.
|
|
111
|
+
const cartAccess = await kernel.services.cart.getById(
|
|
112
|
+
body.cartId,
|
|
113
|
+
actor,
|
|
114
|
+
undefined,
|
|
115
|
+
cartSecret,
|
|
116
|
+
);
|
|
117
|
+
if (!cartAccess.ok) {
|
|
118
|
+
return c.json(
|
|
119
|
+
mapErrorToResponse(cartAccess.error),
|
|
120
|
+
mapErrorToStatus(cartAccess.error),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const idempotencyScope = body.idempotencyKey
|
|
124
|
+
? await makeIdempotencyScope(actor, cartSecret, body.cartId)
|
|
125
|
+
: undefined;
|
|
130
126
|
const orderId = body.idempotencyKey
|
|
131
|
-
? await makeDeterministicId(
|
|
127
|
+
? await makeDeterministicId(
|
|
128
|
+
`checkout:${resolveOrgIdForCommerce(actor, kernel.config)}:${idempotencyScope}:${body.idempotencyKey}`,
|
|
129
|
+
)
|
|
132
130
|
: makeId();
|
|
133
131
|
const checkoutData: CheckoutData = {
|
|
134
132
|
checkoutId: makeId(),
|
|
@@ -203,6 +201,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
203
201
|
context: { moduleName: "checkout" },
|
|
204
202
|
origin: "rest",
|
|
205
203
|
database: { db: kernel.database.db as PluginDb },
|
|
204
|
+
commerceConfig: kernel.config,
|
|
206
205
|
});
|
|
207
206
|
|
|
208
207
|
let pipelineStage = "validate-and-calculate";
|
|
@@ -293,6 +292,7 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
293
292
|
pipelineStage = "create-order";
|
|
294
293
|
const order = await kernel.services.orders.create(orderPayload, actor, undefined, {
|
|
295
294
|
trustedPricing: true,
|
|
295
|
+
...(cartSecret !== undefined ? { guestCredential: cartSecret } : {}),
|
|
296
296
|
});
|
|
297
297
|
|
|
298
298
|
if (!order.ok) {
|
|
@@ -379,7 +379,10 @@ export function checkoutRoutes(kernel: Kernel) {
|
|
|
379
379
|
// Validation, payment authorization, or compensated completion may fail
|
|
380
380
|
// after the cart's atomic active -> checking_out claim. Release only
|
|
381
381
|
// that transient state so the shopper can correct the problem and retry.
|
|
382
|
-
|
|
382
|
+
// Releasing is scoped to this attempt's claim token: an attempt that lost
|
|
383
|
+
// the claim never held it, so its failure cannot reopen a cart another
|
|
384
|
+
// checkout is still working through.
|
|
385
|
+
await kernel.services.cart.releaseCheckoutClaim(body.cartId, checkoutData.checkoutId).catch(
|
|
383
386
|
(releaseError: unknown) => {
|
|
384
387
|
console.error("[checkout] Failed to release cart claim:", {
|
|
385
388
|
cartId: body.cartId,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
invoicePdfRoute,
|
|
7
7
|
receiptHtmlRoute,
|
|
8
8
|
} from "../schemas/documents.js";
|
|
9
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
9
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requireAnyPerm } from "../utils.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Order document rendering (issue #47). Mounted under /orders. Access
|
|
@@ -16,10 +16,15 @@ import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
|
16
16
|
export function documentRoutes(kernel: Kernel) {
|
|
17
17
|
const router = new OpenAPIHono<AppEnv>();
|
|
18
18
|
|
|
19
|
+
router.use("/*", requireAnyPerm(["orders:read", "orders:read:own"]));
|
|
20
|
+
|
|
19
21
|
router.openapi(invoicePdfRoute, async (c) => {
|
|
22
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
20
23
|
const result = await kernel.services.documents.renderInvoicePdf(
|
|
21
24
|
c.req.param("id"),
|
|
22
25
|
c.get("actor"),
|
|
26
|
+
undefined,
|
|
27
|
+
guestCredential,
|
|
23
28
|
);
|
|
24
29
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
25
30
|
return c.body(result.value.pdf.slice().buffer as ArrayBuffer, 200, {
|
|
@@ -29,18 +34,24 @@ export function documentRoutes(kernel: Kernel) {
|
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
router.openapi(invoiceHtmlRoute, async (c) => {
|
|
37
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
32
38
|
const result = await kernel.services.documents.renderInvoiceHtml(
|
|
33
39
|
c.req.param("id"),
|
|
34
40
|
c.get("actor"),
|
|
41
|
+
undefined,
|
|
42
|
+
guestCredential,
|
|
35
43
|
);
|
|
36
44
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
37
45
|
return c.html(result.value.html);
|
|
38
46
|
});
|
|
39
47
|
|
|
40
48
|
router.openapi(receiptHtmlRoute, async (c) => {
|
|
49
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
41
50
|
const result = await kernel.services.documents.renderReceiptHtml(
|
|
42
51
|
c.req.param("id"),
|
|
43
52
|
c.get("actor"),
|
|
53
|
+
undefined,
|
|
54
|
+
guestCredential,
|
|
44
55
|
);
|
|
45
56
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
46
57
|
return c.html(result.value.html);
|
|
@@ -48,10 +59,13 @@ export function documentRoutes(kernel: Kernel) {
|
|
|
48
59
|
|
|
49
60
|
// @ts-expect-error -- openapi handler union return type
|
|
50
61
|
router.openapi(emailInvoiceRoute, async (c) => {
|
|
62
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
51
63
|
const result = await kernel.services.documents.emailInvoice(
|
|
52
64
|
c.req.param("id"),
|
|
53
65
|
c.req.valid("json").to,
|
|
54
66
|
c.get("actor"),
|
|
67
|
+
undefined,
|
|
68
|
+
guestCredential,
|
|
55
69
|
);
|
|
56
70
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
57
71
|
return c.json({ data: result.value });
|
|
@@ -24,6 +24,8 @@ export function inventoryRoutes(kernel: Kernel) {
|
|
|
24
24
|
router.use("/warehouses", requirePerm("inventory:read"));
|
|
25
25
|
router.use("/reserve", requirePerm("inventory:adjust"));
|
|
26
26
|
router.use("/release", requirePerm("inventory:adjust"));
|
|
27
|
+
router.use("/check", requirePerm("inventory:read"));
|
|
28
|
+
router.use("/adjust", requirePerm("inventory:adjust"));
|
|
27
29
|
|
|
28
30
|
// @ts-expect-error -- openapi handler union return type
|
|
29
31
|
router.openapi(listInventoryLevelsRoute, async (c) => {
|
|
@@ -2,13 +2,14 @@ import { OpenAPIHono } from "@hono/zod-openapi";
|
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import type { AttachMediaInput } from "../../../modules/media/service.js";
|
|
4
4
|
import { attachMediaRoute, getMediaRoute, deleteMediaRoute } from "../schemas/media.js";
|
|
5
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
|
|
5
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requireMethodPerm, requirePerm } from "../utils.js";
|
|
6
6
|
|
|
7
7
|
export function mediaRoutes(kernel: Kernel) {
|
|
8
8
|
const router = new OpenAPIHono<AppEnv>();
|
|
9
9
|
|
|
10
10
|
// Upload requires authentication + media:write permission
|
|
11
11
|
router.use("/upload", requirePerm("media:write"));
|
|
12
|
+
router.use("/:id", requireMethodPerm(["DELETE"], "media:write"));
|
|
12
13
|
|
|
13
14
|
router.post("/upload", async (c) => {
|
|
14
15
|
// No storage configured (the default no-op adapter) → media is disabled.
|
|
@@ -43,16 +44,20 @@ export function mediaRoutes(kernel: Kernel) {
|
|
|
43
44
|
|
|
44
45
|
router.openapi(getMediaRoute, async (c) => {
|
|
45
46
|
const signed = c.req.query("signed") === "true";
|
|
47
|
+
const actor = c.get("actor");
|
|
48
|
+
|
|
49
|
+
if (!actor && !signed) {
|
|
50
|
+
return c.json({ error: { code: "NOT_FOUND", message: "Media not found." } }, 404);
|
|
51
|
+
}
|
|
46
52
|
|
|
47
53
|
// Signed URLs require authentication
|
|
48
|
-
if (signed && !
|
|
54
|
+
if (signed && !actor) {
|
|
49
55
|
return c.json(
|
|
50
56
|
{ error: { code: "UNAUTHORIZED", message: "Authentication required for signed URLs." } },
|
|
51
57
|
401,
|
|
52
58
|
);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
const actor = c.get("actor");
|
|
56
61
|
const result = signed
|
|
57
62
|
? await kernel.services.media.getSignedUrl(c.req.param("id"), undefined, actor)
|
|
58
63
|
: await kernel.services.media.getUrl(c.req.param("id"), actor);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import { changeOrderStatusRoute, listOrdersRoute, orderLookupRoute, getOrderRoute, getOrderFulfillmentsRoute, createOrderRoute, quoteOrderRoute, refundOrderRoute, captureOrderRoute, createOrderFulfillmentRoute, addOrderLineItemRoute, updateOrderLineItemRoute, removeOrderLineItemRoute, refundOrderLinesRoute, undoOrderRefundRoute, listOrderRefundsRoute, refundCapStatusRoute, createOrderNoteRoute, listOrderNotesRoute, deleteOrderNoteRoute, orderTimelineRoute } from "../schemas/orders.js";
|
|
4
|
-
import { type AppEnv, isUUID, mapErrorToResponse, mapErrorToStatus, parsePagination } from "../utils.js";
|
|
4
|
+
import { type AppEnv, isUUID, mapErrorToResponse, mapErrorToStatus, parsePagination, requireAnyPerm, requireMethodPerm, requirePerm } from "../utils.js";
|
|
5
5
|
import type { CreateOrderInput } from "../../../modules/orders/service.js";
|
|
6
6
|
import { computeOrderPricing } from "../../../modules/orders/quote.js";
|
|
7
7
|
import { assertPermission } from "../../../auth/permissions.js";
|
|
@@ -9,6 +9,13 @@ import { assertPermission } from "../../../auth/permissions.js";
|
|
|
9
9
|
export function orderRoutes(kernel: Kernel) {
|
|
10
10
|
const router = new OpenAPIHono<AppEnv>();
|
|
11
11
|
|
|
12
|
+
router.use("/", requireMethodPerm(["GET"], "orders:read"));
|
|
13
|
+
router.use("/", requireMethodPerm(["POST"], "orders:create"));
|
|
14
|
+
router.use("/lookup", requirePerm("orders:read"));
|
|
15
|
+
router.use("/:idOrNumber", requireAnyPerm(["orders:read", "orders:read:own"]));
|
|
16
|
+
router.use("/:id/*", requireAnyPerm(["orders:read", "orders:read:own", "orders:update", "orders:manage"]));
|
|
17
|
+
router.use("/refunds/cap", requirePerm("orders:read"));
|
|
18
|
+
|
|
12
19
|
// @ts-expect-error -- openapi handler union return type
|
|
13
20
|
router.openapi(listOrdersRoute, async (c) => {
|
|
14
21
|
const pagination = parsePagination(c.req.query());
|
|
@@ -54,9 +61,10 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
54
61
|
// @ts-expect-error -- openapi handler union return type
|
|
55
62
|
router.openapi(getOrderRoute, async (c) => {
|
|
56
63
|
const idOrNumber = c.req.param("idOrNumber");
|
|
64
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
57
65
|
const result = isUUID(idOrNumber)
|
|
58
|
-
? await kernel.services.orders.getById(idOrNumber, c.get("actor"))
|
|
59
|
-
: await kernel.services.orders.getByNumber(idOrNumber, c.get("actor"));
|
|
66
|
+
? await kernel.services.orders.getById(idOrNumber, c.get("actor"), undefined, guestCredential)
|
|
67
|
+
: await kernel.services.orders.getByNumber(idOrNumber, c.get("actor"), undefined, guestCredential);
|
|
60
68
|
|
|
61
69
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
62
70
|
return c.json({ data: result.value });
|
|
@@ -65,12 +73,15 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
65
73
|
// @ts-expect-error -- openapi handler union return type
|
|
66
74
|
router.openapi(createOrderRoute, async (c) => {
|
|
67
75
|
try {
|
|
68
|
-
assertPermission(c.get("actor"), "orders:
|
|
76
|
+
assertPermission(c.get("actor"), "orders:create");
|
|
69
77
|
} catch (error) {
|
|
70
78
|
return c.json(mapErrorToResponse(error), mapErrorToStatus(error));
|
|
71
79
|
}
|
|
72
80
|
const body = c.req.valid("json") as CreateOrderInput;
|
|
73
|
-
const
|
|
81
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
82
|
+
const result = await kernel.services.orders.create(body, c.get("actor"), undefined, {
|
|
83
|
+
...(guestCredential !== undefined ? { guestCredential } : {}),
|
|
84
|
+
});
|
|
74
85
|
if (!result.ok) return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
75
86
|
return c.json({ data: result.value }, 201);
|
|
76
87
|
});
|
|
@@ -273,8 +284,15 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
273
284
|
const actor = c.get("actor");
|
|
274
285
|
const orderId = c.req.param("id");
|
|
275
286
|
|
|
287
|
+
try {
|
|
288
|
+
assertPermission(actor, "orders:update");
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return c.json(mapErrorToResponse(error), mapErrorToStatus(error));
|
|
291
|
+
}
|
|
292
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
293
|
+
|
|
276
294
|
// Verify the order exists and the actor has access before recording
|
|
277
|
-
const orderResult = await kernel.services.orders.getById(orderId, actor);
|
|
295
|
+
const orderResult = await kernel.services.orders.getById(orderId, actor, undefined, guestCredential);
|
|
278
296
|
if (!orderResult.ok) return c.json(mapErrorToResponse(orderResult.error), mapErrorToStatus(orderResult.error));
|
|
279
297
|
|
|
280
298
|
const body = c.req.valid("json") as {
|
|
@@ -298,11 +316,12 @@ export function orderRoutes(kernel: Kernel) {
|
|
|
298
316
|
router.openapi(getOrderFulfillmentsRoute, async (c) => {
|
|
299
317
|
const orderId = c.req.param("id");
|
|
300
318
|
const actor = c.get("actor");
|
|
319
|
+
const guestCredential = c.req.header("x-cart-secret") ?? c.req.header("X-Cart-Secret") ?? undefined;
|
|
301
320
|
|
|
302
321
|
// Verify the order exists and the actor has access before returning fulfillments
|
|
303
322
|
const orderResult = isUUID(orderId)
|
|
304
|
-
? await kernel.services.orders.getById(orderId, actor)
|
|
305
|
-
: await kernel.services.orders.getByNumber(orderId, actor);
|
|
323
|
+
? await kernel.services.orders.getById(orderId, actor, undefined, guestCredential)
|
|
324
|
+
: await kernel.services.orders.getByNumber(orderId, actor, undefined, guestCredential);
|
|
306
325
|
|
|
307
326
|
if (!orderResult.ok) return c.json(mapErrorToResponse(orderResult.error), mapErrorToStatus(orderResult.error));
|
|
308
327
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveOrgIdForCommerce } from "../../../auth/org.js";
|
|
3
3
|
import { CommerceValidationError } from "../../../kernel/errors.js";
|
|
4
4
|
import type { DrizzleDatabase } from "../../../kernel/database/drizzle-db.js";
|
|
5
5
|
import { CatalogRepository } from "../../../modules/catalog/repository/index.js";
|
|
@@ -98,7 +98,7 @@ export function pricingRoutes(kernel: Kernel) {
|
|
|
98
98
|
|
|
99
99
|
if (input.entityId) {
|
|
100
100
|
const catalogRepo = new CatalogRepository(kernel.database.db as DrizzleDatabase);
|
|
101
|
-
const orgId =
|
|
101
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
102
102
|
const entity = await catalogRepo.findEntityById(input.entityId);
|
|
103
103
|
if (!entity || entity.organizationId !== orgId) {
|
|
104
104
|
return c.json(
|
|
@@ -3,8 +3,9 @@ import { rateLimiter } from "hono-rate-limiter";
|
|
|
3
3
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
4
4
|
import { createPromotionRoute, updatePromotionRoute, validatePromotionRoute, deactivatePromotionRoute, listPromotionsRoute } from "../schemas/promotions.js";
|
|
5
5
|
import type { PromotionStatusFilter } from "../../../modules/promotions/service.js";
|
|
6
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
|
|
7
|
-
import {
|
|
6
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requireMethodPerm, requirePerm } from "../utils.js";
|
|
7
|
+
import { resolveOrgIdForCommerce } from "../../../auth/org.js";
|
|
8
|
+
import { assertPermission } from "../../../auth/permissions.js";
|
|
8
9
|
|
|
9
10
|
export function promotionRoutes(kernel: Kernel) {
|
|
10
11
|
const router = new OpenAPIHono<AppEnv>();
|
|
@@ -59,7 +60,7 @@ export function promotionRoutes(kernel: Kernel) {
|
|
|
59
60
|
router.openapi(validatePromotionRoute, async (c) => {
|
|
60
61
|
const payload = c.req.valid("json");
|
|
61
62
|
const actor = c.get("actor");
|
|
62
|
-
const orgId =
|
|
63
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
63
64
|
|
|
64
65
|
// Apply (not just validate) so the response carries the authoritative
|
|
65
66
|
// discount the cart would receive — the same computation checkout runs.
|
|
@@ -84,10 +85,7 @@ export function promotionRoutes(kernel: Kernel) {
|
|
|
84
85
|
|
|
85
86
|
// Guard inline (not via router.use("/:id")) because a "/:id" middleware
|
|
86
87
|
// would also match single-segment routes like POST /validate.
|
|
87
|
-
router.use("/:id",
|
|
88
|
-
if (c.req.method !== "PATCH") return next();
|
|
89
|
-
return requirePerm("promotions:manage")(c, next);
|
|
90
|
-
});
|
|
88
|
+
router.use("/:id", requireMethodPerm(["PATCH"], "promotions:manage"));
|
|
91
89
|
|
|
92
90
|
// @ts-expect-error -- openapi() enforces strict response typing but our handler
|
|
93
91
|
// returns union responses (200 | 400 | 404 | 422). The route definition
|
|
@@ -95,7 +93,12 @@ export function promotionRoutes(kernel: Kernel) {
|
|
|
95
93
|
router.openapi(updatePromotionRoute, async (c) => {
|
|
96
94
|
const body = c.req.valid("json");
|
|
97
95
|
const actor = c.get("actor");
|
|
98
|
-
|
|
96
|
+
try {
|
|
97
|
+
assertPermission(actor, "promotions:manage");
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return c.json(mapErrorToResponse(error), mapErrorToStatus(error));
|
|
100
|
+
}
|
|
101
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
99
102
|
const result = await kernel.services.promotions.update(orgId, c.req.param("id"), body, actor);
|
|
100
103
|
if (!result.ok) {
|
|
101
104
|
return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
@@ -110,7 +113,7 @@ export function promotionRoutes(kernel: Kernel) {
|
|
|
110
113
|
// contract; the handler returns dynamic status.
|
|
111
114
|
router.openapi(deactivatePromotionRoute, async (c) => {
|
|
112
115
|
const actor = c.get("actor");
|
|
113
|
-
const orgId =
|
|
116
|
+
const orgId = resolveOrgIdForCommerce(actor, kernel.config);
|
|
114
117
|
const result = await kernel.services.promotions.deactivate(orgId, c.req.param("id"));
|
|
115
118
|
if (!result.ok) {
|
|
116
119
|
return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import type { Kernel } from "../../../runtime/kernel.js";
|
|
3
3
|
import { searchRoute, suggestRoute } from "../schemas/search.js";
|
|
4
|
-
import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
|
|
4
|
+
import { type AppEnv, mapErrorToResponse, mapErrorToStatus, requirePerm } from "../utils.js";
|
|
5
|
+
|
|
6
|
+
const SAFE_ATTRIBUTE_NAME = /^[A-Za-z0-9_-]+$/;
|
|
5
7
|
|
|
6
8
|
export function searchRoutes(kernel: Kernel) {
|
|
7
9
|
const router = new OpenAPIHono<AppEnv>();
|
|
8
10
|
|
|
11
|
+
router.use("/*", requirePerm("catalog:read"));
|
|
12
|
+
|
|
9
13
|
// @ts-expect-error -- openapi handler union return type
|
|
10
14
|
router.openapi(searchRoute, async (c) => {
|
|
11
15
|
try {
|
|
@@ -21,6 +25,19 @@ export function searchRoutes(kernel: Kernel) {
|
|
|
21
25
|
.map((facet) => facet.trim())
|
|
22
26
|
.filter(Boolean);
|
|
23
27
|
|
|
28
|
+
const attributes: Record<string, string | string[]> = {};
|
|
29
|
+
for (const [key, values] of Object.entries(c.req.queries())) {
|
|
30
|
+
if (!key.startsWith("attr.")) continue;
|
|
31
|
+
const name = key.slice("attr.".length);
|
|
32
|
+
if (!SAFE_ATTRIBUTE_NAME.test(name)) {
|
|
33
|
+
return c.json(
|
|
34
|
+
{ error: "invalid_argument", field: key, message: `Invalid attribute filter name: ${name}` },
|
|
35
|
+
422,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
attributes[name] = values.length === 1 ? values[0]! : values;
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
const result = await kernel.services.search.query({
|
|
25
42
|
query: q,
|
|
26
43
|
page,
|
|
@@ -30,6 +47,7 @@ export function searchRoutes(kernel: Kernel) {
|
|
|
30
47
|
...(category ? { category } : {}),
|
|
31
48
|
...(brand ? { brand } : {}),
|
|
32
49
|
...(status ? { status } : {}),
|
|
50
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
33
51
|
},
|
|
34
52
|
...(facets.length > 0 ? { facets } : {}),
|
|
35
53
|
}, { actor: c.get("actor"), tx: null, requestId: "" });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
import { errorResponses } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
const ProposalResponse = z.object({
|
|
5
|
+
data: z.object({
|
|
6
|
+
items: z.array(z.any()),
|
|
7
|
+
total: z.number(),
|
|
8
|
+
page: z.number(),
|
|
9
|
+
limit: z.number(),
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const ListQuery = z.object({
|
|
14
|
+
entityType: z.string().min(1).optional(),
|
|
15
|
+
page: z.string().max(10).optional(),
|
|
16
|
+
limit: z.string().max(10).optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const listCustomFieldProposalsRoute = createRoute({
|
|
20
|
+
method: "get",
|
|
21
|
+
path: "/custom-field-proposals",
|
|
22
|
+
tags: ["Admin"],
|
|
23
|
+
summary: "List proposed custom-field values",
|
|
24
|
+
request: { query: ListQuery },
|
|
25
|
+
responses: {
|
|
26
|
+
200: { content: { "application/json": { schema: ProposalResponse } }, description: "Proposed custom fields." },
|
|
27
|
+
...errorResponses,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
import { errorResponses } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
const FieldType = z.enum([
|
|
5
|
+
"text",
|
|
6
|
+
"number",
|
|
7
|
+
"boolean",
|
|
8
|
+
"date",
|
|
9
|
+
"json",
|
|
10
|
+
"relation",
|
|
11
|
+
"select",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const CreateBody = z
|
|
15
|
+
.object({
|
|
16
|
+
entityType: z.string().min(1),
|
|
17
|
+
name: z.string().min(1),
|
|
18
|
+
type: FieldType,
|
|
19
|
+
unit: z.string().nullable().optional(),
|
|
20
|
+
options: z.array(z.string()).nullable().optional(),
|
|
21
|
+
target: z.string().nullable().optional(),
|
|
22
|
+
filterable: z.boolean().optional(),
|
|
23
|
+
localized: z.boolean().optional(),
|
|
24
|
+
sortOrder: z.number().int().optional(),
|
|
25
|
+
})
|
|
26
|
+
.openapi("CreateEntityFieldDefinitionRequest");
|
|
27
|
+
|
|
28
|
+
const UpdateBody = z
|
|
29
|
+
.object({
|
|
30
|
+
entityType: z.string().min(1).optional(),
|
|
31
|
+
name: z.string().min(1).optional(),
|
|
32
|
+
type: FieldType.optional(),
|
|
33
|
+
unit: z.string().nullable().optional(),
|
|
34
|
+
options: z.array(z.string()).nullable().optional(),
|
|
35
|
+
target: z.string().nullable().optional(),
|
|
36
|
+
filterable: z.boolean().optional(),
|
|
37
|
+
localized: z.boolean().optional(),
|
|
38
|
+
sortOrder: z.number().int().optional(),
|
|
39
|
+
})
|
|
40
|
+
.openapi("UpdateEntityFieldDefinitionRequest");
|
|
41
|
+
|
|
42
|
+
const IdParam = z.object({ id: z.uuid() });
|
|
43
|
+
const ListQuery = z.object({ entityType: z.string().min(1).optional() });
|
|
44
|
+
const DataResponse = z
|
|
45
|
+
.object({ data: z.any() })
|
|
46
|
+
.openapi("EntityFieldDefinitionResponse");
|
|
47
|
+
|
|
48
|
+
export const createEntityFieldDefinitionRoute = createRoute({
|
|
49
|
+
method: "post",
|
|
50
|
+
path: "/entity-field-definitions",
|
|
51
|
+
tags: ["Admin"],
|
|
52
|
+
summary: "Create a runtime entity field definition",
|
|
53
|
+
request: {
|
|
54
|
+
body: {
|
|
55
|
+
content: { "application/json": { schema: CreateBody } },
|
|
56
|
+
required: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
responses: {
|
|
60
|
+
201: {
|
|
61
|
+
content: { "application/json": { schema: DataResponse } },
|
|
62
|
+
description: "Definition created.",
|
|
63
|
+
},
|
|
64
|
+
...errorResponses,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const listEntityFieldDefinitionsRoute = createRoute({
|
|
69
|
+
method: "get",
|
|
70
|
+
path: "/entity-field-definitions",
|
|
71
|
+
tags: ["Admin"],
|
|
72
|
+
summary: "List runtime entity field definitions",
|
|
73
|
+
request: { query: ListQuery },
|
|
74
|
+
responses: {
|
|
75
|
+
200: {
|
|
76
|
+
content: { "application/json": { schema: DataResponse } },
|
|
77
|
+
description: "Definitions.",
|
|
78
|
+
},
|
|
79
|
+
...errorResponses,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const updateEntityFieldDefinitionRoute = createRoute({
|
|
84
|
+
method: "patch",
|
|
85
|
+
path: "/entity-field-definitions/{id}",
|
|
86
|
+
tags: ["Admin"],
|
|
87
|
+
summary: "Update a runtime entity field definition",
|
|
88
|
+
request: {
|
|
89
|
+
params: IdParam,
|
|
90
|
+
body: {
|
|
91
|
+
content: { "application/json": { schema: UpdateBody } },
|
|
92
|
+
required: true,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
responses: {
|
|
96
|
+
200: {
|
|
97
|
+
content: { "application/json": { schema: DataResponse } },
|
|
98
|
+
description: "Definition updated.",
|
|
99
|
+
},
|
|
100
|
+
...errorResponses,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const archiveEntityFieldDefinitionRoute = createRoute({
|
|
105
|
+
method: "post",
|
|
106
|
+
path: "/entity-field-definitions/{id}/archive",
|
|
107
|
+
tags: ["Admin"],
|
|
108
|
+
summary: "Archive a runtime entity field definition",
|
|
109
|
+
request: { params: IdParam },
|
|
110
|
+
responses: {
|
|
111
|
+
200: {
|
|
112
|
+
content: { "application/json": { schema: DataResponse } },
|
|
113
|
+
description: "Definition archived.",
|
|
114
|
+
},
|
|
115
|
+
...errorResponses,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export type CreateEntityFieldDefinitionBody = z.infer<typeof CreateBody>;
|
|
120
|
+
export type UpdateEntityFieldDefinitionBody = z.infer<typeof UpdateBody>;
|