@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,22 +1,362 @@
|
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
|
+
import { assertPermission } from "../../auth/permissions.js";
|
|
3
|
+
import { Err, Ok } from "../../kernel/result.js";
|
|
4
|
+
import { CommerceConflictError, CommerceNotFoundError, CommerceValidationError, toCommerceError } from "../../kernel/errors.js";
|
|
5
|
+
import { createTxContext, isWriteContextTransactional, resolveWriteContextHookContext } from "../../kernel/database/tx-context.js";
|
|
6
|
+
import { isValidFieldPath, validateFieldPath } from "./ownership.js";
|
|
7
|
+
import { comparableSerialize } from "./revision.js";
|
|
1
8
|
import { EntityService } from "./entity-service.js";
|
|
2
9
|
import { CategoryService } from "./category-service.js";
|
|
3
10
|
import { BrandService } from "./brand-service.js";
|
|
11
|
+
function isUniqueViolation(error) {
|
|
12
|
+
if (error == null || typeof error !== "object")
|
|
13
|
+
return false;
|
|
14
|
+
const value = error;
|
|
15
|
+
if (value.code === "23505")
|
|
16
|
+
return true;
|
|
17
|
+
return isUniqueViolation(value.cause);
|
|
18
|
+
}
|
|
4
19
|
export class CatalogServiceImpl {
|
|
5
20
|
repository;
|
|
21
|
+
database;
|
|
22
|
+
config;
|
|
6
23
|
entities;
|
|
7
24
|
categories;
|
|
8
25
|
brands;
|
|
9
26
|
constructor(deps) {
|
|
10
27
|
this.repository = deps.repository;
|
|
11
|
-
this.
|
|
28
|
+
this.database = deps.database;
|
|
29
|
+
this.config = deps.config;
|
|
30
|
+
this.entities = new EntityService(deps, this.resolveEntityFieldDefinitions.bind(this));
|
|
12
31
|
this.categories = new CategoryService(deps);
|
|
13
32
|
this.brands = new BrandService(deps);
|
|
14
33
|
}
|
|
34
|
+
codeFieldDefinition(entityType, name) {
|
|
35
|
+
return this.config.entities?.[entityType]?.fields.find((field) => field.name === name);
|
|
36
|
+
}
|
|
37
|
+
runtimeFieldDefinition(row) {
|
|
38
|
+
return {
|
|
39
|
+
name: row.name,
|
|
40
|
+
type: row.type,
|
|
41
|
+
...(row.unit != null ? { unit: row.unit } : {}),
|
|
42
|
+
...(row.options != null ? { options: row.options } : {}),
|
|
43
|
+
...(row.target != null ? { target: row.target } : {}),
|
|
44
|
+
filterable: row.filterable,
|
|
45
|
+
localized: row.localized,
|
|
46
|
+
sortOrder: row.sortOrder,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async resolveEntityFieldDefinitions(entityType, actorOrOrg, ctx) {
|
|
50
|
+
const orgId = typeof actorOrOrg === "string"
|
|
51
|
+
? actorOrOrg
|
|
52
|
+
: resolveOrgIdForCommerce(actorOrOrg ?? ctx?.actor ?? null, this.config);
|
|
53
|
+
const codeFields = this.config.entities?.[entityType]?.fields ?? [];
|
|
54
|
+
const merged = new Map(codeFields.map((field) => [field.name, { ...field }]));
|
|
55
|
+
const runtimeFields = await this.repository.findActiveEntityFieldDefinitions(orgId, entityType, ctx);
|
|
56
|
+
for (const row of runtimeFields) {
|
|
57
|
+
const codeField = merged.get(row.name);
|
|
58
|
+
if (!codeField) {
|
|
59
|
+
merged.set(row.name, this.runtimeFieldDefinition(row));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
merged.set(row.name, {
|
|
63
|
+
...codeField,
|
|
64
|
+
...(row.options != null ? { options: row.options } : {}),
|
|
65
|
+
filterable: row.filterable,
|
|
66
|
+
localized: row.localized,
|
|
67
|
+
sortOrder: row.sortOrder,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return [...merged.values()].sort((first, second) => (first.sortOrder ?? 0) - (second.sortOrder ?? 0));
|
|
71
|
+
}
|
|
72
|
+
listEntityFieldDefinitions(actor, entityType, ctx) {
|
|
73
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
74
|
+
return this.withMutationResult(actor ?? ctx?.actor ?? null, ctx, async (txCtx) => Ok(await this.repository.findEntityFieldDefinitions(orgId, entityType, txCtx)));
|
|
75
|
+
}
|
|
76
|
+
createEntityFieldDefinition(input, actor, ctx) {
|
|
77
|
+
if (input.entityType.length === 0 || input.name.length === 0) {
|
|
78
|
+
return Promise.resolve(Err(new CommerceValidationError("Entity type and field name are required.")));
|
|
79
|
+
}
|
|
80
|
+
assertPermission(actor, "catalog:update");
|
|
81
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
82
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
83
|
+
const codeField = this.codeFieldDefinition(input.entityType, input.name);
|
|
84
|
+
if (codeField && input.type !== codeField.type) {
|
|
85
|
+
return Err(new CommerceValidationError("Code-defined fields only allow options, filterable, localized, and sortOrder overrides."));
|
|
86
|
+
}
|
|
87
|
+
return Ok(await this.repository.createEntityFieldDefinition({
|
|
88
|
+
organizationId: orgId,
|
|
89
|
+
entityType: input.entityType,
|
|
90
|
+
name: input.name,
|
|
91
|
+
type: input.type,
|
|
92
|
+
// A shadow row over a code field inherits the code values for anything
|
|
93
|
+
// omitted, so creating one never silently flips a code default.
|
|
94
|
+
...(codeField?.unit !== undefined || input.unit !== undefined
|
|
95
|
+
? { unit: codeField ? codeField.unit ?? null : input.unit }
|
|
96
|
+
: {}),
|
|
97
|
+
...(codeField?.target !== undefined || input.target !== undefined
|
|
98
|
+
? { target: codeField ? codeField.target ?? null : input.target }
|
|
99
|
+
: {}),
|
|
100
|
+
...(input.options !== undefined
|
|
101
|
+
? { options: input.options }
|
|
102
|
+
: codeField?.options !== undefined ? { options: codeField.options } : {}),
|
|
103
|
+
...(input.filterable !== undefined
|
|
104
|
+
? { filterable: input.filterable }
|
|
105
|
+
: codeField?.filterable !== undefined ? { filterable: codeField.filterable } : {}),
|
|
106
|
+
...(input.localized !== undefined ? { localized: input.localized } : {}),
|
|
107
|
+
...(input.sortOrder !== undefined ? { sortOrder: input.sortOrder } : {}),
|
|
108
|
+
}, txCtx));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
updateEntityFieldDefinition(id, input, actor, ctx) {
|
|
112
|
+
assertPermission(actor, "catalog:update");
|
|
113
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
114
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
115
|
+
const existing = await this.repository.findEntityFieldDefinitionById(orgId, id, txCtx);
|
|
116
|
+
if (!existing)
|
|
117
|
+
return Err(new CommerceNotFoundError("Entity field definition not found."));
|
|
118
|
+
const codeField = this.codeFieldDefinition(existing.entityType, existing.name);
|
|
119
|
+
if (codeField && (input.entityType !== undefined || input.name !== undefined || input.type !== undefined || input.unit !== undefined || input.target !== undefined)) {
|
|
120
|
+
return Err(new CommerceValidationError("Code-defined fields only allow options, filterable, localized, and sortOrder updates."));
|
|
121
|
+
}
|
|
122
|
+
const data = {
|
|
123
|
+
...(input.entityType !== undefined ? { entityType: input.entityType } : {}),
|
|
124
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
125
|
+
...(input.type !== undefined ? { type: input.type } : {}),
|
|
126
|
+
...(input.unit !== undefined ? { unit: input.unit } : {}),
|
|
127
|
+
...(input.options !== undefined ? { options: input.options } : {}),
|
|
128
|
+
...(input.target !== undefined ? { target: input.target } : {}),
|
|
129
|
+
...(input.filterable !== undefined ? { filterable: input.filterable } : {}),
|
|
130
|
+
...(input.localized !== undefined ? { localized: input.localized } : {}),
|
|
131
|
+
...(input.sortOrder !== undefined ? { sortOrder: input.sortOrder } : {}),
|
|
132
|
+
};
|
|
133
|
+
const updated = await this.repository.updateEntityFieldDefinition(orgId, id, data, txCtx);
|
|
134
|
+
if (!updated)
|
|
135
|
+
return Err(new CommerceNotFoundError("Entity field definition not found."));
|
|
136
|
+
return Ok(updated);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
archiveEntityFieldDefinition(id, actor, ctx) {
|
|
140
|
+
assertPermission(actor, "catalog:update");
|
|
141
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
142
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
143
|
+
const existing = await this.repository.findEntityFieldDefinitionById(orgId, id, txCtx);
|
|
144
|
+
if (!existing)
|
|
145
|
+
return Err(new CommerceNotFoundError("Entity field definition not found."));
|
|
146
|
+
if (this.codeFieldDefinition(existing.entityType, existing.name)) {
|
|
147
|
+
return Err(new CommerceValidationError("Code-defined fields cannot be archived."));
|
|
148
|
+
}
|
|
149
|
+
if (existing.status === "archived")
|
|
150
|
+
return Ok(existing);
|
|
151
|
+
const updated = await this.repository.updateEntityFieldDefinition(orgId, id, { status: "archived" }, txCtx);
|
|
152
|
+
if (!updated)
|
|
153
|
+
return Err(new CommerceNotFoundError("Entity field definition not found."));
|
|
154
|
+
return Ok(updated);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async withMutation(actor, ctx, fn) {
|
|
158
|
+
if (isWriteContextTransactional(ctx))
|
|
159
|
+
return fn(ctx);
|
|
160
|
+
const hookContext = resolveWriteContextHookContext(ctx);
|
|
161
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
162
|
+
try {
|
|
163
|
+
return await this.database.transaction(async (tx) => fn(createTxContext(tx, {
|
|
164
|
+
actor,
|
|
165
|
+
...(hookContext ? { hookContext } : {}),
|
|
166
|
+
})));
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (!isUniqueViolation(error) || attempt === 2)
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw new Error("Catalog mutation retry limit reached.");
|
|
174
|
+
}
|
|
175
|
+
async withMutationResult(actor, ctx, fn) {
|
|
176
|
+
try {
|
|
177
|
+
return await this.withMutation(actor, ctx, fn);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (isUniqueViolation(error))
|
|
181
|
+
return Err(new CommerceConflictError("A resource with those values already exists."));
|
|
182
|
+
return Err(toCommerceError(error));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async captureRevision(entityId, actor, reason, ctx, force = false) {
|
|
186
|
+
const snapshot = await this.repository.snapshotEntity(entityId, ctx);
|
|
187
|
+
const latest = await this.repository.findLatestRevision(entityId, ctx);
|
|
188
|
+
if (!(force || reason === "push") && latest && comparableSerialize(latest.snapshot) === comparableSerialize(snapshot))
|
|
189
|
+
return latest;
|
|
190
|
+
const revisionActor = actor ?? ctx.actor;
|
|
191
|
+
const organizationId = typeof snapshot.entity.organizationId === "string"
|
|
192
|
+
? snapshot.entity.organizationId
|
|
193
|
+
: resolveOrgIdForCommerce(revisionActor, this.config);
|
|
194
|
+
return this.repository.createRevision({
|
|
195
|
+
organizationId,
|
|
196
|
+
entityId,
|
|
197
|
+
snapshot,
|
|
198
|
+
reason,
|
|
199
|
+
actorId: revisionActor?.userId ?? null,
|
|
200
|
+
actorType: revisionActor?.type ?? null,
|
|
201
|
+
requestId: ctx.requestId,
|
|
202
|
+
}, ctx);
|
|
203
|
+
}
|
|
204
|
+
async recordEntityRevision(entityId, actor, reason = "update", ctx) {
|
|
205
|
+
try {
|
|
206
|
+
return await this.withMutation(actor, ctx, async (txCtx) => {
|
|
207
|
+
const revisionActor = actor ?? txCtx.actor;
|
|
208
|
+
assertPermission(revisionActor, "catalog:update");
|
|
209
|
+
const entity = await this.repository.findEntityById(entityId, txCtx);
|
|
210
|
+
if (!entity)
|
|
211
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
212
|
+
if (entity.organizationId !== resolveOrgIdForCommerce(revisionActor, this.config)) {
|
|
213
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
214
|
+
}
|
|
215
|
+
return Ok(await this.captureRevision(entityId, revisionActor, reason, txCtx));
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
return Err(toCommerceError(error));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
restoreDate(value) {
|
|
223
|
+
if (value == null)
|
|
224
|
+
return null;
|
|
225
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
226
|
+
}
|
|
227
|
+
async applyRevisionSnapshot(entityId, snapshot, ctx) {
|
|
228
|
+
const entity = snapshot.entity;
|
|
229
|
+
const entityData = {
|
|
230
|
+
sourceStoreId: entity.sourceStoreId,
|
|
231
|
+
type: entity.type,
|
|
232
|
+
slug: entity.slug,
|
|
233
|
+
status: entity.status,
|
|
234
|
+
isVisible: entity.isVisible,
|
|
235
|
+
taxClass: entity.taxClass,
|
|
236
|
+
metadata: entity.metadata,
|
|
237
|
+
publishedAt: this.restoreDate(snapshot.entity.publishedAt),
|
|
238
|
+
};
|
|
239
|
+
await this.repository.updateEntity(entityId, entityData, ctx);
|
|
240
|
+
await this.repository.deleteAttributesByEntityId(entityId, ctx);
|
|
241
|
+
for (const row of snapshot.attributes) {
|
|
242
|
+
await this.repository.createAttribute({
|
|
243
|
+
id: row.id,
|
|
244
|
+
entityId,
|
|
245
|
+
locale: row.locale,
|
|
246
|
+
title: row.title,
|
|
247
|
+
subtitle: row.subtitle,
|
|
248
|
+
description: row.description,
|
|
249
|
+
richDescription: row.richDescription,
|
|
250
|
+
seoTitle: row.seoTitle,
|
|
251
|
+
seoDescription: row.seoDescription,
|
|
252
|
+
}, ctx);
|
|
253
|
+
}
|
|
254
|
+
await this.repository.deleteCustomFieldsByEntityId(entityId, ctx);
|
|
255
|
+
for (const row of snapshot.customFields) {
|
|
256
|
+
await this.repository.createCustomField({
|
|
257
|
+
id: row.id,
|
|
258
|
+
entityId,
|
|
259
|
+
fieldName: row.fieldName,
|
|
260
|
+
fieldType: row.fieldType,
|
|
261
|
+
source: row.source,
|
|
262
|
+
status: row.status,
|
|
263
|
+
confidence: row.confidence,
|
|
264
|
+
evidence: row.evidence,
|
|
265
|
+
locale: row.locale,
|
|
266
|
+
approvedAt: this.restoreDate(row.approvedAt),
|
|
267
|
+
approvedBy: row.approvedBy,
|
|
268
|
+
textValue: row.textValue,
|
|
269
|
+
numberValue: row.numberValue,
|
|
270
|
+
booleanValue: row.booleanValue,
|
|
271
|
+
dateValue: this.restoreDate(row.dateValue),
|
|
272
|
+
jsonValue: row.jsonValue,
|
|
273
|
+
createdAt: this.restoreDate(row.createdAt) ?? new Date(),
|
|
274
|
+
updatedAt: this.restoreDate(row.updatedAt) ?? new Date(),
|
|
275
|
+
}, ctx);
|
|
276
|
+
}
|
|
277
|
+
await this.repository.deleteEntityCategoriesByEntityId(entityId, ctx);
|
|
278
|
+
for (const row of snapshot.categories) {
|
|
279
|
+
await this.repository.addEntityToCategory(entityId, row.categoryId, row.sortOrder, ctx);
|
|
280
|
+
}
|
|
281
|
+
await this.repository.deleteEntityBrandsByEntityId(entityId, ctx);
|
|
282
|
+
for (const row of snapshot.brands) {
|
|
283
|
+
await this.repository.addEntityToBrand(entityId, row.brandId, row.sortOrder, ctx);
|
|
284
|
+
}
|
|
285
|
+
// Snapshots written before tags existed carry no key; restoring one must
|
|
286
|
+
// not delete tag links the snapshot never saw.
|
|
287
|
+
if (snapshot.tags) {
|
|
288
|
+
await this.repository.deleteEntityTagsByEntityId(entityId, ctx);
|
|
289
|
+
for (const row of snapshot.tags) {
|
|
290
|
+
await this.repository.addEntityTag(entityId, row.tagId, ctx);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
await this.repository.deleteEntityMediaByEntityId(entityId, ctx);
|
|
294
|
+
for (const row of snapshot.media) {
|
|
295
|
+
await this.repository.createEntityMedia({
|
|
296
|
+
entityId,
|
|
297
|
+
mediaAssetId: row.mediaAssetId,
|
|
298
|
+
role: row.role,
|
|
299
|
+
sortOrder: row.sortOrder,
|
|
300
|
+
variantId: row.variantId,
|
|
301
|
+
}, ctx);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async restoreEntityRevision(entityId, revisionId, actor, ctx) {
|
|
305
|
+
try {
|
|
306
|
+
return await this.withMutation(actor, ctx, async (txCtx) => {
|
|
307
|
+
const revisionActor = actor ?? txCtx.actor;
|
|
308
|
+
assertPermission(revisionActor, "catalog:update");
|
|
309
|
+
const entity = await this.repository.findEntityById(entityId, txCtx);
|
|
310
|
+
if (!entity)
|
|
311
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
312
|
+
if (entity.organizationId !== resolveOrgIdForCommerce(revisionActor, this.config)) {
|
|
313
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
314
|
+
}
|
|
315
|
+
if (entity.sourceStoreId != null)
|
|
316
|
+
assertPermission(revisionActor, "catalog:sync");
|
|
317
|
+
const revision = await this.repository.findRevisionById(entityId, revisionId, txCtx);
|
|
318
|
+
if (!revision)
|
|
319
|
+
return Err(new CommerceNotFoundError("Revision not found."));
|
|
320
|
+
await this.applyRevisionSnapshot(entityId, revision.snapshot, txCtx);
|
|
321
|
+
const restored = await this.entities.getById(entityId, { includeAttributes: true, includeCategories: true, includeBrands: true, includeMedia: true }, revisionActor, txCtx);
|
|
322
|
+
if (!restored.ok)
|
|
323
|
+
return restored;
|
|
324
|
+
await this.captureRevision(entityId, revisionActor, "restore", txCtx, true);
|
|
325
|
+
return restored;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
return Err(toCommerceError(error));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async trimEntityRevisions(actor, olderThanDays = 90, ctx) {
|
|
333
|
+
try {
|
|
334
|
+
return await this.withMutation(actor, ctx, async (txCtx) => {
|
|
335
|
+
const revisionActor = actor ?? txCtx.actor;
|
|
336
|
+
assertPermission(revisionActor, "catalog:update");
|
|
337
|
+
const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000);
|
|
338
|
+
return Ok(await this.repository.deleteRevisionsOlderThan(resolveOrgIdForCommerce(revisionActor, this.config), cutoff, txCtx));
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
return Err(toCommerceError(error));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
15
345
|
create(input, actor, ctx) {
|
|
16
|
-
return this.
|
|
346
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
347
|
+
const result = await this.entities.create(input, actor, txCtx);
|
|
348
|
+
if (result.ok)
|
|
349
|
+
await this.captureRevision(result.value.id, actor, "create", txCtx);
|
|
350
|
+
return result;
|
|
351
|
+
});
|
|
17
352
|
}
|
|
18
353
|
update(id, input, actor, ctx) {
|
|
19
|
-
return this.
|
|
354
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
355
|
+
const result = await this.entities.update(id, input, actor, txCtx);
|
|
356
|
+
if (result.ok)
|
|
357
|
+
await this.captureRevision(id, actor, "update", txCtx);
|
|
358
|
+
return result;
|
|
359
|
+
});
|
|
20
360
|
}
|
|
21
361
|
delete(id, actor, ctx) {
|
|
22
362
|
return this.entities.delete(id, actor, ctx);
|
|
@@ -24,26 +364,174 @@ export class CatalogServiceImpl {
|
|
|
24
364
|
getById(id, options, actor, ctx) {
|
|
25
365
|
return this.entities.getById(id, options, actor, ctx);
|
|
26
366
|
}
|
|
367
|
+
getByIdForInternalUse(id, organizationId, options, ctx) {
|
|
368
|
+
return this.entities.getByIdForInternalUse(id, organizationId, options, ctx);
|
|
369
|
+
}
|
|
27
370
|
getBySlug(slug, options, actor, ctx) {
|
|
28
371
|
return this.entities.getBySlug(slug, options, actor, ctx);
|
|
29
372
|
}
|
|
30
373
|
list(params, actor, ctx) {
|
|
31
374
|
return this.entities.list(params, actor, ctx);
|
|
32
375
|
}
|
|
376
|
+
async resolveFieldOwner(entityId, fieldPath, storeId, ctx, variantId) {
|
|
377
|
+
if (!isValidFieldPath(fieldPath))
|
|
378
|
+
return undefined;
|
|
379
|
+
const rows = await this.repository.findFieldOwnershipForResolution(entityId, storeId, variantId ?? null, ctx);
|
|
380
|
+
let selected;
|
|
381
|
+
let selectedPriority = -1;
|
|
382
|
+
for (const row of rows) {
|
|
383
|
+
if (row.fieldPath !== fieldPath)
|
|
384
|
+
continue;
|
|
385
|
+
const priority = (row.variantId === variantId && variantId != null ? 2 : 0)
|
|
386
|
+
+ (row.storeId === storeId ? 1 : 0);
|
|
387
|
+
if (priority > selectedPriority) {
|
|
388
|
+
selected = row;
|
|
389
|
+
selectedPriority = priority;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return selected?.owner;
|
|
393
|
+
}
|
|
394
|
+
async resolveFieldOwners(entityId, storeId, ctx) {
|
|
395
|
+
const rows = await this.repository.findFieldOwnershipForResolution(entityId, storeId, null, ctx);
|
|
396
|
+
const resolved = new Map();
|
|
397
|
+
for (const row of rows) {
|
|
398
|
+
const priority = row.storeId === storeId ? 1 : 0;
|
|
399
|
+
const current = resolved.get(row.fieldPath);
|
|
400
|
+
if (!current || priority > current.priority) {
|
|
401
|
+
resolved.set(row.fieldPath, { owner: row.owner, priority });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return new Map([...resolved].map(([fieldPath, value]) => [fieldPath, value.owner]));
|
|
405
|
+
}
|
|
406
|
+
setFieldOwner(entityId, fieldPath, storeId, owner, actor, ctx, variantId) {
|
|
407
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
408
|
+
const setter = actor ?? txCtx.actor;
|
|
409
|
+
assertPermission(setter, "catalog:update");
|
|
410
|
+
validateFieldPath(fieldPath);
|
|
411
|
+
const entity = await this.repository.findEntityById(entityId, txCtx);
|
|
412
|
+
if (!entity)
|
|
413
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
414
|
+
if (entity.organizationId !== resolveOrgIdForCommerce(setter, this.config)) {
|
|
415
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
416
|
+
}
|
|
417
|
+
if (entity.sourceStoreId != null)
|
|
418
|
+
assertPermission(setter, "catalog:sync");
|
|
419
|
+
await this.repository.upsertFieldOwnership({
|
|
420
|
+
organizationId: entity.organizationId,
|
|
421
|
+
entityId,
|
|
422
|
+
...(variantId != null ? { variantId } : {}),
|
|
423
|
+
...(storeId != null ? { storeId } : {}),
|
|
424
|
+
fieldPath,
|
|
425
|
+
owner,
|
|
426
|
+
}, txCtx);
|
|
427
|
+
return Ok(undefined);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
listFieldOwnership(entityId, actor, ctx, storeId) {
|
|
431
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
432
|
+
const reader = actor ?? txCtx.actor;
|
|
433
|
+
assertPermission(reader, "catalog:read");
|
|
434
|
+
const entity = await this.repository.findEntityById(entityId, txCtx);
|
|
435
|
+
if (!entity)
|
|
436
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
437
|
+
if (entity.organizationId !== resolveOrgIdForCommerce(reader, this.config)) {
|
|
438
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
439
|
+
}
|
|
440
|
+
return Ok(await this.repository.findFieldOwnership(entityId, txCtx, storeId));
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
seedImportedFieldOwnership(entityId, storeId, fieldPaths, ctx) {
|
|
444
|
+
return this.withMutationResult(null, ctx, async (txCtx) => {
|
|
445
|
+
const entity = await this.repository.findEntityById(entityId, txCtx);
|
|
446
|
+
if (!entity)
|
|
447
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
448
|
+
const rows = fieldPaths.map((fieldPath) => ({
|
|
449
|
+
organizationId: entity.organizationId,
|
|
450
|
+
entityId,
|
|
451
|
+
storeId,
|
|
452
|
+
fieldPath: validateFieldPath(fieldPath),
|
|
453
|
+
owner: "store",
|
|
454
|
+
}));
|
|
455
|
+
await this.repository.seedFieldOwnership(rows, txCtx);
|
|
456
|
+
return Ok(undefined);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
33
459
|
publish(id, actor, ctx) {
|
|
34
|
-
return this.
|
|
460
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
461
|
+
const result = await this.entities.publish(id, actor, txCtx);
|
|
462
|
+
if (result.ok)
|
|
463
|
+
await this.captureRevision(id, actor, "update", txCtx);
|
|
464
|
+
return result;
|
|
465
|
+
});
|
|
35
466
|
}
|
|
36
467
|
archive(id, actor, ctx) {
|
|
37
|
-
return this.
|
|
468
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
469
|
+
const result = await this.entities.archive(id, actor, txCtx);
|
|
470
|
+
if (result.ok)
|
|
471
|
+
await this.captureRevision(id, actor, "update", txCtx);
|
|
472
|
+
return result;
|
|
473
|
+
});
|
|
38
474
|
}
|
|
39
475
|
discontinue(id, actor, ctx) {
|
|
40
|
-
return this.
|
|
476
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
477
|
+
const result = await this.entities.discontinue(id, actor, txCtx);
|
|
478
|
+
if (result.ok)
|
|
479
|
+
await this.captureRevision(id, actor, "update", txCtx);
|
|
480
|
+
return result;
|
|
481
|
+
});
|
|
41
482
|
}
|
|
42
483
|
setAttributes(entityId, locale, attrs, actor, ctx) {
|
|
43
|
-
return this.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
484
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
485
|
+
const result = await this.entities.setAttributes(entityId, locale, attrs, actor, txCtx);
|
|
486
|
+
if (result.ok)
|
|
487
|
+
await this.captureRevision(entityId, actor, "update", txCtx);
|
|
488
|
+
return result;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
reviewCustomField(status, entityId, fieldName, locale, actor, ctx) {
|
|
492
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
493
|
+
const reviewer = actor ?? txCtx.actor;
|
|
494
|
+
assertPermission(reviewer, "catalog:update");
|
|
495
|
+
const entity = await this.repository.findEntityById(entityId, txCtx);
|
|
496
|
+
if (!entity)
|
|
497
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
498
|
+
if (entity.organizationId !== resolveOrgIdForCommerce(reviewer, this.config)) {
|
|
499
|
+
return Err(new CommerceNotFoundError("Entity not found."));
|
|
500
|
+
}
|
|
501
|
+
if (entity.sourceStoreId != null)
|
|
502
|
+
assertPermission(reviewer, "catalog:sync");
|
|
503
|
+
const proposal = await this.repository.findProposedCustomField(entityId, fieldName, locale, txCtx);
|
|
504
|
+
if (!proposal)
|
|
505
|
+
return Err(new CommerceNotFoundError("Custom field proposal not found."));
|
|
506
|
+
if (status === "approved") {
|
|
507
|
+
await this.repository.deleteCustomField(entityId, fieldName, locale, txCtx);
|
|
508
|
+
}
|
|
509
|
+
const updated = await this.repository.updateProposedCustomField(proposal.id, {
|
|
510
|
+
status,
|
|
511
|
+
...(status === "approved"
|
|
512
|
+
? { approvedAt: new Date(), approvedBy: reviewer?.userId ?? null }
|
|
513
|
+
: {}),
|
|
514
|
+
}, txCtx);
|
|
515
|
+
// Throw, never return Err, past this point: the approved-row delete above
|
|
516
|
+
// must roll back when the proposal was already resolved by a concurrent
|
|
517
|
+
// reviewer, or the live value is lost with no replacement.
|
|
518
|
+
if (!updated)
|
|
519
|
+
throw new CommerceNotFoundError("Custom field proposal not found.");
|
|
520
|
+
if (status === "approved") {
|
|
521
|
+
await this.repository.rejectOtherProposedCustomFields(entityId, fieldName, locale, updated.id, txCtx);
|
|
522
|
+
}
|
|
523
|
+
await this.captureRevision(entityId, reviewer, "update", txCtx);
|
|
524
|
+
return Ok(updated);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
approveCustomField(entityId, fieldName, locale, actor, ctx) {
|
|
528
|
+
return this.reviewCustomField("approved", entityId, fieldName, locale, actor, ctx);
|
|
529
|
+
}
|
|
530
|
+
rejectCustomField(entityId, fieldName, locale, actor, ctx) {
|
|
531
|
+
return this.reviewCustomField("rejected", entityId, fieldName, locale, actor, ctx);
|
|
532
|
+
}
|
|
533
|
+
getAttributes(entityId, locale, actor, ctx) {
|
|
534
|
+
return this.entities.getAttributes(entityId, locale, actor, ctx);
|
|
47
535
|
}
|
|
48
536
|
listCategories(ctx, opts) {
|
|
49
537
|
return this.categories.listCategories(ctx, opts);
|
|
@@ -64,10 +552,20 @@ export class CatalogServiceImpl {
|
|
|
64
552
|
return this.categories.deleteCategory(id, actor, ctx);
|
|
65
553
|
}
|
|
66
554
|
addToCategory(entityId, categoryId, actor, ctx) {
|
|
67
|
-
return this.
|
|
555
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
556
|
+
const result = await this.categories.addToCategory(entityId, categoryId, actor, txCtx);
|
|
557
|
+
if (result.ok)
|
|
558
|
+
await this.captureRevision(entityId, actor, "update", txCtx);
|
|
559
|
+
return result;
|
|
560
|
+
});
|
|
68
561
|
}
|
|
69
562
|
removeFromCategory(entityId, categoryId, actor, ctx) {
|
|
70
|
-
return this.
|
|
563
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
564
|
+
const result = await this.categories.removeFromCategory(entityId, categoryId, actor, txCtx);
|
|
565
|
+
if (result.ok)
|
|
566
|
+
await this.captureRevision(entityId, actor, "update", txCtx);
|
|
567
|
+
return result;
|
|
568
|
+
});
|
|
71
569
|
}
|
|
72
570
|
listBrands(ctx) {
|
|
73
571
|
return this.brands.listBrands(ctx);
|
|
@@ -82,10 +580,20 @@ export class CatalogServiceImpl {
|
|
|
82
580
|
return this.brands.deleteBrand(id, actor, ctx);
|
|
83
581
|
}
|
|
84
582
|
addToBrand(entityId, brandId, actor, ctx) {
|
|
85
|
-
return this.
|
|
583
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
584
|
+
const result = await this.brands.addToBrand(entityId, brandId, actor, txCtx);
|
|
585
|
+
if (result.ok)
|
|
586
|
+
await this.captureRevision(entityId, actor, "update", txCtx);
|
|
587
|
+
return result;
|
|
588
|
+
});
|
|
86
589
|
}
|
|
87
590
|
removeFromBrand(entityId, brandId, actor, ctx) {
|
|
88
|
-
return this.
|
|
591
|
+
return this.withMutationResult(actor, ctx, async (txCtx) => {
|
|
592
|
+
const result = await this.brands.removeFromBrand(entityId, brandId, actor, txCtx);
|
|
593
|
+
if (result.ok)
|
|
594
|
+
await this.captureRevision(entityId, actor, "update", txCtx);
|
|
595
|
+
return result;
|
|
596
|
+
});
|
|
89
597
|
}
|
|
90
598
|
createOptionType(input, actor, ctx) {
|
|
91
599
|
return this.entities.createOptionType(input, actor, ctx);
|