@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,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import type { Actor } from "../../auth/types.js";
|
|
3
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
3
4
|
import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
|
|
4
5
|
import type { PluginDb } from "../../kernel/database/plugin-types.js";
|
|
5
6
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
@@ -33,6 +34,7 @@ interface PricingServiceDeps {
|
|
|
33
34
|
repository: PricingRepository;
|
|
34
35
|
catalogRepository: CatalogRepository;
|
|
35
36
|
hooks: HookRegistry;
|
|
37
|
+
config: CommerceConfig;
|
|
36
38
|
services: Record<string, unknown>;
|
|
37
39
|
database: DatabaseAdapter;
|
|
38
40
|
}
|
|
@@ -41,6 +43,7 @@ function hookContext(
|
|
|
41
43
|
actor: Actor | null,
|
|
42
44
|
services: Record<string, unknown>,
|
|
43
45
|
database: DatabaseAdapter,
|
|
46
|
+
config: CommerceConfig,
|
|
44
47
|
tx: unknown,
|
|
45
48
|
): HookContext {
|
|
46
49
|
return createHookContext({
|
|
@@ -50,6 +53,7 @@ function hookContext(
|
|
|
50
53
|
services,
|
|
51
54
|
context: { moduleName: "pricing" },
|
|
52
55
|
database: { db: database.db as PluginDb },
|
|
56
|
+
commerceConfig: config,
|
|
53
57
|
});
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -211,7 +215,7 @@ export class PricingService {
|
|
|
211
215
|
);
|
|
212
216
|
}
|
|
213
217
|
|
|
214
|
-
const orgId =
|
|
218
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
215
219
|
const entity = await this.catalogRepo.findEntityById(input.entityId, ctx, orgId);
|
|
216
220
|
if (!entity) {
|
|
217
221
|
return Err(
|
|
@@ -224,6 +228,7 @@ export class PricingService {
|
|
|
224
228
|
entityId: input.entityId,
|
|
225
229
|
currency: normalizeCurrency(input.currency),
|
|
226
230
|
amount: input.amount,
|
|
231
|
+
...(input.compareAtAmount !== undefined ? { compareAtAmount: input.compareAtAmount } : {}),
|
|
227
232
|
metadata: input.metadata ?? {},
|
|
228
233
|
variantId: input.variantId ?? null,
|
|
229
234
|
customerGroupId: input.customerGroupId ?? null,
|
|
@@ -252,8 +257,12 @@ export class PricingService {
|
|
|
252
257
|
|
|
253
258
|
const record = existing
|
|
254
259
|
? (await this.repo.updatePrice(
|
|
255
|
-
|
|
256
|
-
{
|
|
260
|
+
existing.id,
|
|
261
|
+
{
|
|
262
|
+
amount: priceData.amount,
|
|
263
|
+
...(priceData.compareAtAmount !== undefined ? { compareAtAmount: priceData.compareAtAmount } : {}),
|
|
264
|
+
metadata: priceData.metadata,
|
|
265
|
+
},
|
|
257
266
|
ctx,
|
|
258
267
|
)) ?? existing
|
|
259
268
|
: await this.repo.createPrice(priceData, ctx);
|
|
@@ -262,10 +271,7 @@ export class PricingService {
|
|
|
262
271
|
"pricing.afterCreate",
|
|
263
272
|
) as AfterHook<Price>[];
|
|
264
273
|
const hctx = hookContext(
|
|
265
|
-
actor ?? ctx?.actor ?? null,
|
|
266
|
-
this.deps.services,
|
|
267
|
-
this.deps.database,
|
|
268
|
-
ctx?.tx ?? null,
|
|
274
|
+
actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
|
|
269
275
|
);
|
|
270
276
|
await runAfterHooks(afterHooks, null, record, "create", hctx);
|
|
271
277
|
|
|
@@ -296,7 +302,7 @@ export class PricingService {
|
|
|
296
302
|
);
|
|
297
303
|
}
|
|
298
304
|
|
|
299
|
-
const orgId =
|
|
305
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
300
306
|
|
|
301
307
|
const modifierData: PriceModifierInsert = {
|
|
302
308
|
organizationId: orgId,
|
|
@@ -322,10 +328,7 @@ export class PricingService {
|
|
|
322
328
|
"pricing.afterCreate",
|
|
323
329
|
) as AfterHook<PriceModifier>[];
|
|
324
330
|
const hctx = hookContext(
|
|
325
|
-
actor ?? ctx?.actor ?? null,
|
|
326
|
-
this.deps.services,
|
|
327
|
-
this.deps.database,
|
|
328
|
-
ctx?.tx ?? null,
|
|
331
|
+
actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
|
|
329
332
|
);
|
|
330
333
|
await runAfterHooks(afterHooks, null, modifier, "create", hctx);
|
|
331
334
|
|
|
@@ -337,7 +340,7 @@ export class PricingService {
|
|
|
337
340
|
actor?: Actor | null,
|
|
338
341
|
ctx?: TxContext,
|
|
339
342
|
): Promise<Result<PriceModifier[]>> {
|
|
340
|
-
const orgId =
|
|
343
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
341
344
|
const rows = await this.repo.findModifiers(
|
|
342
345
|
orgId,
|
|
343
346
|
{
|
|
@@ -367,7 +370,7 @@ export class PricingService {
|
|
|
367
370
|
actor?: Actor | null,
|
|
368
371
|
ctx?: TxContext,
|
|
369
372
|
): Promise<Result<PriceModifier>> {
|
|
370
|
-
const orgId =
|
|
373
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
371
374
|
const existing = await this.repo.findModifierById(orgId, id, ctx);
|
|
372
375
|
if (!existing) {
|
|
373
376
|
return Err(new CommerceNotFoundError("Price modifier not found."));
|
|
@@ -394,7 +397,7 @@ export class PricingService {
|
|
|
394
397
|
actor?: Actor | null,
|
|
395
398
|
ctx?: TxContext,
|
|
396
399
|
): Promise<Result<{ deleted: true }>> {
|
|
397
|
-
const orgId =
|
|
400
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
398
401
|
const existing = await this.repo.findModifierById(orgId, id, ctx);
|
|
399
402
|
if (!existing) {
|
|
400
403
|
return Err(new CommerceNotFoundError("Price modifier not found."));
|
|
@@ -413,7 +416,7 @@ export class PricingService {
|
|
|
413
416
|
actor?: Actor | null,
|
|
414
417
|
ctx?: TxContext,
|
|
415
418
|
): Promise<Result<{ prices: Price[]; modifiers: PriceModifier[] }>> {
|
|
416
|
-
const orgId =
|
|
419
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
417
420
|
// Get all prices for the entity if specified, or filter after retrieval
|
|
418
421
|
let prices: Price[] = [];
|
|
419
422
|
if (filter?.entityId) {
|
|
@@ -472,7 +475,7 @@ export class PricingService {
|
|
|
472
475
|
actor?: Actor | null,
|
|
473
476
|
ctx?: TxContext,
|
|
474
477
|
): Promise<Result<ResolvedPrice>> {
|
|
475
|
-
const orgId =
|
|
478
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
476
479
|
const entity = await this.catalogRepo.findEntityById(input.entityId, ctx, orgId);
|
|
477
480
|
if (!entity) {
|
|
478
481
|
return Err(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
1
2
|
import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
|
|
2
3
|
import { defineModule } from "../../kernel/module/index.js";
|
|
3
4
|
import type { CatalogServiceImpl } from "../catalog/service.js";
|
|
@@ -26,6 +27,7 @@ export const promotionsModule = defineModule<
|
|
|
26
27
|
catalogRepository: deps.services.catalog.repository,
|
|
27
28
|
ordersRepository: new OrdersRepository(deps.db.db as DrizzleDatabase),
|
|
28
29
|
hooks: deps.hooks,
|
|
30
|
+
config: deps.config as CommerceConfig,
|
|
29
31
|
services: deps.services,
|
|
30
32
|
database: deps.db,
|
|
31
33
|
}),
|
|
@@ -19,8 +19,9 @@ import type {
|
|
|
19
19
|
} from "./repository/index.js";
|
|
20
20
|
import type { CatalogRepository } from "../catalog/repository/index.js";
|
|
21
21
|
import type { OrdersRepository } from "../orders/repository/index.js";
|
|
22
|
-
import { resolveOrgId } from "../../auth/org.js";
|
|
22
|
+
import { resolveOrgId, resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
23
23
|
import type { Actor } from "../../auth/types.js";
|
|
24
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
24
25
|
|
|
25
26
|
// PromotionType is derived from the zod enum (single source of truth) in
|
|
26
27
|
// ./schemas.js; re-export it here for backwards-compatible imports.
|
|
@@ -35,6 +36,7 @@ interface PromotionServiceDeps {
|
|
|
35
36
|
catalogRepository: CatalogRepository;
|
|
36
37
|
ordersRepository: OrdersRepository;
|
|
37
38
|
hooks: HookRegistry;
|
|
39
|
+
config: CommerceConfig;
|
|
38
40
|
services: Record<string, unknown>;
|
|
39
41
|
database: DatabaseAdapter;
|
|
40
42
|
}
|
|
@@ -43,6 +45,7 @@ function hookContext(
|
|
|
43
45
|
actor: Actor | null,
|
|
44
46
|
services: Record<string, unknown>,
|
|
45
47
|
database: DatabaseAdapter,
|
|
48
|
+
config: CommerceConfig,
|
|
46
49
|
tx: unknown,
|
|
47
50
|
): HookContext {
|
|
48
51
|
return createHookContext({
|
|
@@ -52,6 +55,7 @@ function hookContext(
|
|
|
52
55
|
services,
|
|
53
56
|
context: { moduleName: "promotions" },
|
|
54
57
|
database: { db: database.db as PluginDb },
|
|
58
|
+
commerceConfig: config,
|
|
55
59
|
});
|
|
56
60
|
}
|
|
57
61
|
|
|
@@ -165,6 +169,16 @@ export class PromotionService {
|
|
|
165
169
|
this.ordersRepo = deps.ordersRepository;
|
|
166
170
|
}
|
|
167
171
|
|
|
172
|
+
private resolveEvaluationOrgId(
|
|
173
|
+
ctx: TxContext | undefined,
|
|
174
|
+
explicitOrgId?: string,
|
|
175
|
+
): string {
|
|
176
|
+
if (explicitOrgId) {
|
|
177
|
+
return resolveOrgId(ctx?.actor ?? null, explicitOrgId);
|
|
178
|
+
}
|
|
179
|
+
return resolveOrgIdForCommerce(ctx?.actor ?? null, this.deps.config);
|
|
180
|
+
}
|
|
181
|
+
|
|
168
182
|
async create(
|
|
169
183
|
input: CreatePromotionInput,
|
|
170
184
|
actor?: Actor | null,
|
|
@@ -189,7 +203,7 @@ export class PromotionService {
|
|
|
189
203
|
);
|
|
190
204
|
}
|
|
191
205
|
|
|
192
|
-
const orgId =
|
|
206
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
193
207
|
|
|
194
208
|
if (input.code) {
|
|
195
209
|
const normalized = input.code.trim().toUpperCase();
|
|
@@ -229,10 +243,7 @@ export class PromotionService {
|
|
|
229
243
|
"promotions.afterCreate",
|
|
230
244
|
) as AfterHook<Promotion>[];
|
|
231
245
|
const hctx = hookContext(
|
|
232
|
-
actor ?? ctx?.actor ?? null,
|
|
233
|
-
this.deps.services,
|
|
234
|
-
this.deps.database,
|
|
235
|
-
ctx?.tx ?? null,
|
|
246
|
+
actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
|
|
236
247
|
);
|
|
237
248
|
await runAfterHooks(afterHooks, null, promotion, "create", hctx);
|
|
238
249
|
|
|
@@ -253,7 +264,8 @@ export class PromotionService {
|
|
|
253
264
|
const afterHooks = this.deps.hooks.resolve(
|
|
254
265
|
"promotions.afterUpdate",
|
|
255
266
|
) as AfterHook<Promotion>[];
|
|
256
|
-
|
|
267
|
+
// Actor-less by design; resolves to the deployment's declared organization.
|
|
268
|
+
const hctx = hookContext(null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null);
|
|
257
269
|
await runAfterHooks(afterHooks, promotion, updated, "update", hctx);
|
|
258
270
|
|
|
259
271
|
return Ok(updated);
|
|
@@ -331,10 +343,7 @@ export class PromotionService {
|
|
|
331
343
|
"promotions.afterUpdate",
|
|
332
344
|
) as AfterHook<Promotion>[];
|
|
333
345
|
const hctx = hookContext(
|
|
334
|
-
actor ?? ctx?.actor ?? null,
|
|
335
|
-
this.deps.services,
|
|
336
|
-
this.deps.database,
|
|
337
|
-
ctx?.tx ?? null,
|
|
346
|
+
actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, this.deps.config, ctx?.tx ?? null,
|
|
338
347
|
);
|
|
339
348
|
await runAfterHooks(afterHooks, existing, updated, "update", hctx);
|
|
340
349
|
|
|
@@ -346,7 +355,7 @@ export class PromotionService {
|
|
|
346
355
|
actor?: Actor | null,
|
|
347
356
|
ctx?: TxContext,
|
|
348
357
|
): Promise<Result<Promotion[]>> {
|
|
349
|
-
const orgId =
|
|
358
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
350
359
|
const all = await this.repo.findAll(orgId, ctx);
|
|
351
360
|
const now = new Date();
|
|
352
361
|
|
|
@@ -375,7 +384,7 @@ export class PromotionService {
|
|
|
375
384
|
timestamp = new Date(),
|
|
376
385
|
ctx?: TxContext,
|
|
377
386
|
): Promise<Result<Promotion[]>> {
|
|
378
|
-
const orgId =
|
|
387
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.deps.config);
|
|
379
388
|
const active = await this.repo.findActive(orgId, ctx);
|
|
380
389
|
// Further filter by timestamp in case repo returns slightly stale results
|
|
381
390
|
const filtered = active.filter((p) => matchesWindow(p, timestamp));
|
|
@@ -388,7 +397,7 @@ export class PromotionService {
|
|
|
388
397
|
ctx?: TxContext,
|
|
389
398
|
): Promise<Result<Promotion>> {
|
|
390
399
|
const normalized = code.trim().toUpperCase();
|
|
391
|
-
const evalOrgId =
|
|
400
|
+
const evalOrgId = this.resolveEvaluationOrgId(ctx, context.orgId);
|
|
392
401
|
const promotion = await this.repo.findByCode(evalOrgId, normalized, ctx);
|
|
393
402
|
if (!promotion) {
|
|
394
403
|
return Err(
|
|
@@ -434,7 +443,7 @@ export class PromotionService {
|
|
|
434
443
|
ctx?: TxContext,
|
|
435
444
|
): Promise<Result<PromotionApplicationResult>> {
|
|
436
445
|
const timestamp = context.timestamp ?? new Date();
|
|
437
|
-
const applyOrgId =
|
|
446
|
+
const applyOrgId = this.resolveEvaluationOrgId(ctx, context.orgId);
|
|
438
447
|
const result: PromotionApplicationResult = {
|
|
439
448
|
totalDiscount: 0,
|
|
440
449
|
freeShipping: false,
|
|
@@ -580,7 +589,7 @@ export class PromotionService {
|
|
|
580
589
|
return "First-order promotion requires authenticated customer.";
|
|
581
590
|
}
|
|
582
591
|
const orders = await this.ordersRepo.findByCustomerId(
|
|
583
|
-
|
|
592
|
+
this.resolveEvaluationOrgId(ctx, context.orgId),
|
|
584
593
|
context.customerId,
|
|
585
594
|
ctx,
|
|
586
595
|
);
|
|
@@ -2,22 +2,28 @@ import type { Result } from "../../kernel/result.js";
|
|
|
2
2
|
|
|
3
3
|
export interface SearchDocument {
|
|
4
4
|
id: string;
|
|
5
|
+
organizationId?: string;
|
|
5
6
|
type: string;
|
|
6
7
|
slug: string;
|
|
7
8
|
title: string;
|
|
8
9
|
description?: string;
|
|
9
10
|
status?: string;
|
|
11
|
+
isVisible?: boolean;
|
|
10
12
|
categories: string[];
|
|
11
13
|
brands: string[];
|
|
12
14
|
text: string;
|
|
15
|
+
attributes?: Record<string, string | string[]>;
|
|
13
16
|
payload?: Record<string, unknown>;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export interface SearchFilters {
|
|
20
|
+
/** Server-supplied tenant constraint; REST callers cannot choose this value. */
|
|
21
|
+
organizationId?: string;
|
|
17
22
|
type?: string;
|
|
18
23
|
category?: string;
|
|
19
24
|
brand?: string;
|
|
20
25
|
status?: string;
|
|
26
|
+
attributes?: Record<string, string | string[]>;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export interface SearchQueryParams {
|
|
@@ -32,6 +38,8 @@ export interface SearchSuggestParams {
|
|
|
32
38
|
prefix: string;
|
|
33
39
|
type?: string;
|
|
34
40
|
limit?: number;
|
|
41
|
+
/** Server-supplied tenant constraint; REST callers cannot choose this value. */
|
|
42
|
+
organizationId?: string;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
export interface SearchHit {
|
|
@@ -16,6 +16,9 @@ export const searchModule = defineModule<Record<string, never>, SearchService, S
|
|
|
16
16
|
const config = deps.config as CommerceConfig;
|
|
17
17
|
return new SearchService({
|
|
18
18
|
catalogRepository: deps.services.catalog.repository,
|
|
19
|
+
resolveEntityFieldDefinitions: deps.services.catalog.resolveEntityFieldDefinitions.bind(deps.services.catalog),
|
|
20
|
+
config,
|
|
21
|
+
...(config.entities ? { entities: config.entities } : {}),
|
|
19
22
|
...(config.search?.adapter ? { adapter: config.search.adapter } : {}),
|
|
20
23
|
...(config.search?.defaultFacets
|
|
21
24
|
? { defaultFacets: config.search.defaultFacets }
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { Ok, type Result } from "../../kernel/result.js";
|
|
2
|
-
import {
|
|
1
|
+
import { Err, Ok, type Result } from "../../kernel/result.js";
|
|
2
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
3
|
+
import { OrgResolutionError } from "../../kernel/errors.js";
|
|
3
4
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
4
|
-
import type {
|
|
5
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
6
|
+
import type {
|
|
7
|
+
CatalogRepository,
|
|
8
|
+
SellableCustomField,
|
|
9
|
+
SellableEntity,
|
|
10
|
+
} from "../catalog/repository/index.js";
|
|
5
11
|
import type {
|
|
6
12
|
SearchAdapter,
|
|
7
13
|
SearchDocument,
|
|
@@ -10,9 +16,14 @@ import type {
|
|
|
10
16
|
SearchQueryResult,
|
|
11
17
|
SearchSuggestParams,
|
|
12
18
|
} from "./adapter.js";
|
|
19
|
+
import type { EntityFieldDefinitionResolver } from "../catalog/service.js";
|
|
20
|
+
import { canReadUnpublishedCatalog } from "../catalog/read-policy.js";
|
|
13
21
|
|
|
14
22
|
interface SearchServiceDeps {
|
|
15
23
|
catalogRepository: CatalogRepository;
|
|
24
|
+
config: CommerceConfig;
|
|
25
|
+
entities?: CommerceConfig["entities"];
|
|
26
|
+
resolveEntityFieldDefinitions?: EntityFieldDefinitionResolver;
|
|
16
27
|
adapter?: SearchAdapter;
|
|
17
28
|
defaultFacets?: string[];
|
|
18
29
|
}
|
|
@@ -39,6 +50,19 @@ function includesAllTokens(haystack: string, tokens: string[]): boolean {
|
|
|
39
50
|
return tokens.every((token) => lower.includes(token));
|
|
40
51
|
}
|
|
41
52
|
|
|
53
|
+
function customFieldValue(field: SellableCustomField): string | undefined {
|
|
54
|
+
if (field.textValue != null) return field.textValue;
|
|
55
|
+
if (field.numberValue != null) return String(field.numberValue);
|
|
56
|
+
if (field.booleanValue != null) return String(field.booleanValue);
|
|
57
|
+
if (field.dateValue != null) return field.dateValue.toISOString();
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function attributeValues(value: string | string[] | undefined): string[] {
|
|
62
|
+
if (value == null) return [];
|
|
63
|
+
return Array.isArray(value) ? value : [value];
|
|
64
|
+
}
|
|
65
|
+
|
|
42
66
|
function scoreText(document: SearchDocument, tokens: string[]): number {
|
|
43
67
|
if (tokens.length === 0) return 1;
|
|
44
68
|
|
|
@@ -109,16 +133,43 @@ export class SearchService {
|
|
|
109
133
|
entity: SellableEntity,
|
|
110
134
|
ctx?: TxContext,
|
|
111
135
|
): Promise<SearchDocument> {
|
|
112
|
-
const
|
|
136
|
+
const localizedAttributes =
|
|
113
137
|
await this.deps.catalogRepository.findAttributesByEntityId(
|
|
114
138
|
entity.id,
|
|
115
139
|
ctx,
|
|
116
140
|
);
|
|
117
|
-
const primary =
|
|
141
|
+
const primary = localizedAttributes[0];
|
|
118
142
|
const title = primary?.title ?? entity.slug;
|
|
119
143
|
const description = primary?.description;
|
|
120
144
|
const categories = await this.entityCategories(entity.id, ctx);
|
|
121
145
|
const brands = await this.entityBrands(entity.id, ctx);
|
|
146
|
+
const definitions = this.deps.resolveEntityFieldDefinitions
|
|
147
|
+
? await this.deps.resolveEntityFieldDefinitions(entity.type, entity.organizationId, ctx)
|
|
148
|
+
: (this.deps.entities?.[entity.type]?.fields ?? []);
|
|
149
|
+
const filterableFields = new Set(
|
|
150
|
+
definitions
|
|
151
|
+
.filter((field) => field.filterable === true)
|
|
152
|
+
.map((field) => field.name),
|
|
153
|
+
);
|
|
154
|
+
const customFields = await this.deps.catalogRepository.findCustomFieldsByEntityId(
|
|
155
|
+
entity.id,
|
|
156
|
+
ctx,
|
|
157
|
+
);
|
|
158
|
+
const attributeValuesByName = new Map<string, string[]>();
|
|
159
|
+
for (const field of customFields) {
|
|
160
|
+
if (field.locale !== "en" || !filterableFields.has(field.fieldName)) continue;
|
|
161
|
+
const value = customFieldValue(field);
|
|
162
|
+
if (value == null) continue;
|
|
163
|
+
const values = attributeValuesByName.get(field.fieldName) ?? [];
|
|
164
|
+
if (!values.includes(value)) values.push(value);
|
|
165
|
+
attributeValuesByName.set(field.fieldName, values);
|
|
166
|
+
}
|
|
167
|
+
const attributes = Object.fromEntries(
|
|
168
|
+
[...attributeValuesByName.entries()].map(([name, values]) => [
|
|
169
|
+
name,
|
|
170
|
+
values.length === 1 ? values[0]! : values,
|
|
171
|
+
]),
|
|
172
|
+
) as Record<string, string | string[]>;
|
|
122
173
|
|
|
123
174
|
const textParts: string[] = [
|
|
124
175
|
entity.slug,
|
|
@@ -126,20 +177,23 @@ export class SearchService {
|
|
|
126
177
|
description ?? "",
|
|
127
178
|
...categories,
|
|
128
179
|
...brands,
|
|
129
|
-
...
|
|
130
|
-
...
|
|
180
|
+
...localizedAttributes.map((attr) => attr.title),
|
|
181
|
+
...localizedAttributes.map((attr) => attr.description ?? ""),
|
|
131
182
|
];
|
|
132
183
|
|
|
133
184
|
return {
|
|
134
185
|
id: entity.id,
|
|
186
|
+
organizationId: entity.organizationId,
|
|
135
187
|
type: entity.type,
|
|
136
188
|
slug: entity.slug,
|
|
137
189
|
title,
|
|
138
190
|
...(description ? { description } : {}),
|
|
139
191
|
status: entity.status,
|
|
192
|
+
isVisible: entity.isVisible,
|
|
140
193
|
categories,
|
|
141
194
|
brands,
|
|
142
195
|
text: textParts.join(" ").trim(),
|
|
196
|
+
...(Object.keys(attributes).length > 0 ? { attributes } : {}),
|
|
143
197
|
payload: {
|
|
144
198
|
metadata: entity.metadata ?? undefined,
|
|
145
199
|
},
|
|
@@ -147,10 +201,18 @@ export class SearchService {
|
|
|
147
201
|
}
|
|
148
202
|
|
|
149
203
|
private async allDocuments(ctx?: TxContext): Promise<SearchDocument[]> {
|
|
150
|
-
const
|
|
204
|
+
const actor = ctx?.actor;
|
|
205
|
+
if (!actor) {
|
|
206
|
+
// Deliberate exception: this needs an authenticated actor, not merely a
|
|
207
|
+
// resolvable organization, because visibility scoping is decided from the
|
|
208
|
+
// actor's permissions. A declared deployment default would satisfy the
|
|
209
|
+
// organization but not that.
|
|
210
|
+
throw new OrgResolutionError("Organization could not be resolved for a search read.");
|
|
211
|
+
}
|
|
212
|
+
const orgId = resolveOrgIdForCommerce(actor, this.deps.config);
|
|
151
213
|
const entities = await this.deps.catalogRepository.findEntities(
|
|
152
214
|
orgId,
|
|
153
|
-
undefined,
|
|
215
|
+
canReadUnpublishedCatalog(actor) ? undefined : { status: "active", isVisible: true },
|
|
154
216
|
ctx,
|
|
155
217
|
);
|
|
156
218
|
return Promise.all(
|
|
@@ -163,11 +225,17 @@ export class SearchService {
|
|
|
163
225
|
filters: SearchFilters | undefined,
|
|
164
226
|
): boolean {
|
|
165
227
|
if (!filters) return true;
|
|
228
|
+
if (filters.organizationId && document.organizationId !== filters.organizationId) return false;
|
|
166
229
|
if (filters.type && document.type !== filters.type) return false;
|
|
167
230
|
if (filters.status && document.status !== filters.status) return false;
|
|
168
231
|
if (filters.category && !document.categories.includes(filters.category))
|
|
169
232
|
return false;
|
|
170
233
|
if (filters.brand && !document.brands.includes(filters.brand)) return false;
|
|
234
|
+
for (const [name, requested] of Object.entries(filters.attributes ?? {})) {
|
|
235
|
+
const actual = attributeValues(document.attributes?.[name]);
|
|
236
|
+
const expected = attributeValues(requested);
|
|
237
|
+
if (!expected.some((value) => actual.includes(value))) return false;
|
|
238
|
+
}
|
|
171
239
|
return true;
|
|
172
240
|
}
|
|
173
241
|
|
|
@@ -214,6 +282,26 @@ export class SearchService {
|
|
|
214
282
|
}
|
|
215
283
|
}
|
|
216
284
|
}
|
|
285
|
+
|
|
286
|
+
const attributeName = facet.startsWith("attributes.")
|
|
287
|
+
? facet.slice("attributes.".length)
|
|
288
|
+
: facet;
|
|
289
|
+
if (
|
|
290
|
+
attributeName !== "type" &&
|
|
291
|
+
attributeName !== "status" &&
|
|
292
|
+
attributeName !== "category" &&
|
|
293
|
+
attributeName !== "categories" &&
|
|
294
|
+
attributeName !== "brand" &&
|
|
295
|
+
attributeName !== "brands"
|
|
296
|
+
) {
|
|
297
|
+
const values: Record<string, number> = {};
|
|
298
|
+
for (const document of documents) {
|
|
299
|
+
for (const value of new Set(attributeValues(document.attributes?.[attributeName]))) {
|
|
300
|
+
values[value] = (values[value] ?? 0) + 1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (Object.keys(values).length > 0) output[attributeName] = values;
|
|
304
|
+
}
|
|
217
305
|
}
|
|
218
306
|
|
|
219
307
|
return output;
|
|
@@ -244,18 +332,49 @@ export class SearchService {
|
|
|
244
332
|
const safeQuery = (params.query ?? "").slice(0, 500);
|
|
245
333
|
|
|
246
334
|
if (this.deps.adapter) {
|
|
247
|
-
|
|
335
|
+
const actor = ctx?.actor ?? null;
|
|
336
|
+
// Deliberate exception: this needs an authenticated actor, not merely a
|
|
337
|
+
// resolvable organization, because visibility scoping is decided from the
|
|
338
|
+
// actor's permissions. A declared deployment default would satisfy the
|
|
339
|
+
// organization but not that.
|
|
340
|
+
if (!actor) return Err(new OrgResolutionError("Organization could not be resolved for a search read."));
|
|
341
|
+
const organizationId = resolveOrgIdForCommerce(actor, this.deps.config);
|
|
342
|
+
const result = await this.deps.adapter.search({
|
|
248
343
|
...params,
|
|
249
344
|
query: safeQuery,
|
|
250
345
|
page,
|
|
251
346
|
limit,
|
|
347
|
+
filters: {
|
|
348
|
+
...params.filters,
|
|
349
|
+
organizationId,
|
|
350
|
+
...(!canReadUnpublishedCatalog(actor) ? { status: "active" } : {}),
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
if (!result.ok) return result;
|
|
354
|
+
const hits = result.value.hits.filter((hit) =>
|
|
355
|
+
hit.document.organizationId === organizationId &&
|
|
356
|
+
(canReadUnpublishedCatalog(actor) ||
|
|
357
|
+
(hit.document.status === "active" && hit.document.isVisible !== false)),
|
|
358
|
+
);
|
|
359
|
+
return Ok({
|
|
360
|
+
...result.value,
|
|
361
|
+
hits,
|
|
362
|
+
total: hits.length,
|
|
363
|
+
facets: this.computeFacets(hits.map((hit) => hit.document), params.facets),
|
|
252
364
|
});
|
|
253
365
|
}
|
|
254
366
|
|
|
255
367
|
// In-memory fallback: require a non-empty query to avoid loading all entities
|
|
256
368
|
// (allDocuments triggers N+1 DB queries per entity — unsafe at scale)
|
|
257
369
|
const tokens = tokenize(safeQuery);
|
|
258
|
-
if (
|
|
370
|
+
if (
|
|
371
|
+
tokens.length === 0 &&
|
|
372
|
+
!params.filters?.type &&
|
|
373
|
+
!params.filters?.category &&
|
|
374
|
+
!params.filters?.brand &&
|
|
375
|
+
!params.filters?.status &&
|
|
376
|
+
Object.keys(params.filters?.attributes ?? {}).length === 0
|
|
377
|
+
) {
|
|
259
378
|
return Ok({ hits: [], total: 0, page, limit, facets: {} });
|
|
260
379
|
}
|
|
261
380
|
|
|
@@ -309,11 +428,22 @@ export class SearchService {
|
|
|
309
428
|
if (prefix.length === 0) return Ok([]);
|
|
310
429
|
|
|
311
430
|
if (this.deps.adapter) {
|
|
312
|
-
|
|
431
|
+
const actor = ctx?.actor ?? null;
|
|
432
|
+
// Deliberate exception: this needs an authenticated actor, not merely a
|
|
433
|
+
// resolvable organization, because visibility scoping is decided from the
|
|
434
|
+
// actor's permissions. A declared deployment default would satisfy the
|
|
435
|
+
// organization but not that.
|
|
436
|
+
if (!actor) return Err(new OrgResolutionError("Organization could not be resolved for a search read."));
|
|
437
|
+
const organizationId = resolveOrgIdForCommerce(actor, this.deps.config);
|
|
438
|
+
const result = await this.deps.adapter.suggest({
|
|
313
439
|
...params,
|
|
314
440
|
prefix,
|
|
315
441
|
limit,
|
|
442
|
+
organizationId,
|
|
316
443
|
});
|
|
444
|
+
if (!result.ok) return result;
|
|
445
|
+
const visibleTitles = new Set((await this.allDocuments(ctx)).map((document) => document.title));
|
|
446
|
+
return Ok(result.value.filter((title) => visibleTitles.has(title)));
|
|
317
447
|
}
|
|
318
448
|
|
|
319
449
|
// In-memory fallback: require prefix >= 2 chars to avoid loading all entities
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
1
2
|
import type { DrizzleDatabase } from "../../kernel/database/drizzle-db.js";
|
|
2
3
|
import { defineModule } from "../../kernel/module/index.js";
|
|
3
4
|
import { SettingsRepository } from "./repository/index.js";
|
|
@@ -14,5 +15,6 @@ export const settingsModule = defineModule<
|
|
|
14
15
|
service: (deps) =>
|
|
15
16
|
new SettingsService({
|
|
16
17
|
repository: new SettingsRepository(deps.db.db as DrizzleDatabase),
|
|
18
|
+
config: deps.config as CommerceConfig,
|
|
17
19
|
}),
|
|
18
20
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveOrgIdForCommerce } from "../../auth/org.js";
|
|
2
2
|
import type { Actor } from "../../auth/types.js";
|
|
3
|
+
import type { CommerceConfig } from "../../config/types.js";
|
|
3
4
|
import type { TxContext } from "../../kernel/database/tx-context.js";
|
|
4
5
|
import { CommerceValidationError } from "../../kernel/errors.js";
|
|
5
6
|
import { Err, Ok, type Result } from "../../kernel/result.js";
|
|
@@ -9,6 +10,7 @@ const GROUP_NAME_RE = /^[a-z][a-z0-9_-]*$/;
|
|
|
9
10
|
|
|
10
11
|
interface SettingsServiceDeps {
|
|
11
12
|
repository: SettingsRepository;
|
|
13
|
+
config: CommerceConfig;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -20,9 +22,11 @@ interface SettingsServiceDeps {
|
|
|
20
22
|
*/
|
|
21
23
|
export class SettingsService {
|
|
22
24
|
private repository: SettingsRepository;
|
|
25
|
+
private config: CommerceConfig;
|
|
23
26
|
|
|
24
27
|
constructor(deps: SettingsServiceDeps) {
|
|
25
28
|
this.repository = deps.repository;
|
|
29
|
+
this.config = deps.config;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
/** Runtime read for plugins/hooks. Unset groups read as `{}`. */
|
|
@@ -40,7 +44,7 @@ export class SettingsService {
|
|
|
40
44
|
actor?: Actor | null,
|
|
41
45
|
ctx?: TxContext,
|
|
42
46
|
): Promise<Result<Record<string, unknown>>> {
|
|
43
|
-
const orgId =
|
|
47
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
44
48
|
return Ok(await this.read(orgId, group, ctx));
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -48,7 +52,7 @@ export class SettingsService {
|
|
|
48
52
|
actor?: Actor | null,
|
|
49
53
|
ctx?: TxContext,
|
|
50
54
|
): Promise<Result<Record<string, Record<string, unknown>>>> {
|
|
51
|
-
const orgId =
|
|
55
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
52
56
|
const rows = await this.repository.findAll(orgId, ctx);
|
|
53
57
|
const all: Record<string, Record<string, unknown>> = {};
|
|
54
58
|
for (const row of rows) all[row.group] = row.value;
|
|
@@ -69,7 +73,7 @@ export class SettingsService {
|
|
|
69
73
|
),
|
|
70
74
|
);
|
|
71
75
|
}
|
|
72
|
-
const orgId =
|
|
76
|
+
const orgId = resolveOrgIdForCommerce(actor ?? ctx?.actor ?? null, this.config);
|
|
73
77
|
const current = await this.read(orgId, group, ctx);
|
|
74
78
|
const next: Record<string, unknown> = { ...current };
|
|
75
79
|
for (const [key, value] of Object.entries(patch)) {
|