@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import { assertPermission } from "../../auth/permissions.js";
|
|
3
3
|
import type { Actor } from "../../auth/types.js";
|
|
4
4
|
import type { CommerceConfig } from "../../config/types.js";
|
|
@@ -57,7 +57,7 @@ export class InventoryService {
|
|
|
57
57
|
): Promise<string> {
|
|
58
58
|
const orgId =
|
|
59
59
|
orgIdOverride ??
|
|
60
|
-
|
|
60
|
+
resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
61
61
|
const warehouses = await this.repo.findAllWarehouses(orgId, ctx);
|
|
62
62
|
const sorted = warehouses.sort((a, b) => a.priority - b.priority);
|
|
63
63
|
if (sorted.length > 0) {
|
|
@@ -90,9 +90,8 @@ export class InventoryService {
|
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const orgId =
|
|
93
|
+
const orgId = resolveOrgIdForCommerce(
|
|
94
94
|
actor ?? ctx?.actor ?? null,
|
|
95
|
-
undefined,
|
|
96
95
|
this.deps.config,
|
|
97
96
|
);
|
|
98
97
|
|
|
@@ -113,9 +112,8 @@ export class InventoryService {
|
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
async listWarehouses(actor?: Actor | null, ctx?: TxContext): Promise<Result<Warehouse[]>> {
|
|
116
|
-
const orgId =
|
|
115
|
+
const orgId = resolveOrgIdForCommerce(
|
|
117
116
|
actor ?? ctx?.actor ?? null,
|
|
118
|
-
undefined,
|
|
119
117
|
this.deps.config,
|
|
120
118
|
);
|
|
121
119
|
const warehouses = await this.repo.findAllWarehouses(orgId, ctx);
|
|
@@ -128,9 +126,8 @@ export class InventoryService {
|
|
|
128
126
|
ctx?: TxContext,
|
|
129
127
|
actor?: Actor | null,
|
|
130
128
|
): Promise<Result<number>> {
|
|
131
|
-
const orgId =
|
|
129
|
+
const orgId = resolveOrgIdForCommerce(
|
|
132
130
|
actor ?? ctx?.actor ?? null,
|
|
133
|
-
undefined,
|
|
134
131
|
this.deps.config,
|
|
135
132
|
);
|
|
136
133
|
const available = await this.repo.getAvailableQuantity(
|
|
@@ -147,9 +144,8 @@ export class InventoryService {
|
|
|
147
144
|
actor?: Actor | null,
|
|
148
145
|
ctx?: TxContext,
|
|
149
146
|
): Promise<Result<InventoryLevel[]>> {
|
|
150
|
-
const orgId =
|
|
147
|
+
const orgId = resolveOrgIdForCommerce(
|
|
151
148
|
actor ?? ctx?.actor ?? null,
|
|
152
|
-
undefined,
|
|
153
149
|
this.deps.config,
|
|
154
150
|
);
|
|
155
151
|
if (params?.entityId) {
|
|
@@ -177,9 +173,8 @@ export class InventoryService {
|
|
|
177
173
|
actor?: Actor | null,
|
|
178
174
|
ctx?: TxContext,
|
|
179
175
|
): Promise<Result<Record<string, number>>> {
|
|
180
|
-
const orgId =
|
|
176
|
+
const orgId = resolveOrgIdForCommerce(
|
|
181
177
|
actor ?? ctx?.actor ?? null,
|
|
182
|
-
undefined,
|
|
183
178
|
this.deps.config,
|
|
184
179
|
);
|
|
185
180
|
const data = await this.repo.getAvailableQuantities(orgId, entityIds, ctx);
|
|
@@ -191,9 +186,8 @@ export class InventoryService {
|
|
|
191
186
|
ctx?: TxContext,
|
|
192
187
|
actor?: Actor | null,
|
|
193
188
|
): Promise<Result<InventoryLevel[]>> {
|
|
194
|
-
const orgId =
|
|
189
|
+
const orgId = resolveOrgIdForCommerce(
|
|
195
190
|
actor ?? ctx?.actor ?? null,
|
|
196
|
-
undefined,
|
|
197
191
|
this.deps.config,
|
|
198
192
|
);
|
|
199
193
|
const levels = await this.repo.findLevelsByEntityId(orgId, entityId, ctx);
|
|
@@ -213,9 +207,8 @@ export class InventoryService {
|
|
|
213
207
|
);
|
|
214
208
|
}
|
|
215
209
|
|
|
216
|
-
const orgId =
|
|
210
|
+
const orgId = resolveOrgIdForCommerce(
|
|
217
211
|
actor ?? ctx?.actor ?? null,
|
|
218
|
-
undefined,
|
|
219
212
|
this.deps.config,
|
|
220
213
|
);
|
|
221
214
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(actor, ctx));
|
|
@@ -267,9 +260,8 @@ export class InventoryService {
|
|
|
267
260
|
actor?: Actor | null,
|
|
268
261
|
ctx?: TxContext,
|
|
269
262
|
): Promise<Result<void>> {
|
|
270
|
-
const orgId =
|
|
263
|
+
const orgId = resolveOrgIdForCommerce(
|
|
271
264
|
actor ?? ctx?.actor ?? null,
|
|
272
|
-
undefined,
|
|
273
265
|
this.deps.config,
|
|
274
266
|
);
|
|
275
267
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(actor, ctx));
|
|
@@ -360,7 +352,7 @@ export class InventoryService {
|
|
|
360
352
|
const performedBy = input.performedBy ?? actor?.userId ?? "system";
|
|
361
353
|
|
|
362
354
|
const doAdjust = async (txCtx: TxContext) => {
|
|
363
|
-
const orgId =
|
|
355
|
+
const orgId = resolveOrgIdForCommerce(actor ?? txCtx.actor ?? null, this.deps.config);
|
|
364
356
|
|
|
365
357
|
// Lock the level first so `before` and the write are atomic.
|
|
366
358
|
const existing = await this.repo.findLevelForUpdate(
|
|
@@ -444,6 +436,7 @@ export class InventoryService {
|
|
|
444
436
|
services: this.deps.services,
|
|
445
437
|
context: { moduleName: "inventory" },
|
|
446
438
|
database: { db: this.deps.database.db as PluginDb },
|
|
439
|
+
commerceConfig: this.deps.config,
|
|
447
440
|
});
|
|
448
441
|
|
|
449
442
|
const afterHooks = this.deps.hooks.resolve("inventory.afterAdjust");
|
|
@@ -496,9 +489,8 @@ export class InventoryService {
|
|
|
496
489
|
ctx?: TxContext,
|
|
497
490
|
): Promise<Result<InventoryLevel>> {
|
|
498
491
|
const warehouseId = input.warehouseId ?? (await this.pickWarehouse(actor, ctx));
|
|
499
|
-
const orgId =
|
|
492
|
+
const orgId = resolveOrgIdForCommerce(
|
|
500
493
|
actor ?? ctx?.actor ?? null,
|
|
501
|
-
undefined,
|
|
502
494
|
this.deps.config,
|
|
503
495
|
);
|
|
504
496
|
|
|
@@ -551,7 +543,7 @@ export class InventoryService {
|
|
|
551
543
|
): Promise<Result<void>> {
|
|
552
544
|
const orgId =
|
|
553
545
|
input.orgId ??
|
|
554
|
-
|
|
546
|
+
resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config);
|
|
555
547
|
const warehouseId =
|
|
556
548
|
input.warehouseId ?? (await this.pickWarehouse(null, ctx, orgId));
|
|
557
549
|
const variantId = input.variantId ?? null;
|
|
@@ -602,9 +594,8 @@ export class InventoryService {
|
|
|
602
594
|
variantId?: string,
|
|
603
595
|
ctx?: TxContext,
|
|
604
596
|
): Promise<Result<InventoryLevel>> {
|
|
605
|
-
const orgId =
|
|
597
|
+
const orgId = resolveOrgIdForCommerce(
|
|
606
598
|
ctx?.actor ?? null,
|
|
607
|
-
undefined,
|
|
608
599
|
this.deps.config,
|
|
609
600
|
);
|
|
610
601
|
const level = await this.repo.findLevelByKey(
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { index, integer, jsonb, numeric, pgEnum, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
|
2
4
|
import { organization } from "../../auth/auth-schema.js";
|
|
3
5
|
import { sellableEntities, variants } from "../catalog/schema.js";
|
|
4
6
|
|
|
7
|
+
export const mediaAssetOrigin = pgEnum("media_asset_origin", ["merchant", "generated", "imported"]);
|
|
8
|
+
export type MediaAssetOrigin = (typeof mediaAssetOrigin.enumValues)[number];
|
|
9
|
+
|
|
5
10
|
export const mediaAssets = pgTable(
|
|
6
11
|
"media_assets",
|
|
7
12
|
{
|
|
@@ -17,6 +22,11 @@ export const mediaAssets = pgTable(
|
|
|
17
22
|
height: integer("height"),
|
|
18
23
|
alt: text("alt"),
|
|
19
24
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
|
25
|
+
origin: mediaAssetOrigin("origin").notNull().default("merchant"),
|
|
26
|
+
confidence: numeric("confidence", { precision: 4, scale: 3 }),
|
|
27
|
+
derivedFromAssetId: uuid("derived_from_asset_id").references((): AnyPgColumn => mediaAssets.id, {
|
|
28
|
+
onDelete: "set null",
|
|
29
|
+
}),
|
|
20
30
|
uploadedAt: timestamp("uploaded_at", { withTimezone: true }).defaultNow().notNull(),
|
|
21
31
|
},
|
|
22
32
|
(table) => ({
|
|
@@ -38,4 +48,15 @@ export const entityMedia = pgTable("entity_media", {
|
|
|
38
48
|
enum: ["primary", "gallery", "thumbnail", "video", "document"],
|
|
39
49
|
}).notNull(),
|
|
40
50
|
sortOrder: integer("sort_order").notNull().default(0),
|
|
41
|
-
})
|
|
51
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
52
|
+
}, (table) => ({
|
|
53
|
+
// Two partial indexes instead of one NULLS DISTINCT index: Postgres treats
|
|
54
|
+
// null variant ids as distinct, so a single index cannot reject duplicate
|
|
55
|
+
// entity-level rows.
|
|
56
|
+
entityLevelUnique: uniqueIndex("entity_media_entity_level_unique")
|
|
57
|
+
.on(table.entityId, table.mediaAssetId)
|
|
58
|
+
.where(sql`${table.variantId} IS NULL`),
|
|
59
|
+
variantLevelUnique: uniqueIndex("entity_media_variant_level_unique")
|
|
60
|
+
.on(table.entityId, table.variantId, table.mediaAssetId)
|
|
61
|
+
.where(sql`${table.variantId} IS NOT NULL`),
|
|
62
|
+
}));
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
|
+
import { assertPermission } from "../../auth/permissions.js";
|
|
2
3
|
import type { Actor } from "../../auth/types.js";
|
|
3
4
|
import type { CommerceConfig } from "../../config/types.js";
|
|
4
5
|
import type { StorageAdapter, StoredFile } from "./adapter.js";
|
|
5
6
|
import { Err, Ok, type Result } from "../../kernel/result.js";
|
|
6
7
|
import {
|
|
8
|
+
CommerceConflictError,
|
|
7
9
|
CommerceNotFoundError,
|
|
8
10
|
CommerceValidationError,
|
|
11
|
+
toCommerceError,
|
|
9
12
|
} from "../../kernel/errors.js";
|
|
10
|
-
import type { MediaRepository } from "./repository/index.js";
|
|
13
|
+
import type { MediaRepository, MediaAsset } from "./repository/index.js";
|
|
11
14
|
import type { CatalogRepository } from "../catalog/repository/index.js";
|
|
12
15
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
16
|
+
import { createTxContext } from "../../kernel/database/tx-context.js";
|
|
17
|
+
import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
|
|
13
18
|
import { makeId } from "../../utils/id.js";
|
|
19
|
+
import type { MediaAssetOrigin } from "./schema.js";
|
|
14
20
|
|
|
15
21
|
export interface UploadMediaInput {
|
|
16
22
|
filename: string;
|
|
@@ -18,6 +24,7 @@ export interface UploadMediaInput {
|
|
|
18
24
|
data: ArrayBuffer;
|
|
19
25
|
alt?: string;
|
|
20
26
|
metadata?: Record<string, unknown>;
|
|
27
|
+
origin?: MediaAssetOrigin;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export interface AttachMediaInput {
|
|
@@ -43,6 +50,8 @@ interface MediaServiceDeps {
|
|
|
43
50
|
catalogRepository: CatalogRepository;
|
|
44
51
|
storage: StorageAdapter;
|
|
45
52
|
config: CommerceConfig;
|
|
53
|
+
database?: DatabaseAdapter;
|
|
54
|
+
services?: Record<string, unknown>;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
const DEFAULT_ALLOWED_MIME_TYPES = [
|
|
@@ -53,6 +62,17 @@ const DEFAULT_ALLOWED_MIME_TYPES = [
|
|
|
53
62
|
"application/pdf",
|
|
54
63
|
];
|
|
55
64
|
|
|
65
|
+
export function assertMediaWritable(asset: Pick<MediaAsset, "origin">, actor: Actor | null): void {
|
|
66
|
+
if (asset.origin === "merchant") assertPermission(actor, "media:write");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isUniqueViolation(error: unknown): boolean {
|
|
70
|
+
if (error == null || typeof error !== "object") return false;
|
|
71
|
+
const value = error as { code?: unknown; cause?: unknown };
|
|
72
|
+
if (value.code === "23505") return true;
|
|
73
|
+
return isUniqueViolation(value.cause);
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
function isSvgOrXml(buffer: Uint8Array): boolean {
|
|
57
77
|
const prefix = new TextDecoder()
|
|
58
78
|
.decode(buffer.slice(0, 256))
|
|
@@ -97,6 +117,16 @@ export class MediaService {
|
|
|
97
117
|
this.catalogRepo = deps.catalogRepository;
|
|
98
118
|
}
|
|
99
119
|
|
|
120
|
+
private async withTransaction<T>(
|
|
121
|
+
actor: Actor | null,
|
|
122
|
+
ctx: TxContext | undefined,
|
|
123
|
+
fn: (txCtx: TxContext) => Promise<T>,
|
|
124
|
+
): Promise<T> {
|
|
125
|
+
if (ctx?.tx != null) return fn(ctx);
|
|
126
|
+
if (this.deps.database == null) throw new Error("Database is required for media mutations.");
|
|
127
|
+
return this.deps.database.transaction(async (tx) => fn(createTxContext(tx, { actor })));
|
|
128
|
+
}
|
|
129
|
+
|
|
100
130
|
async upload(
|
|
101
131
|
input: UploadMediaInput,
|
|
102
132
|
actor?: Actor | null,
|
|
@@ -108,7 +138,7 @@ export class MediaService {
|
|
|
108
138
|
);
|
|
109
139
|
}
|
|
110
140
|
|
|
111
|
-
const orgId =
|
|
141
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
112
142
|
const bytes = new Uint8Array(input.data);
|
|
113
143
|
const detectedMime = detectMimeFromBuffer(bytes);
|
|
114
144
|
const allowSvg = this.deps.config.media?.allowSvg === true;
|
|
@@ -148,6 +178,7 @@ export class MediaService {
|
|
|
148
178
|
contentType: effectiveMime,
|
|
149
179
|
size: input.data.byteLength,
|
|
150
180
|
metadata: input.metadata ?? {},
|
|
181
|
+
origin: input.origin ?? "merchant",
|
|
151
182
|
uploadedAt: new Date(),
|
|
152
183
|
...(input.alt !== undefined ? { alt: input.alt } : {}),
|
|
153
184
|
},
|
|
@@ -172,7 +203,7 @@ export class MediaService {
|
|
|
172
203
|
actor?: Actor | null,
|
|
173
204
|
ctx?: TxContext,
|
|
174
205
|
): Promise<Result<string>> {
|
|
175
|
-
const orgId =
|
|
206
|
+
const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
|
|
176
207
|
const asset = await this.repo.findAssetById(id, ctx, orgId);
|
|
177
208
|
if (!asset) return Err(new CommerceNotFoundError("Media asset not found."));
|
|
178
209
|
return this.deps.storage.getUrl(asset.storageKey);
|
|
@@ -184,7 +215,7 @@ export class MediaService {
|
|
|
184
215
|
actor?: Actor | null,
|
|
185
216
|
ctx?: TxContext,
|
|
186
217
|
): Promise<Result<string>> {
|
|
187
|
-
const orgId =
|
|
218
|
+
const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
|
|
188
219
|
const asset = await this.repo.findAssetById(id, ctx, orgId);
|
|
189
220
|
if (!asset) return Err(new CommerceNotFoundError("Media asset not found."));
|
|
190
221
|
return this.deps.storage.getSignedUrl(asset.storageKey, expiresIn);
|
|
@@ -195,10 +226,16 @@ export class MediaService {
|
|
|
195
226
|
actor?: Actor | null,
|
|
196
227
|
ctx?: TxContext,
|
|
197
228
|
): Promise<Result<void>> {
|
|
198
|
-
const orgId =
|
|
229
|
+
const orgId = resolveOrgIdForCommerce(actor ?? null, this.deps.config);
|
|
199
230
|
const asset = await this.repo.findAssetById(id, ctx, orgId);
|
|
200
231
|
if (!asset) return Err(new CommerceNotFoundError("Media asset not found."));
|
|
201
232
|
|
|
233
|
+
try {
|
|
234
|
+
assertMediaWritable(asset, actor ?? ctx?.actor ?? null);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
return Err(toCommerceError(error));
|
|
237
|
+
}
|
|
238
|
+
|
|
202
239
|
const deleted = await this.deps.storage.delete(asset.storageKey);
|
|
203
240
|
if (!deleted.ok) return deleted;
|
|
204
241
|
|
|
@@ -218,31 +255,55 @@ export class MediaService {
|
|
|
218
255
|
actor?: Actor | null,
|
|
219
256
|
ctx?: TxContext,
|
|
220
257
|
): Promise<Result<void>> {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
258
|
+
try {
|
|
259
|
+
return await this.withTransaction(actor ?? ctx?.actor ?? null, ctx, async (txCtx) => {
|
|
260
|
+
const orgId = resolveOrgIdForCommerce(actor ?? txCtx.actor, this.deps.config);
|
|
261
|
+
const entity = await this.catalogRepo.findEntityById(input.entityId, txCtx, orgId);
|
|
262
|
+
if (!entity) return Err(new CommerceNotFoundError("Entity not found."));
|
|
226
263
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return Err(new CommerceNotFoundError("Media asset not found."));
|
|
230
|
-
}
|
|
264
|
+
const asset = await this.repo.findAssetById(input.mediaAssetId, txCtx, orgId);
|
|
265
|
+
if (!asset) return Err(new CommerceNotFoundError("Media asset not found."));
|
|
231
266
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
role: input.role,
|
|
237
|
-
sortOrder: input.sortOrder ?? 0,
|
|
238
|
-
...(input.variantId !== undefined
|
|
239
|
-
? { variantId: input.variantId }
|
|
240
|
-
: {}),
|
|
241
|
-
},
|
|
242
|
-
ctx,
|
|
243
|
-
);
|
|
267
|
+
const existing = await this.repo.findEntityMedia(input.entityId, input.variantId, txCtx);
|
|
268
|
+
if (existing.some((link) => link.mediaAssetId === input.mediaAssetId)) {
|
|
269
|
+
return Err(new CommerceConflictError("Media asset is already attached at this level."));
|
|
270
|
+
}
|
|
244
271
|
|
|
245
|
-
|
|
272
|
+
await this.repo.createEntityMedia(
|
|
273
|
+
{
|
|
274
|
+
entityId: input.entityId,
|
|
275
|
+
mediaAssetId: input.mediaAssetId,
|
|
276
|
+
role: input.role,
|
|
277
|
+
sortOrder: input.sortOrder ?? 0,
|
|
278
|
+
...(input.variantId !== undefined
|
|
279
|
+
? { variantId: input.variantId }
|
|
280
|
+
: {}),
|
|
281
|
+
},
|
|
282
|
+
txCtx,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const catalog = this.deps.services?.catalog;
|
|
286
|
+
if (catalog == null || typeof catalog !== "object") {
|
|
287
|
+
throw new Error("Catalog service is required for media revision capture.");
|
|
288
|
+
}
|
|
289
|
+
const catalogService = catalog as {
|
|
290
|
+
recordEntityRevision: (
|
|
291
|
+
entityId: string,
|
|
292
|
+
actor: Actor | null,
|
|
293
|
+
reason?: "create" | "update" | "import" | "enrichment" | "push" | "restore",
|
|
294
|
+
ctx?: TxContext,
|
|
295
|
+
) => Promise<Result<unknown>>;
|
|
296
|
+
};
|
|
297
|
+
const revision = await catalogService.recordEntityRevision(input.entityId, actor ?? txCtx.actor, "update", txCtx);
|
|
298
|
+
if (!revision.ok) throw revision.error;
|
|
299
|
+
return Ok(undefined);
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (isUniqueViolation(error)) {
|
|
303
|
+
return Err(new CommerceConflictError("Media asset is already attached at this level."));
|
|
304
|
+
}
|
|
305
|
+
return Err(toCommerceError(error));
|
|
306
|
+
}
|
|
246
307
|
}
|
|
247
308
|
|
|
248
309
|
/**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { GuestOrderAccessStrategy } from "../../config/types.js";
|
|
2
|
+
|
|
3
|
+
const UNIT_MS: Record<string, number> = {
|
|
4
|
+
s: 1_000,
|
|
5
|
+
m: 60_000,
|
|
6
|
+
h: 3_600_000,
|
|
7
|
+
d: 86_400_000,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parses a window like `"7d"`, `"2h"`, `"30m"`, `"45s"`.
|
|
12
|
+
* Throws at configuration time rather than silently admitting everything —
|
|
13
|
+
* a typo in a security window must not read as "no limit".
|
|
14
|
+
*/
|
|
15
|
+
export function parseAccessWindow(duration: string): number {
|
|
16
|
+
const match = /^(\d+)(s|m|h|d)$/.exec(duration.trim());
|
|
17
|
+
if (!match) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Invalid guest order access window "${duration}". Use a whole number followed by s, m, h, or d — for example "7d".`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return Number(match[1]) * UNIT_MS[match[2]!]!;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Grants a guest bearer credential access to a placed order for a fixed window
|
|
27
|
+
* after it was placed.
|
|
28
|
+
*
|
|
29
|
+
* Anchored on `placedAt`, never on the cart's expiry: a shopper who checks out
|
|
30
|
+
* on the sixth day of a seven-day cart would otherwise get one day of receipt
|
|
31
|
+
* access. The two lifetimes are independent by construction.
|
|
32
|
+
*/
|
|
33
|
+
export function windowedGuestOrderAccess(duration: string): GuestOrderAccessStrategy {
|
|
34
|
+
const windowMs = parseAccessWindow(duration);
|
|
35
|
+
return {
|
|
36
|
+
canAccessOrder(order, now) {
|
|
37
|
+
const placedAt =
|
|
38
|
+
order.placedAt instanceof Date ? order.placedAt : new Date(order.placedAt);
|
|
39
|
+
const elapsed = now.getTime() - placedAt.getTime();
|
|
40
|
+
return Number.isFinite(elapsed) && elapsed <= windowMs;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The default window. Seven days matches `config.cart.ttlMinutes` so the
|
|
47
|
+
* product carries one number rather than two.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately not Vendure's two hours: that figure is calibrated for a
|
|
50
|
+
* guessable order code, and a cart secret is a 122-bit random UUID. Guessing is
|
|
51
|
+
* not the exposure here — a secret that leaked through a referrer, a shared
|
|
52
|
+
* link or a log line is, and a bounded window is what expires it.
|
|
53
|
+
*/
|
|
54
|
+
export const defaultGuestOrderAccess: GuestOrderAccessStrategy =
|
|
55
|
+
windowedGuestOrderAccess("7d");
|
|
@@ -101,6 +101,7 @@ export async function computeOrderPricing(
|
|
|
101
101
|
context: { moduleName: "orders" },
|
|
102
102
|
origin: "rest",
|
|
103
103
|
database: { db: kernel.database.db as PluginDb },
|
|
104
|
+
commerceConfig: kernel.config,
|
|
104
105
|
});
|
|
105
106
|
if (tx !== undefined) context.tx = tx as typeof context.tx;
|
|
106
107
|
|
|
@@ -166,6 +166,7 @@ export class OrdersRepository {
|
|
|
166
166
|
async findByIdempotencyKey(
|
|
167
167
|
orgId: string,
|
|
168
168
|
idempotencyKey: string,
|
|
169
|
+
idempotencyScope: string,
|
|
169
170
|
ctx?: TxContext,
|
|
170
171
|
): Promise<Order | undefined> {
|
|
171
172
|
const db = this.getDb(ctx);
|
|
@@ -175,6 +176,7 @@ export class OrdersRepository {
|
|
|
175
176
|
.where(
|
|
176
177
|
and(
|
|
177
178
|
eq(orders.organizationId, orgId),
|
|
179
|
+
eq(orders.idempotencyScope, idempotencyScope),
|
|
178
180
|
eq(orders.idempotencyKey, idempotencyKey),
|
|
179
181
|
),
|
|
180
182
|
);
|
|
@@ -21,6 +21,8 @@ export const orders = pgTable("orders", {
|
|
|
21
21
|
// Client-supplied key making order creation safely retryable (offline POS
|
|
22
22
|
// queues, network retries) — replays return the original order.
|
|
23
23
|
idempotencyKey: text("idempotency_key"),
|
|
24
|
+
// Hash of the authenticated actor or guest credential that owns the key.
|
|
25
|
+
idempotencyScope: text("idempotency_scope"),
|
|
24
26
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
|
25
27
|
placedAt: timestamp("placed_at", { withTimezone: true }).defaultNow().notNull(),
|
|
26
28
|
fulfilledAt: timestamp("fulfilled_at", { withTimezone: true }),
|
|
@@ -32,9 +34,9 @@ export const orders = pgTable("orders", {
|
|
|
32
34
|
customerIdIdx: index("idx_orders_customer_id").on(table.customerId),
|
|
33
35
|
placedAtIdx: index("idx_orders_placed_at").on(table.placedAt),
|
|
34
36
|
paymentIntentIdx: index("idx_orders_payment_intent").on(table.paymentIntentId),
|
|
35
|
-
orgIdempotencyKeyUnique: uniqueIndex("
|
|
36
|
-
.on(table.organizationId, table.idempotencyKey)
|
|
37
|
-
.where(sql`idempotency_key IS NOT NULL`),
|
|
37
|
+
orgIdempotencyKeyUnique: uniqueIndex("orders_org_idempotency_scope_key_unique")
|
|
38
|
+
.on(table.organizationId, table.idempotencyScope, table.idempotencyKey)
|
|
39
|
+
.where(sql`idempotency_scope IS NOT NULL AND idempotency_key IS NOT NULL`),
|
|
38
40
|
}));
|
|
39
41
|
|
|
40
42
|
export const orderLineItems = pgTable("order_line_items", {
|