@porulle/core 0.10.8 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/auth-schema-guard.d.ts +2444 -0
- package/dist/auth/auth-schema-guard.d.ts.map +1 -0
- package/dist/auth/auth-schema-guard.js +113 -0
- package/dist/auth/auth-schema.d.ts +228 -0
- package/dist/auth/auth-schema.d.ts.map +1 -1
- package/dist/auth/auth-schema.js +24 -2
- package/dist/auth/middleware.d.ts +1 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +13 -5
- package/dist/auth/org.d.ts +8 -2
- package/dist/auth/org.d.ts.map +1 -1
- package/dist/auth/org.js +12 -4
- package/dist/auth/organization-guard.d.ts +5 -0
- package/dist/auth/organization-guard.d.ts.map +1 -0
- package/dist/auth/organization-guard.js +124 -0
- package/dist/auth/permissions.d.ts +10 -0
- package/dist/auth/permissions.d.ts.map +1 -1
- package/dist/auth/permissions.js +31 -7
- package/dist/auth/role-authority.d.ts +44 -0
- package/dist/auth/role-authority.d.ts.map +1 -0
- package/dist/auth/role-authority.js +85 -0
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +17 -0
- package/dist/auth/strict-org-resolution.d.ts.map +1 -1
- package/dist/auth/strict-org-resolution.js +9 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/types.d.ts +33 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +10 -9
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/interfaces/rest/audit-middleware.d.ts.map +1 -1
- package/dist/interfaces/rest/audit-middleware.js +2 -0
- package/dist/interfaces/rest/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/customer-portal.js +9 -8
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +4 -0
- package/dist/interfaces/rest/route-coverage.d.ts +14 -0
- package/dist/interfaces/rest/route-coverage.d.ts.map +1 -0
- package/dist/interfaces/rest/route-coverage.js +81 -0
- package/dist/interfaces/rest/router.d.ts +7 -2
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/router.js +8 -4
- package/dist/interfaces/rest/routes/admin/compensation-failures.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/compensation-failures.js +7 -5
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
- package/dist/interfaces/rest/routes/admin/permissions.d.ts +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/permissions.js +2 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/admin/staff.js +126 -66
- package/dist/interfaces/rest/routes/admin-jobs.js +3 -3
- package/dist/interfaces/rest/routes/audit.js +3 -3
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +13 -10
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +47 -4
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +30 -35
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/documents.js +10 -5
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +2 -0
- package/dist/interfaces/rest/routes/media.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/media.js +7 -3
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +26 -8
- package/dist/interfaces/rest/routes/pricing.js +2 -2
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +13 -10
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +14 -1
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +7 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +86 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.d.ts +204 -0
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/responses.d.ts +71 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +1 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.js +1 -0
- package/dist/interfaces/rest/utils.d.ts +15 -0
- package/dist/interfaces/rest/utils.d.ts.map +1 -1
- package/dist/interfaces/rest/utils.js +45 -2
- package/dist/kernel/compensation/executor.js +2 -2
- package/dist/kernel/database/tx-context.d.ts +8 -0
- package/dist/kernel/database/tx-context.d.ts.map +1 -1
- package/dist/kernel/database/tx-context.js +13 -0
- package/dist/kernel/hooks/create-context.d.ts +2 -0
- package/dist/kernel/hooks/create-context.d.ts.map +1 -1
- package/dist/kernel/hooks/create-context.js +1 -0
- package/dist/kernel/hooks/types.d.ts +2 -0
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +9 -5
- package/dist/modules/analytics/service.js +2 -2
- package/dist/modules/audit/service.js +3 -3
- package/dist/modules/cart/repository/index.d.ts +13 -7
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +16 -10
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +4 -0
- package/dist/modules/cart/service.d.ts +22 -9
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +57 -33
- package/dist/modules/catalog/brand-service.js +7 -7
- package/dist/modules/catalog/category-service.js +7 -7
- package/dist/modules/catalog/entity-service.d.ts +19 -10
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +180 -51
- package/dist/modules/catalog/module.d.ts +6 -1
- package/dist/modules/catalog/module.d.ts.map +1 -1
- package/dist/modules/catalog/module.js +6 -1
- package/dist/modules/catalog/ownership.d.ts +5 -0
- package/dist/modules/catalog/ownership.d.ts.map +1 -0
- package/dist/modules/catalog/ownership.js +11 -0
- package/dist/modules/catalog/read-policy.d.ts +9 -0
- package/dist/modules/catalog/read-policy.d.ts.map +1 -0
- package/dist/modules/catalog/read-policy.js +8 -0
- package/dist/modules/catalog/repository/index.d.ts +55 -3
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +335 -6
- package/dist/modules/catalog/revision.d.ts +4 -0
- package/dist/modules/catalog/revision.d.ts.map +1 -0
- package/dist/modules/catalog/revision.js +33 -0
- package/dist/modules/catalog/schema.d.ts +979 -110
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +112 -2
- package/dist/modules/catalog/schemas.d.ts +2 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +2 -0
- package/dist/modules/catalog/service.d.ts +80 -15
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +522 -14
- package/dist/modules/channels/adapter.d.ts +85 -0
- package/dist/modules/channels/adapter.d.ts.map +1 -1
- package/dist/modules/channels/adapter.js +9 -1
- package/dist/modules/customers/module.d.ts.map +1 -1
- package/dist/modules/customers/module.js +1 -0
- package/dist/modules/customers/service.d.ts +2 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +21 -20
- package/dist/modules/documents/module.d.ts.map +1 -1
- package/dist/modules/documents/module.js +1 -0
- package/dist/modules/documents/service.d.ts +18 -4
- package/dist/modules/documents/service.d.ts.map +1 -1
- package/dist/modules/documents/service.js +54 -16
- package/dist/modules/fulfillment/module.d.ts.map +1 -1
- package/dist/modules/fulfillment/module.js +1 -0
- package/dist/modules/fulfillment/service.d.ts +2 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +15 -6
- package/dist/modules/inventory/service.d.ts.map +1 -1
- package/dist/modules/inventory/service.js +15 -14
- package/dist/modules/media/module.d.ts +68 -0
- package/dist/modules/media/module.d.ts.map +1 -1
- package/dist/modules/media/module.js +2 -0
- package/dist/modules/media/schema.d.ts +70 -0
- package/dist/modules/media/schema.d.ts.map +1 -1
- package/dist/modules/media/schema.js +20 -2
- package/dist/modules/media/service.d.ts +8 -1
- package/dist/modules/media/service.d.ts.map +1 -1
- package/dist/modules/media/service.js +71 -23
- package/dist/modules/orders/guest-access.d.ts +27 -0
- package/dist/modules/orders/guest-access.d.ts.map +1 -0
- package/dist/modules/orders/guest-access.js +46 -0
- package/dist/modules/orders/module.d.ts.map +1 -1
- package/dist/modules/orders/module.js +1 -0
- package/dist/modules/orders/quote.d.ts.map +1 -1
- package/dist/modules/orders/quote.js +1 -0
- package/dist/modules/orders/repository/index.d.ts +1 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +2 -2
- package/dist/modules/orders/schema.d.ts +17 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +5 -3
- package/dist/modules/orders/service.d.ts +16 -7
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +184 -55
- package/dist/modules/pricing/module.d.ts.map +1 -1
- package/dist/modules/pricing/module.js +1 -0
- package/dist/modules/pricing/schema.d.ts +17 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -1
- package/dist/modules/pricing/schema.js +1 -0
- package/dist/modules/pricing/schemas.d.ts +1 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -1
- package/dist/modules/pricing/schemas.js +1 -0
- package/dist/modules/pricing/service.d.ts +2 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +18 -12
- package/dist/modules/promotions/module.d.ts.map +1 -1
- package/dist/modules/promotions/module.js +1 -0
- package/dist/modules/promotions/service.d.ts +3 -0
- package/dist/modules/promotions/service.d.ts.map +1 -1
- package/dist/modules/promotions/service.js +19 -11
- package/dist/modules/search/adapter.d.ts +8 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +3 -0
- package/dist/modules/search/service.d.ts +5 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +131 -11
- package/dist/modules/settings/module.d.ts.map +1 -1
- package/dist/modules/settings/module.js +1 -0
- package/dist/modules/settings/service.d.ts +3 -0
- package/dist/modules/settings/service.d.ts.map +1 -1
- package/dist/modules/settings/service.js +6 -4
- package/dist/modules/shipping/service.js +9 -9
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +1 -0
- package/dist/modules/tax/service.d.ts +3 -0
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +11 -9
- package/dist/modules/webhooks/hook.js +2 -2
- package/dist/modules/webhooks/module.d.ts.map +1 -1
- package/dist/modules/webhooks/module.js +1 -0
- package/dist/modules/webhooks/service.d.ts +2 -0
- package/dist/modules/webhooks/service.d.ts.map +1 -1
- package/dist/modules/webhooks/service.js +4 -4
- package/dist/runtime/kernel-modules.d.ts +73 -0
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +10 -2
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +35 -13
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +4 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +2 -0
- package/dist/test-utils/test-actors.js +2 -2
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +11 -0
- package/package.json +6 -5
- package/src/auth/auth-schema-guard.ts +143 -0
- package/src/auth/auth-schema.ts +50 -19
- package/src/auth/middleware.ts +17 -5
- package/src/auth/org.ts +15 -3
- package/src/auth/organization-guard.ts +158 -0
- package/src/auth/permissions.ts +31 -8
- package/src/auth/role-authority.ts +107 -0
- package/src/auth/setup.ts +17 -0
- package/src/auth/strict-org-resolution.ts +7 -1
- package/src/auth/types.ts +1 -1
- package/src/config/defaults.ts +2 -0
- package/src/config/types.ts +32 -4
- package/src/hooks/checkout.ts +19 -12
- package/src/index.ts +30 -6
- package/src/interfaces/rest/audit-middleware.ts +2 -0
- package/src/interfaces/rest/customer-portal.ts +31 -24
- package/src/interfaces/rest/index.ts +4 -0
- package/src/interfaces/rest/route-coverage.ts +103 -0
- package/src/interfaces/rest/router.ts +16 -10
- package/src/interfaces/rest/routes/admin/compensation-failures.ts +8 -5
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
- package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
- package/src/interfaces/rest/routes/admin/permissions.ts +3 -1
- package/src/interfaces/rest/routes/admin/staff.ts +161 -72
- package/src/interfaces/rest/routes/admin-jobs.ts +3 -3
- package/src/interfaces/rest/routes/audit.ts +3 -3
- package/src/interfaces/rest/routes/carts.ts +17 -7
- package/src/interfaces/rest/routes/catalog.ts +96 -0
- package/src/interfaces/rest/routes/checkout.ts +41 -38
- package/src/interfaces/rest/routes/documents.ts +15 -1
- package/src/interfaces/rest/routes/inventory.ts +2 -0
- package/src/interfaces/rest/routes/media.ts +8 -3
- package/src/interfaces/rest/routes/orders.ts +27 -8
- package/src/interfaces/rest/routes/pricing.ts +2 -2
- package/src/interfaces/rest/routes/promotions.ts +12 -9
- package/src/interfaces/rest/routes/search.ts +19 -1
- package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
- package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
- package/src/interfaces/rest/schemas/catalog.ts +92 -0
- package/src/interfaces/rest/schemas/search.ts +2 -0
- package/src/interfaces/rest/utils.ts +61 -2
- package/src/kernel/compensation/executor.ts +2 -2
- package/src/kernel/database/tx-context.ts +25 -0
- package/src/kernel/hooks/create-context.ts +3 -0
- package/src/kernel/hooks/types.ts +2 -0
- package/src/kernel/plugin/manifest.ts +9 -5
- package/src/modules/analytics/service.ts +2 -2
- package/src/modules/audit/service.ts +3 -3
- package/src/modules/cart/repository/index.ts +22 -8
- package/src/modules/cart/schema.ts +4 -0
- package/src/modules/cart/service.ts +62 -27
- package/src/modules/catalog/brand-service.ts +7 -7
- package/src/modules/catalog/category-service.ts +7 -7
- package/src/modules/catalog/entity-service.ts +188 -53
- package/src/modules/catalog/module.ts +15 -0
- package/src/modules/catalog/ownership.ts +17 -0
- package/src/modules/catalog/read-policy.ts +16 -0
- package/src/modules/catalog/repository/index.ts +566 -4
- package/src/modules/catalog/revision.ts +35 -0
- package/src/modules/catalog/schema.ts +169 -1
- package/src/modules/catalog/schemas.ts +2 -0
- package/src/modules/catalog/service.ts +766 -26
- package/src/modules/channels/adapter.ts +107 -1
- package/src/modules/customers/module.ts +2 -0
- package/src/modules/customers/service.ts +23 -22
- package/src/modules/documents/module.ts +2 -0
- package/src/modules/documents/service.ts +74 -11
- package/src/modules/fulfillment/module.ts +2 -0
- package/src/modules/fulfillment/service.ts +17 -6
- package/src/modules/inventory/service.ts +15 -24
- package/src/modules/media/module.ts +2 -0
- package/src/modules/media/schema.ts +23 -2
- package/src/modules/media/service.ts +89 -28
- package/src/modules/orders/guest-access.ts +55 -0
- package/src/modules/orders/module.ts +1 -0
- package/src/modules/orders/quote.ts +1 -0
- package/src/modules/orders/repository/index.ts +2 -0
- package/src/modules/orders/schema.ts +5 -3
- package/src/modules/orders/service.ts +302 -52
- package/src/modules/pricing/module.ts +2 -0
- package/src/modules/pricing/schema.ts +1 -0
- package/src/modules/pricing/schemas.ts +1 -0
- package/src/modules/pricing/service.ts +21 -18
- package/src/modules/promotions/module.ts +2 -0
- package/src/modules/promotions/service.ts +25 -16
- package/src/modules/search/adapter.ts +8 -0
- package/src/modules/search/module.ts +3 -0
- package/src/modules/search/service.ts +142 -12
- package/src/modules/settings/module.ts +2 -0
- package/src/modules/settings/service.ts +8 -4
- package/src/modules/shipping/service.ts +9 -9
- package/src/modules/tax/module.ts +1 -0
- package/src/modules/tax/service.ts +13 -9
- package/src/modules/webhooks/hook.ts +2 -2
- package/src/modules/webhooks/module.ts +2 -0
- package/src/modules/webhooks/service.ts +6 -4
- package/src/runtime/server.ts +16 -2
- package/src/test-utils/create-pglite-adapter.ts +34 -12
- package/src/test-utils/create-test-config.ts +10 -6
- package/src/test-utils/rest-api-test-utils.ts +2 -0
- package/src/test-utils/test-actors.ts +2 -2
- package/src/utils/id.ts +20 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eq, and, inArray, type SQL } from "drizzle-orm";
|
|
1
|
+
import { eq, and, asc, count, desc, gt, inArray, isNull, lt, max, ne, or, type SQL } from "drizzle-orm";
|
|
2
2
|
import type { TxContext } from "../../../kernel/database/tx-context.js";
|
|
3
3
|
import { CommerceNotFoundError } from "../../../kernel/errors.js";
|
|
4
4
|
import type {
|
|
@@ -9,15 +9,21 @@ import {
|
|
|
9
9
|
sellableEntities,
|
|
10
10
|
sellableAttributes,
|
|
11
11
|
sellableCustomFields,
|
|
12
|
+
entityFieldDefinitions,
|
|
12
13
|
categories,
|
|
13
14
|
entityCategories,
|
|
14
15
|
brands,
|
|
15
16
|
entityBrands,
|
|
17
|
+
entityTags,
|
|
16
18
|
optionTypes,
|
|
17
19
|
optionValues,
|
|
18
20
|
variants,
|
|
19
21
|
variantOptionValues,
|
|
22
|
+
sellableEntityRevisions,
|
|
23
|
+
catalogFieldOwnership,
|
|
24
|
+
type SellableEntityRevisionSnapshot,
|
|
20
25
|
} from "../schema.js";
|
|
26
|
+
import { entityMedia } from "../../media/schema.js";
|
|
21
27
|
|
|
22
28
|
// Infer types from Drizzle schema
|
|
23
29
|
export type SellableEntity = typeof sellableEntities.$inferSelect;
|
|
@@ -27,6 +33,8 @@ export type SellableAttributeInsert = typeof sellableAttributes.$inferInsert;
|
|
|
27
33
|
export type SellableCustomField = typeof sellableCustomFields.$inferSelect;
|
|
28
34
|
export type SellableCustomFieldInsert =
|
|
29
35
|
typeof sellableCustomFields.$inferInsert;
|
|
36
|
+
export type EntityFieldDefinitionRecord = typeof entityFieldDefinitions.$inferSelect;
|
|
37
|
+
export type EntityFieldDefinitionInsert = typeof entityFieldDefinitions.$inferInsert;
|
|
30
38
|
export type Category = typeof categories.$inferSelect;
|
|
31
39
|
export type CategoryInsert = typeof categories.$inferInsert;
|
|
32
40
|
export type EntityCategory = typeof entityCategories.$inferSelect;
|
|
@@ -43,6 +51,14 @@ export type Variant = typeof variants.$inferSelect;
|
|
|
43
51
|
export type VariantInsert = typeof variants.$inferInsert;
|
|
44
52
|
export type VariantOptionValue = typeof variantOptionValues.$inferSelect;
|
|
45
53
|
export type VariantOptionValueInsert = typeof variantOptionValues.$inferInsert;
|
|
54
|
+
export type SellableEntityRevision = typeof sellableEntityRevisions.$inferSelect;
|
|
55
|
+
export type SellableEntityRevisionInsert = typeof sellableEntityRevisions.$inferInsert;
|
|
56
|
+
export type CatalogFieldOwnership = typeof catalogFieldOwnership.$inferSelect;
|
|
57
|
+
export type CatalogFieldOwnershipInsert = typeof catalogFieldOwnership.$inferInsert;
|
|
58
|
+
export type ProposedCustomField = SellableCustomField & {
|
|
59
|
+
entitySlug: string;
|
|
60
|
+
entityType: string;
|
|
61
|
+
};
|
|
46
62
|
|
|
47
63
|
/**
|
|
48
64
|
* CatalogRepository provides type-safe database operations for catalog entities.
|
|
@@ -108,6 +124,7 @@ export class CatalogRepository {
|
|
|
108
124
|
filter?: {
|
|
109
125
|
type?: string;
|
|
110
126
|
status?: string;
|
|
127
|
+
isVisible?: boolean;
|
|
111
128
|
ids?: string[];
|
|
112
129
|
},
|
|
113
130
|
ctx?: TxContext,
|
|
@@ -123,6 +140,9 @@ export class CatalogRepository {
|
|
|
123
140
|
eq(sellableEntities.status, filter.status as SellableEntity["status"]),
|
|
124
141
|
);
|
|
125
142
|
}
|
|
143
|
+
if (filter?.isVisible !== undefined) {
|
|
144
|
+
conditions.push(eq(sellableEntities.isVisible, filter.isVisible));
|
|
145
|
+
}
|
|
126
146
|
if (filter?.ids && filter.ids.length > 0) {
|
|
127
147
|
conditions.push(inArray(sellableEntities.id, filter.ids));
|
|
128
148
|
}
|
|
@@ -165,6 +185,247 @@ export class CatalogRepository {
|
|
|
165
185
|
return result.length > 0;
|
|
166
186
|
}
|
|
167
187
|
|
|
188
|
+
async findFieldOwnership(
|
|
189
|
+
entityId: string,
|
|
190
|
+
ctx?: TxContext,
|
|
191
|
+
storeId?: string,
|
|
192
|
+
): Promise<CatalogFieldOwnership[]> {
|
|
193
|
+
const db = this.getDb(ctx);
|
|
194
|
+
const conditions = [eq(catalogFieldOwnership.entityId, entityId)];
|
|
195
|
+
if (storeId !== undefined) {
|
|
196
|
+
conditions.push(or(isNull(catalogFieldOwnership.storeId), eq(catalogFieldOwnership.storeId, storeId))!);
|
|
197
|
+
}
|
|
198
|
+
return db
|
|
199
|
+
.select()
|
|
200
|
+
.from(catalogFieldOwnership)
|
|
201
|
+
.where(and(...conditions))
|
|
202
|
+
.orderBy(asc(catalogFieldOwnership.fieldPath), asc(catalogFieldOwnership.updatedAt));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async findFieldOwnershipForResolution(
|
|
206
|
+
entityId: string,
|
|
207
|
+
storeId: string,
|
|
208
|
+
variantId: string | null,
|
|
209
|
+
ctx?: TxContext,
|
|
210
|
+
): Promise<CatalogFieldOwnership[]> {
|
|
211
|
+
const db = this.getDb(ctx);
|
|
212
|
+
return db
|
|
213
|
+
.select()
|
|
214
|
+
.from(catalogFieldOwnership)
|
|
215
|
+
.where(and(
|
|
216
|
+
eq(catalogFieldOwnership.entityId, entityId),
|
|
217
|
+
or(isNull(catalogFieldOwnership.storeId), eq(catalogFieldOwnership.storeId, storeId)),
|
|
218
|
+
variantId == null
|
|
219
|
+
? isNull(catalogFieldOwnership.variantId)
|
|
220
|
+
: or(isNull(catalogFieldOwnership.variantId), eq(catalogFieldOwnership.variantId, variantId)),
|
|
221
|
+
));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async upsertFieldOwnership(
|
|
225
|
+
data: CatalogFieldOwnershipInsert,
|
|
226
|
+
ctx?: TxContext,
|
|
227
|
+
): Promise<CatalogFieldOwnership> {
|
|
228
|
+
const db = this.getDb(ctx);
|
|
229
|
+
const rows = await db
|
|
230
|
+
.insert(catalogFieldOwnership)
|
|
231
|
+
.values(data)
|
|
232
|
+
.onConflictDoUpdate({
|
|
233
|
+
target: [
|
|
234
|
+
catalogFieldOwnership.organizationId,
|
|
235
|
+
catalogFieldOwnership.entityId,
|
|
236
|
+
catalogFieldOwnership.variantId,
|
|
237
|
+
catalogFieldOwnership.storeId,
|
|
238
|
+
catalogFieldOwnership.fieldPath,
|
|
239
|
+
],
|
|
240
|
+
set: {
|
|
241
|
+
owner: data.owner,
|
|
242
|
+
updatedAt: data.updatedAt ?? new Date(),
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
.returning();
|
|
246
|
+
return rows[0]!;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async seedFieldOwnership(
|
|
250
|
+
data: CatalogFieldOwnershipInsert[],
|
|
251
|
+
ctx?: TxContext,
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
if (data.length === 0) return;
|
|
254
|
+
const db = this.getDb(ctx);
|
|
255
|
+
await db.insert(catalogFieldOwnership).values(data).onConflictDoNothing();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async findRevisionsByEntityId(
|
|
259
|
+
entityId: string,
|
|
260
|
+
ctx?: TxContext,
|
|
261
|
+
): Promise<SellableEntityRevision[]> {
|
|
262
|
+
const db = this.getDb(ctx);
|
|
263
|
+
return db
|
|
264
|
+
.select()
|
|
265
|
+
.from(sellableEntityRevisions)
|
|
266
|
+
.where(eq(sellableEntityRevisions.entityId, entityId))
|
|
267
|
+
.orderBy(asc(sellableEntityRevisions.revision));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async findRevisionMarkers(
|
|
271
|
+
entityId: string,
|
|
272
|
+
since?: Date,
|
|
273
|
+
ctx?: TxContext,
|
|
274
|
+
): Promise<Array<Pick<SellableEntityRevision, "createdAt" | "reason">>> {
|
|
275
|
+
const db = this.getDb(ctx);
|
|
276
|
+
return db
|
|
277
|
+
.select({
|
|
278
|
+
createdAt: sellableEntityRevisions.createdAt,
|
|
279
|
+
reason: sellableEntityRevisions.reason,
|
|
280
|
+
})
|
|
281
|
+
.from(sellableEntityRevisions)
|
|
282
|
+
.where(and(
|
|
283
|
+
eq(sellableEntityRevisions.entityId, entityId),
|
|
284
|
+
...(since ? [gt(sellableEntityRevisions.createdAt, since)] : []),
|
|
285
|
+
))
|
|
286
|
+
.orderBy(asc(sellableEntityRevisions.createdAt));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async findLatestRevision(
|
|
290
|
+
entityId: string,
|
|
291
|
+
ctx?: TxContext,
|
|
292
|
+
): Promise<SellableEntityRevision | undefined> {
|
|
293
|
+
const db = this.getDb(ctx);
|
|
294
|
+
const rows = await db
|
|
295
|
+
.select()
|
|
296
|
+
.from(sellableEntityRevisions)
|
|
297
|
+
.where(eq(sellableEntityRevisions.entityId, entityId))
|
|
298
|
+
.orderBy(desc(sellableEntityRevisions.revision))
|
|
299
|
+
.limit(1);
|
|
300
|
+
return rows[0];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async findRevisionById(
|
|
304
|
+
entityId: string,
|
|
305
|
+
id: string,
|
|
306
|
+
ctx?: TxContext,
|
|
307
|
+
): Promise<SellableEntityRevision | undefined> {
|
|
308
|
+
const db = this.getDb(ctx);
|
|
309
|
+
const rows = await db
|
|
310
|
+
.select()
|
|
311
|
+
.from(sellableEntityRevisions)
|
|
312
|
+
.where(
|
|
313
|
+
and(
|
|
314
|
+
eq(sellableEntityRevisions.entityId, entityId),
|
|
315
|
+
eq(sellableEntityRevisions.id, id),
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
return rows[0];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async createRevision(
|
|
322
|
+
data: Omit<SellableEntityRevisionInsert, "id" | "revision" | "pinned">,
|
|
323
|
+
ctx?: TxContext,
|
|
324
|
+
): Promise<SellableEntityRevision> {
|
|
325
|
+
const db = this.getDb(ctx);
|
|
326
|
+
const rows = await db
|
|
327
|
+
.select({ revision: max(sellableEntityRevisions.revision) })
|
|
328
|
+
.from(sellableEntityRevisions)
|
|
329
|
+
.where(eq(sellableEntityRevisions.entityId, data.entityId));
|
|
330
|
+
const revision = (rows[0]?.revision ?? 0) + 1;
|
|
331
|
+
const inserted = await db
|
|
332
|
+
.insert(sellableEntityRevisions)
|
|
333
|
+
.values({
|
|
334
|
+
...data,
|
|
335
|
+
revision,
|
|
336
|
+
pinned: revision === 1,
|
|
337
|
+
})
|
|
338
|
+
.returning();
|
|
339
|
+
return inserted[0]!;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async updateRevision(
|
|
343
|
+
id: string,
|
|
344
|
+
data: Partial<Pick<SellableEntityRevisionInsert, "createdAt" | "pinned">>,
|
|
345
|
+
ctx?: TxContext,
|
|
346
|
+
): Promise<SellableEntityRevision | undefined> {
|
|
347
|
+
const db = this.getDb(ctx);
|
|
348
|
+
const rows = await db
|
|
349
|
+
.update(sellableEntityRevisions)
|
|
350
|
+
.set(data)
|
|
351
|
+
.where(eq(sellableEntityRevisions.id, id))
|
|
352
|
+
.returning();
|
|
353
|
+
return rows[0];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async deleteRevisionsOlderThan(
|
|
357
|
+
organizationId: string,
|
|
358
|
+
cutoff: Date,
|
|
359
|
+
ctx?: TxContext,
|
|
360
|
+
): Promise<number> {
|
|
361
|
+
const db = this.getDb(ctx);
|
|
362
|
+
const deleted = await db
|
|
363
|
+
.delete(sellableEntityRevisions)
|
|
364
|
+
.where(
|
|
365
|
+
and(
|
|
366
|
+
eq(sellableEntityRevisions.organizationId, organizationId),
|
|
367
|
+
lt(sellableEntityRevisions.createdAt, cutoff),
|
|
368
|
+
eq(sellableEntityRevisions.pinned, false),
|
|
369
|
+
gt(sellableEntityRevisions.revision, 1),
|
|
370
|
+
),
|
|
371
|
+
)
|
|
372
|
+
.returning({ id: sellableEntityRevisions.id });
|
|
373
|
+
return deleted.length;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async snapshotEntity(
|
|
377
|
+
entityId: string,
|
|
378
|
+
ctx?: TxContext,
|
|
379
|
+
): Promise<SellableEntityRevisionSnapshot> {
|
|
380
|
+
const entity = await this.findEntityById(entityId, ctx);
|
|
381
|
+
if (!entity) throw new CommerceNotFoundError("Entity not found.");
|
|
382
|
+
const [attributes, customFields, categoriesForEntity, brandsForEntity, media, tagsForEntity] = await Promise.all([
|
|
383
|
+
this.findAttributesByEntityId(entityId, ctx),
|
|
384
|
+
this.findAllCustomFieldsByEntityId(entityId, ctx),
|
|
385
|
+
this.findEntityCategories(entityId, ctx),
|
|
386
|
+
this.findEntityBrands(entityId, ctx),
|
|
387
|
+
this.findEntityMedia(entityId, ctx),
|
|
388
|
+
this.findEntityTags(entityId, ctx),
|
|
389
|
+
]);
|
|
390
|
+
return {
|
|
391
|
+
entity: entity as unknown as Record<string, unknown>,
|
|
392
|
+
attributes: attributes.sort((a, b) => a.locale.localeCompare(b.locale) || a.id.localeCompare(b.id)) as unknown as Array<Record<string, unknown>>,
|
|
393
|
+
customFields: customFields.sort((a, b) => a.locale.localeCompare(b.locale) || a.fieldName.localeCompare(b.fieldName) || a.id.localeCompare(b.id)) as unknown as Array<Record<string, unknown>>,
|
|
394
|
+
media: media.sort((a, b) => a.mediaAssetId.localeCompare(b.mediaAssetId) || (a.variantId ?? "").localeCompare(b.variantId ?? "")) as unknown as Array<Record<string, unknown>>,
|
|
395
|
+
categories: categoriesForEntity.sort((a, b) => a.categoryId.localeCompare(b.categoryId)) as unknown as Array<Record<string, unknown>>,
|
|
396
|
+
brands: brandsForEntity.sort((a, b) => a.brandId.localeCompare(b.brandId)) as unknown as Array<Record<string, unknown>>,
|
|
397
|
+
tags: tagsForEntity.sort((a, b) => a.tagId.localeCompare(b.tagId)) as unknown as Array<Record<string, unknown>>,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async findEntityMedia(
|
|
402
|
+
entityId: string,
|
|
403
|
+
ctx?: TxContext,
|
|
404
|
+
): Promise<Array<typeof entityMedia.$inferSelect>> {
|
|
405
|
+
const db = this.getDb(ctx);
|
|
406
|
+
return db
|
|
407
|
+
.select()
|
|
408
|
+
.from(entityMedia)
|
|
409
|
+
.where(eq(entityMedia.entityId, entityId));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async deleteEntityMediaByEntityId(
|
|
413
|
+
entityId: string,
|
|
414
|
+
ctx?: TxContext,
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
const db = this.getDb(ctx);
|
|
417
|
+
await db.delete(entityMedia).where(eq(entityMedia.entityId, entityId));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async createEntityMedia(
|
|
421
|
+
data: typeof entityMedia.$inferInsert,
|
|
422
|
+
ctx?: TxContext,
|
|
423
|
+
): Promise<typeof entityMedia.$inferSelect> {
|
|
424
|
+
const db = this.getDb(ctx);
|
|
425
|
+
const rows = await db.insert(entityMedia).values(data).returning();
|
|
426
|
+
return rows[0]!;
|
|
427
|
+
}
|
|
428
|
+
|
|
168
429
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
169
430
|
// Sellable Attributes
|
|
170
431
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -252,6 +513,22 @@ export class CatalogRepository {
|
|
|
252
513
|
async findCustomFieldsByEntityId(
|
|
253
514
|
entityId: string,
|
|
254
515
|
ctx?: TxContext,
|
|
516
|
+
): Promise<SellableCustomField[]> {
|
|
517
|
+
const db = this.getDb(ctx);
|
|
518
|
+
return db
|
|
519
|
+
.select()
|
|
520
|
+
.from(sellableCustomFields)
|
|
521
|
+
.where(
|
|
522
|
+
and(
|
|
523
|
+
eq(sellableCustomFields.entityId, entityId),
|
|
524
|
+
eq(sellableCustomFields.status, "approved"),
|
|
525
|
+
),
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async findAllCustomFieldsByEntityId(
|
|
530
|
+
entityId: string,
|
|
531
|
+
ctx?: TxContext,
|
|
255
532
|
): Promise<SellableCustomField[]> {
|
|
256
533
|
const db = this.getDb(ctx);
|
|
257
534
|
return db
|
|
@@ -260,6 +537,137 @@ export class CatalogRepository {
|
|
|
260
537
|
.where(eq(sellableCustomFields.entityId, entityId));
|
|
261
538
|
}
|
|
262
539
|
|
|
540
|
+
async findProposedCustomField(
|
|
541
|
+
entityId: string,
|
|
542
|
+
fieldName: string,
|
|
543
|
+
locale = "en",
|
|
544
|
+
ctx?: TxContext,
|
|
545
|
+
): Promise<SellableCustomField | undefined> {
|
|
546
|
+
const db = this.getDb(ctx);
|
|
547
|
+
const rows = await db
|
|
548
|
+
.select()
|
|
549
|
+
.from(sellableCustomFields)
|
|
550
|
+
.where(
|
|
551
|
+
and(
|
|
552
|
+
eq(sellableCustomFields.entityId, entityId),
|
|
553
|
+
eq(sellableCustomFields.fieldName, fieldName),
|
|
554
|
+
eq(sellableCustomFields.locale, locale),
|
|
555
|
+
eq(sellableCustomFields.status, "proposed"),
|
|
556
|
+
),
|
|
557
|
+
)
|
|
558
|
+
.orderBy(desc(sellableCustomFields.createdAt), desc(sellableCustomFields.id))
|
|
559
|
+
.limit(1);
|
|
560
|
+
return rows[0];
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async updateProposedCustomField(
|
|
564
|
+
id: string,
|
|
565
|
+
data: Partial<Omit<SellableCustomFieldInsert, "id">>,
|
|
566
|
+
ctx?: TxContext,
|
|
567
|
+
): Promise<SellableCustomField | undefined> {
|
|
568
|
+
const db = this.getDb(ctx);
|
|
569
|
+
const rows = await db
|
|
570
|
+
.update(sellableCustomFields)
|
|
571
|
+
.set({ ...data, updatedAt: new Date() })
|
|
572
|
+
.where(
|
|
573
|
+
and(
|
|
574
|
+
eq(sellableCustomFields.id, id),
|
|
575
|
+
eq(sellableCustomFields.status, "proposed"),
|
|
576
|
+
),
|
|
577
|
+
)
|
|
578
|
+
.returning();
|
|
579
|
+
return rows[0];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async rejectOtherProposedCustomFields(
|
|
583
|
+
entityId: string,
|
|
584
|
+
fieldName: string,
|
|
585
|
+
locale: string,
|
|
586
|
+
excludeId: string,
|
|
587
|
+
ctx?: TxContext,
|
|
588
|
+
): Promise<number> {
|
|
589
|
+
const db = this.getDb(ctx);
|
|
590
|
+
const rows = await db
|
|
591
|
+
.update(sellableCustomFields)
|
|
592
|
+
.set({ status: "rejected", updatedAt: new Date() })
|
|
593
|
+
.where(
|
|
594
|
+
and(
|
|
595
|
+
eq(sellableCustomFields.entityId, entityId),
|
|
596
|
+
eq(sellableCustomFields.fieldName, fieldName),
|
|
597
|
+
eq(sellableCustomFields.locale, locale),
|
|
598
|
+
eq(sellableCustomFields.status, "proposed"),
|
|
599
|
+
ne(sellableCustomFields.id, excludeId),
|
|
600
|
+
),
|
|
601
|
+
)
|
|
602
|
+
.returning({ id: sellableCustomFields.id });
|
|
603
|
+
return rows.length;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async listProposedCustomFields(
|
|
607
|
+
organizationId: string,
|
|
608
|
+
pagination: { page: number; limit: number },
|
|
609
|
+
filter?: { entityType?: string },
|
|
610
|
+
ctx?: TxContext,
|
|
611
|
+
): Promise<{ items: ProposedCustomField[]; total: number }> {
|
|
612
|
+
const db = this.getDb(ctx);
|
|
613
|
+
const page = Math.max(1, pagination.page);
|
|
614
|
+
const limit = Math.max(1, pagination.limit);
|
|
615
|
+
const conditions: SQL[] = [
|
|
616
|
+
eq(sellableCustomFields.status, "proposed"),
|
|
617
|
+
eq(sellableEntities.organizationId, organizationId),
|
|
618
|
+
];
|
|
619
|
+
if (filter?.entityType !== undefined) {
|
|
620
|
+
conditions.push(eq(sellableEntities.type, filter.entityType));
|
|
621
|
+
}
|
|
622
|
+
const where = and(...conditions);
|
|
623
|
+
const rows = await db
|
|
624
|
+
.select({
|
|
625
|
+
customField: sellableCustomFields,
|
|
626
|
+
entitySlug: sellableEntities.slug,
|
|
627
|
+
entityType: sellableEntities.type,
|
|
628
|
+
})
|
|
629
|
+
.from(sellableCustomFields)
|
|
630
|
+
.innerJoin(sellableEntities, eq(sellableCustomFields.entityId, sellableEntities.id))
|
|
631
|
+
.where(where)
|
|
632
|
+
.orderBy(asc(sellableCustomFields.createdAt), asc(sellableCustomFields.id))
|
|
633
|
+
.limit(limit)
|
|
634
|
+
.offset((page - 1) * limit);
|
|
635
|
+
const totalRows = await db
|
|
636
|
+
.select({ count: count() })
|
|
637
|
+
.from(sellableCustomFields)
|
|
638
|
+
.innerJoin(sellableEntities, eq(sellableCustomFields.entityId, sellableEntities.id))
|
|
639
|
+
.where(where);
|
|
640
|
+
return {
|
|
641
|
+
items: rows.map(({ customField, entitySlug, entityType }) => ({
|
|
642
|
+
...customField,
|
|
643
|
+
entitySlug,
|
|
644
|
+
entityType,
|
|
645
|
+
})),
|
|
646
|
+
total: totalRows[0]?.count ?? 0,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
async findCustomFieldByName(
|
|
651
|
+
entityId: string,
|
|
652
|
+
fieldName: string,
|
|
653
|
+
locale = "en",
|
|
654
|
+
ctx?: TxContext,
|
|
655
|
+
): Promise<SellableCustomField | undefined> {
|
|
656
|
+
const db = this.getDb(ctx);
|
|
657
|
+
const rows = await db
|
|
658
|
+
.select()
|
|
659
|
+
.from(sellableCustomFields)
|
|
660
|
+
.where(
|
|
661
|
+
and(
|
|
662
|
+
eq(sellableCustomFields.entityId, entityId),
|
|
663
|
+
eq(sellableCustomFields.fieldName, fieldName),
|
|
664
|
+
eq(sellableCustomFields.locale, locale),
|
|
665
|
+
eq(sellableCustomFields.status, "approved"),
|
|
666
|
+
),
|
|
667
|
+
);
|
|
668
|
+
return rows[0];
|
|
669
|
+
}
|
|
670
|
+
|
|
263
671
|
async createCustomField(
|
|
264
672
|
data: SellableCustomFieldInsert,
|
|
265
673
|
ctx?: TxContext,
|
|
@@ -269,6 +677,54 @@ export class CatalogRepository {
|
|
|
269
677
|
return rows[0]!;
|
|
270
678
|
}
|
|
271
679
|
|
|
680
|
+
async updateCustomField(
|
|
681
|
+
id: string,
|
|
682
|
+
data: Partial<Omit<SellableCustomFieldInsert, "id">>,
|
|
683
|
+
ctx?: TxContext,
|
|
684
|
+
): Promise<SellableCustomField | undefined> {
|
|
685
|
+
const db = this.getDb(ctx);
|
|
686
|
+
const rows = await db
|
|
687
|
+
.update(sellableCustomFields)
|
|
688
|
+
.set({ ...data, updatedAt: new Date() })
|
|
689
|
+
.where(eq(sellableCustomFields.id, id))
|
|
690
|
+
.returning();
|
|
691
|
+
return rows[0];
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async upsertCustomField(
|
|
695
|
+
entityId: string,
|
|
696
|
+
fieldName: string,
|
|
697
|
+
locale: string,
|
|
698
|
+
data: Omit<SellableCustomFieldInsert, "entityId" | "fieldName">,
|
|
699
|
+
ctx?: TxContext,
|
|
700
|
+
): Promise<SellableCustomField> {
|
|
701
|
+
const existing = await this.findCustomFieldByName(entityId, fieldName, locale, ctx);
|
|
702
|
+
if (existing) {
|
|
703
|
+
const updated = await this.updateCustomField(existing.id, data, ctx);
|
|
704
|
+
return updated!;
|
|
705
|
+
}
|
|
706
|
+
return this.createCustomField({ ...data, entityId, fieldName, locale }, ctx);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async deleteCustomField(
|
|
710
|
+
entityId: string,
|
|
711
|
+
fieldName: string,
|
|
712
|
+
locale = "en",
|
|
713
|
+
ctx?: TxContext,
|
|
714
|
+
): Promise<void> {
|
|
715
|
+
const db = this.getDb(ctx);
|
|
716
|
+
await db
|
|
717
|
+
.delete(sellableCustomFields)
|
|
718
|
+
.where(
|
|
719
|
+
and(
|
|
720
|
+
eq(sellableCustomFields.entityId, entityId),
|
|
721
|
+
eq(sellableCustomFields.fieldName, fieldName),
|
|
722
|
+
eq(sellableCustomFields.locale, locale),
|
|
723
|
+
eq(sellableCustomFields.status, "approved"),
|
|
724
|
+
),
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
272
728
|
async deleteCustomFieldsByEntityId(
|
|
273
729
|
entityId: string,
|
|
274
730
|
ctx?: TxContext,
|
|
@@ -279,6 +735,79 @@ export class CatalogRepository {
|
|
|
279
735
|
.where(eq(sellableCustomFields.entityId, entityId));
|
|
280
736
|
}
|
|
281
737
|
|
|
738
|
+
async findEntityFieldDefinitions(
|
|
739
|
+
organizationId: string,
|
|
740
|
+
entityType?: string,
|
|
741
|
+
ctx?: TxContext,
|
|
742
|
+
): Promise<EntityFieldDefinitionRecord[]> {
|
|
743
|
+
const db = this.getDb(ctx);
|
|
744
|
+
const conditions = [eq(entityFieldDefinitions.organizationId, organizationId)];
|
|
745
|
+
if (entityType !== undefined) {
|
|
746
|
+
conditions.push(eq(entityFieldDefinitions.entityType, entityType));
|
|
747
|
+
}
|
|
748
|
+
return db
|
|
749
|
+
.select()
|
|
750
|
+
.from(entityFieldDefinitions)
|
|
751
|
+
.where(and(...conditions))
|
|
752
|
+
.orderBy(asc(entityFieldDefinitions.entityType), asc(entityFieldDefinitions.sortOrder), asc(entityFieldDefinitions.name));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async findActiveEntityFieldDefinitions(
|
|
756
|
+
organizationId: string,
|
|
757
|
+
entityType: string,
|
|
758
|
+
ctx?: TxContext,
|
|
759
|
+
): Promise<EntityFieldDefinitionRecord[]> {
|
|
760
|
+
const db = this.getDb(ctx);
|
|
761
|
+
return db
|
|
762
|
+
.select()
|
|
763
|
+
.from(entityFieldDefinitions)
|
|
764
|
+
.where(
|
|
765
|
+
and(
|
|
766
|
+
eq(entityFieldDefinitions.organizationId, organizationId),
|
|
767
|
+
eq(entityFieldDefinitions.entityType, entityType),
|
|
768
|
+
eq(entityFieldDefinitions.status, "active"),
|
|
769
|
+
),
|
|
770
|
+
)
|
|
771
|
+
.orderBy(asc(entityFieldDefinitions.sortOrder), asc(entityFieldDefinitions.name));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async findEntityFieldDefinitionById(
|
|
775
|
+
organizationId: string,
|
|
776
|
+
id: string,
|
|
777
|
+
ctx?: TxContext,
|
|
778
|
+
): Promise<EntityFieldDefinitionRecord | undefined> {
|
|
779
|
+
const db = this.getDb(ctx);
|
|
780
|
+
const rows = await db
|
|
781
|
+
.select()
|
|
782
|
+
.from(entityFieldDefinitions)
|
|
783
|
+
.where(and(eq(entityFieldDefinitions.organizationId, organizationId), eq(entityFieldDefinitions.id, id)));
|
|
784
|
+
return rows[0];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async createEntityFieldDefinition(
|
|
788
|
+
data: EntityFieldDefinitionInsert,
|
|
789
|
+
ctx?: TxContext,
|
|
790
|
+
): Promise<EntityFieldDefinitionRecord> {
|
|
791
|
+
const db = this.getDb(ctx);
|
|
792
|
+
const rows = await db.insert(entityFieldDefinitions).values(data).returning();
|
|
793
|
+
return rows[0]!;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async updateEntityFieldDefinition(
|
|
797
|
+
organizationId: string,
|
|
798
|
+
id: string,
|
|
799
|
+
data: Partial<Omit<EntityFieldDefinitionInsert, "id" | "organizationId">>,
|
|
800
|
+
ctx?: TxContext,
|
|
801
|
+
): Promise<EntityFieldDefinitionRecord | undefined> {
|
|
802
|
+
const db = this.getDb(ctx);
|
|
803
|
+
const rows = await db
|
|
804
|
+
.update(entityFieldDefinitions)
|
|
805
|
+
.set({ ...data, updatedAt: new Date() })
|
|
806
|
+
.where(and(eq(entityFieldDefinitions.organizationId, organizationId), eq(entityFieldDefinitions.id, id)))
|
|
807
|
+
.returning();
|
|
808
|
+
return rows[0];
|
|
809
|
+
}
|
|
810
|
+
|
|
282
811
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
283
812
|
// Categories
|
|
284
813
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -286,12 +815,17 @@ export class CatalogRepository {
|
|
|
286
815
|
async findCategoryById(
|
|
287
816
|
id: string,
|
|
288
817
|
ctx?: TxContext,
|
|
818
|
+
orgId?: string,
|
|
289
819
|
): Promise<Category | undefined> {
|
|
290
820
|
const db = this.getDb(ctx);
|
|
291
821
|
const rows = await db
|
|
292
822
|
.select()
|
|
293
823
|
.from(categories)
|
|
294
|
-
.where(
|
|
824
|
+
.where(
|
|
825
|
+
orgId
|
|
826
|
+
? and(eq(categories.id, id), eq(categories.organizationId, orgId))
|
|
827
|
+
: eq(categories.id, id),
|
|
828
|
+
);
|
|
295
829
|
return rows[0];
|
|
296
830
|
}
|
|
297
831
|
|
|
@@ -438,9 +972,16 @@ export class CatalogRepository {
|
|
|
438
972
|
// Brands
|
|
439
973
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
440
974
|
|
|
441
|
-
async findBrandById(id: string, ctx?: TxContext): Promise<Brand | undefined> {
|
|
975
|
+
async findBrandById(id: string, ctx?: TxContext, orgId?: string): Promise<Brand | undefined> {
|
|
442
976
|
const db = this.getDb(ctx);
|
|
443
|
-
const rows = await db
|
|
977
|
+
const rows = await db
|
|
978
|
+
.select()
|
|
979
|
+
.from(brands)
|
|
980
|
+
.where(
|
|
981
|
+
orgId
|
|
982
|
+
? and(eq(brands.id, id), eq(brands.organizationId, orgId))
|
|
983
|
+
: eq(brands.id, id),
|
|
984
|
+
);
|
|
444
985
|
return rows[0];
|
|
445
986
|
}
|
|
446
987
|
|
|
@@ -511,6 +1052,27 @@ export class CatalogRepository {
|
|
|
511
1052
|
.where(eq(entityBrands.entityId, entityId));
|
|
512
1053
|
}
|
|
513
1054
|
|
|
1055
|
+
async findEntityTags(
|
|
1056
|
+
entityId: string,
|
|
1057
|
+
ctx?: TxContext,
|
|
1058
|
+
): Promise<Array<typeof entityTags.$inferSelect>> {
|
|
1059
|
+
const db = this.getDb(ctx);
|
|
1060
|
+
return db
|
|
1061
|
+
.select()
|
|
1062
|
+
.from(entityTags)
|
|
1063
|
+
.where(eq(entityTags.entityId, entityId));
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async addEntityTag(entityId: string, tagId: string, ctx?: TxContext): Promise<void> {
|
|
1067
|
+
const db = this.getDb(ctx);
|
|
1068
|
+
await db.insert(entityTags).values({ entityId, tagId }).onConflictDoNothing();
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
async deleteEntityTagsByEntityId(entityId: string, ctx?: TxContext): Promise<void> {
|
|
1072
|
+
const db = this.getDb(ctx);
|
|
1073
|
+
await db.delete(entityTags).where(eq(entityTags.entityId, entityId));
|
|
1074
|
+
}
|
|
1075
|
+
|
|
514
1076
|
async addEntityToBrand(
|
|
515
1077
|
entityId: string,
|
|
516
1078
|
brandId: string,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SellableEntityRevisionSnapshot } from "./schema.js";
|
|
2
|
+
|
|
3
|
+
export function canonicalSerialize(value: unknown): string {
|
|
4
|
+
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
5
|
+
if (Array.isArray(value)) return `[${value.map(canonicalSerialize).join(",")}]`;
|
|
6
|
+
if (value !== null && typeof value === "object") {
|
|
7
|
+
const record = value as Record<string, unknown>;
|
|
8
|
+
return `{${Object.keys(record)
|
|
9
|
+
.sort()
|
|
10
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalSerialize(record[key])}`)
|
|
11
|
+
.join(",")}}`;
|
|
12
|
+
}
|
|
13
|
+
return JSON.stringify(value) ?? "null";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Row bookkeeping timestamps are excluded from comparison only at row roots,
|
|
17
|
+
// never inside nested jsonb where a user key may share the same name.
|
|
18
|
+
function stripRowTimestamps(row: Record<string, unknown>): Record<string, unknown> {
|
|
19
|
+
const { createdAt, updatedAt, ...rest } = row;
|
|
20
|
+
void createdAt;
|
|
21
|
+
void updatedAt;
|
|
22
|
+
return rest;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function comparableSerialize(snapshot: SellableEntityRevisionSnapshot): string {
|
|
26
|
+
return canonicalSerialize({
|
|
27
|
+
entity: stripRowTimestamps(snapshot.entity),
|
|
28
|
+
attributes: snapshot.attributes.map(stripRowTimestamps),
|
|
29
|
+
customFields: snapshot.customFields.map(stripRowTimestamps),
|
|
30
|
+
media: snapshot.media.map(stripRowTimestamps),
|
|
31
|
+
categories: snapshot.categories.map(stripRowTimestamps),
|
|
32
|
+
brands: snapshot.brands.map(stripRowTimestamps),
|
|
33
|
+
tags: (snapshot.tags ?? []).map(stripRowTimestamps),
|
|
34
|
+
});
|
|
35
|
+
}
|