@open-mercato/core 0.9.1-develop.7473.1.de5b22888f → 0.9.1-develop.7475.1.28ca93ed97
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
- package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
- package/dist/generated/entities/customer_group_terms/index.js +2 -0
- package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
- package/dist/generated/entities/ecommerce_store/index.js +33 -0
- package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +8 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
- package/dist/modules/catalog/acl.js +8 -1
- package/dist/modules/catalog/acl.js.map +2 -2
- package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
- package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/history/route.js +131 -0
- package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +54 -245
- package/dist/modules/catalog/api/products/route.js.map +3 -3
- package/dist/modules/catalog/api/tags/route.js +6 -0
- package/dist/modules/catalog/api/tags/route.js.map +2 -2
- package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
- package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
- package/dist/modules/catalog/cli.js +80 -2
- package/dist/modules/catalog/cli.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +51 -2
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/prices.js +43 -1
- package/dist/modules/catalog/commands/prices.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +71 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +7 -0
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
- package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
- package/dist/modules/catalog/components/lookupSources.js +31 -0
- package/dist/modules/catalog/components/lookupSources.js.map +7 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
- package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +122 -1
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +78 -0
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/di.js +18 -0
- package/dist/modules/catalog/di.js.map +2 -2
- package/dist/modules/catalog/lib/omnibus.js +285 -0
- package/dist/modules/catalog/lib/omnibus.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusCache.js +89 -0
- package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
- package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
- package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
- package/dist/modules/catalog/lib/pricing.js +26 -2
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/catalog/lib/productFilters.js +273 -0
- package/dist/modules/catalog/lib/productFilters.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
- package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
- package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
- package/dist/modules/catalog/setup.js +3 -1
- package/dist/modules/catalog/setup.js.map +2 -2
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
- package/dist/modules/catalog/translations.js +10 -0
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +28 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
- package/dist/modules/customer_groups/data/entities.js +3 -0
- package/dist/modules/customer_groups/data/entities.js.map +2 -2
- package/dist/modules/customer_groups/data/validators.js +9 -0
- package/dist/modules/customer_groups/data/validators.js.map +2 -2
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
- package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
- package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
- package/dist/modules/ecommerce/acl.js +13 -0
- package/dist/modules/ecommerce/acl.js.map +7 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/openapi.js +27 -0
- package/dist/modules/ecommerce/api/openapi.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/crud.js +401 -0
- package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/route.js +64 -0
- package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/commands/stores.js +110 -0
- package/dist/modules/ecommerce/commands/stores.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
- package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
- package/dist/modules/ecommerce/components/channelLookups.js +28 -0
- package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
- package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
- package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
- package/dist/modules/ecommerce/components/storeChannels.js +140 -0
- package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
- package/dist/modules/ecommerce/components/storeDomains.js +167 -0
- package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
- package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
- package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
- package/dist/modules/ecommerce/components/storeSeo.js +69 -0
- package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
- package/dist/modules/ecommerce/data/enrichers.js +157 -0
- package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
- package/dist/modules/ecommerce/data/entities.js +197 -0
- package/dist/modules/ecommerce/data/entities.js.map +7 -0
- package/dist/modules/ecommerce/data/validators.js +406 -0
- package/dist/modules/ecommerce/data/validators.js.map +7 -0
- package/dist/modules/ecommerce/di.js +11 -0
- package/dist/modules/ecommerce/di.js.map +7 -0
- package/dist/modules/ecommerce/events.js +27 -0
- package/dist/modules/ecommerce/events.js.map +7 -0
- package/dist/modules/ecommerce/index.js +16 -0
- package/dist/modules/ecommerce/index.js.map +7 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
- package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
- package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
- package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
- package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
- package/dist/modules/ecommerce/lib/references.js +70 -0
- package/dist/modules/ecommerce/lib/references.js.map +7 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
- package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContext.js +393 -0
- package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
- package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
- package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/types.js +1 -0
- package/dist/modules/ecommerce/lib/types.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/notifications.client.js +47 -0
- package/dist/modules/ecommerce/notifications.client.js.map +7 -0
- package/dist/modules/ecommerce/notifications.js +48 -0
- package/dist/modules/ecommerce/notifications.js.map +7 -0
- package/dist/modules/ecommerce/search.js +83 -0
- package/dist/modules/ecommerce/search.js.map +7 -0
- package/dist/modules/ecommerce/setup.js +17 -0
- package/dist/modules/ecommerce/setup.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
- package/dist/modules/query_index/lib/batch.js +23 -9
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
- package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
- package/dist/modules/query_index/lib/document.js +2 -0
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +68 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +7 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +2 -0
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
- package/dist/modules/translations/components/TranslationManager.js +3 -3
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/data/validators.js +1 -1
- package/dist/modules/translations/data/validators.js.map +1 -1
- package/dist/modules/translations/lib/batch.js +21 -1
- package/dist/modules/translations/lib/batch.js.map +2 -2
- package/dist/modules/translations/lib/helpers.js +3 -0
- package/dist/modules/translations/lib/helpers.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +14 -2
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/generated/entities/catalog_price_history_entry/index.ts +25 -0
- package/generated/entities/customer_group_terms/index.ts +1 -0
- package/generated/entities/ecommerce_store/index.ts +15 -0
- package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
- package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
- package/generated/entities.ids.generated.ts +8 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
- package/src/modules/auth/i18n/de.json +8 -0
- package/src/modules/auth/i18n/en.json +8 -0
- package/src/modules/auth/i18n/es.json +8 -0
- package/src/modules/auth/i18n/ko.json +8 -0
- package/src/modules/auth/i18n/pl.json +8 -0
- package/src/modules/catalog/AGENTS.md +5 -2
- package/src/modules/catalog/acl.ts +7 -0
- package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
- package/src/modules/catalog/api/prices/history/route.ts +139 -0
- package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
- package/src/modules/catalog/api/products/route.ts +53 -308
- package/src/modules/catalog/api/tags/route.ts +6 -0
- package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
- package/src/modules/catalog/cli.ts +84 -1
- package/src/modules/catalog/commands/categories.ts +54 -2
- package/src/modules/catalog/commands/prices.ts +43 -1
- package/src/modules/catalog/commands/products.ts +84 -1
- package/src/modules/catalog/commands/variants.ts +7 -0
- package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
- package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
- package/src/modules/catalog/components/lookupSources.ts +29 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
- package/src/modules/catalog/data/entities.ts +123 -1
- package/src/modules/catalog/data/validators.ts +118 -0
- package/src/modules/catalog/di.ts +23 -0
- package/src/modules/catalog/i18n/de.json +77 -0
- package/src/modules/catalog/i18n/en.json +77 -0
- package/src/modules/catalog/i18n/es.json +77 -0
- package/src/modules/catalog/i18n/ko.json +77 -0
- package/src/modules/catalog/i18n/pl.json +77 -0
- package/src/modules/catalog/lib/omnibus.ts +406 -0
- package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
- package/src/modules/catalog/lib/omnibusCache.ts +111 -0
- package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
- package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
- package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
- package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
- package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
- package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
- package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
- package/src/modules/catalog/lib/pricing.ts +42 -4
- package/src/modules/catalog/lib/productFilters.ts +349 -0
- package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
- package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
- package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
- package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
- package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
- package/src/modules/catalog/setup.ts +2 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
- package/src/modules/catalog/translations.ts +12 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +35 -0
- package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
- package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
- package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
- package/src/modules/customer_groups/data/entities.ts +4 -0
- package/src/modules/customer_groups/data/validators.ts +14 -0
- package/src/modules/customer_groups/i18n/de.json +12 -0
- package/src/modules/customer_groups/i18n/en.json +12 -0
- package/src/modules/customer_groups/i18n/es.json +12 -0
- package/src/modules/customer_groups/i18n/ko.json +12 -0
- package/src/modules/customer_groups/i18n/pl.json +12 -0
- package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
- package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
- package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
- package/src/modules/ecommerce/acl.ts +9 -0
- package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
- package/src/modules/ecommerce/api/openapi.ts +28 -0
- package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
- package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
- package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
- package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
- package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
- package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
- package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
- package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
- package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
- package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
- package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
- package/src/modules/ecommerce/api/stores/crud.ts +457 -0
- package/src/modules/ecommerce/api/stores/route.ts +66 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
- package/src/modules/ecommerce/commands/stores.ts +154 -0
- package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
- package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
- package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
- package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
- package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
- package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
- package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
- package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
- package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
- package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
- package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
- package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
- package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
- package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
- package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
- package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
- package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
- package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
- package/src/modules/ecommerce/components/channelLookups.ts +24 -0
- package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
- package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
- package/src/modules/ecommerce/components/storeChannels.ts +210 -0
- package/src/modules/ecommerce/components/storeDomains.ts +221 -0
- package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
- package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
- package/src/modules/ecommerce/components/storeSeo.ts +78 -0
- package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
- package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
- package/src/modules/ecommerce/data/enrichers.ts +213 -0
- package/src/modules/ecommerce/data/entities.ts +168 -0
- package/src/modules/ecommerce/data/validators.ts +587 -0
- package/src/modules/ecommerce/di.ts +9 -0
- package/src/modules/ecommerce/events.ts +27 -0
- package/src/modules/ecommerce/i18n/de.json +341 -0
- package/src/modules/ecommerce/i18n/en.json +341 -0
- package/src/modules/ecommerce/i18n/es.json +341 -0
- package/src/modules/ecommerce/i18n/ko.json +341 -0
- package/src/modules/ecommerce/i18n/pl.json +341 -0
- package/src/modules/ecommerce/index.ts +14 -0
- package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
- package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
- package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
- package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
- package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
- package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
- package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
- package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
- package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
- package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
- package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
- package/src/modules/ecommerce/lib/references.ts +93 -0
- package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
- package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
- package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
- package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
- package/src/modules/ecommerce/lib/storeContext.ts +669 -0
- package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
- package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
- package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
- package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
- package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
- package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
- package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
- package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
- package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
- package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
- package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
- package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
- package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
- package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
- package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
- package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
- package/src/modules/ecommerce/lib/types.ts +69 -0
- package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
- package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
- package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
- package/src/modules/ecommerce/notifications.client.ts +46 -0
- package/src/modules/ecommerce/notifications.ts +46 -0
- package/src/modules/ecommerce/search.ts +93 -0
- package/src/modules/ecommerce/setup.ts +15 -0
- package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
- package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
- package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
- package/src/modules/query_index/lib/batch.ts +25 -9
- package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
- package/src/modules/query_index/lib/document.ts +2 -0
- package/src/modules/query_index/lib/engine.ts +58 -1
- package/src/modules/query_index/lib/indexer.ts +8 -0
- package/src/modules/query_index/lib/search-tokens.ts +2 -0
- package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
- package/src/modules/translations/components/TranslationManager.tsx +3 -3
- package/src/modules/translations/data/validators.ts +1 -1
- package/src/modules/translations/lib/batch.ts +47 -0
- package/src/modules/translations/lib/helpers.ts +4 -0
- package/src/modules/translations/lib/resolve-field-list.ts +16 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { CATALOG_PRODUCT_TYPES } from '@open-mercato/core/modules/catalog/data/types'
|
|
3
|
+
import {
|
|
4
|
+
ecommercePriceDisplayModeSchema,
|
|
5
|
+
ecommercePriceSortFallbackSchema,
|
|
6
|
+
ecommerceStorefrontProductSortSchema,
|
|
7
|
+
} from '../../../data/validators'
|
|
8
|
+
|
|
9
|
+
const availabilityStateSchema = z.enum(['in_stock', 'low_stock', 'out_of_stock', 'backorder', 'preorder', 'not_tracked'])
|
|
10
|
+
|
|
11
|
+
export const storefrontPriceSchema = z.object({
|
|
12
|
+
currencyCode: z.string(),
|
|
13
|
+
displayMode: ecommercePriceDisplayModeSchema,
|
|
14
|
+
amount: z.number(),
|
|
15
|
+
formatted: z.string(),
|
|
16
|
+
isPromotion: z.boolean(),
|
|
17
|
+
originalAmount: z.number().nullable(),
|
|
18
|
+
formattedOriginal: z.string().nullable(),
|
|
19
|
+
lowestPriorAmount: z.number().nullable(),
|
|
20
|
+
formattedLowestPrior: z.string().nullable(),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const storefrontPriceRangeSchema = z.object({
|
|
24
|
+
min: z.number(),
|
|
25
|
+
max: z.number(),
|
|
26
|
+
formattedMin: z.string(),
|
|
27
|
+
formattedMax: z.string(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const storefrontPriceTierSchema = z.object({
|
|
31
|
+
minQuantity: z.number(),
|
|
32
|
+
maxQuantity: z.number().nullable(),
|
|
33
|
+
amount: z.number(),
|
|
34
|
+
formatted: z.string(),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const storefrontAvailabilitySchema = z.object({
|
|
38
|
+
state: availabilityStateSchema,
|
|
39
|
+
canFulfil: z.boolean(),
|
|
40
|
+
leadTimeDays: z.number().nullable(),
|
|
41
|
+
releaseAt: z.string().nullable(),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const storefrontProductListItemSchema = z.object({
|
|
45
|
+
id: z.string().uuid(),
|
|
46
|
+
handle: z.string().nullable(),
|
|
47
|
+
title: z.string(),
|
|
48
|
+
subtitle: z.string().nullable(),
|
|
49
|
+
defaultMediaUrl: z.string().nullable(),
|
|
50
|
+
productType: z.string(),
|
|
51
|
+
isConfigurable: z.boolean(),
|
|
52
|
+
hasVariants: z.boolean(),
|
|
53
|
+
variantCount: z.number().int(),
|
|
54
|
+
categories: z.array(z.object({ id: z.string().uuid(), name: z.string(), slug: z.string().nullable() })),
|
|
55
|
+
tags: z.array(z.string()),
|
|
56
|
+
price: storefrontPriceSchema.nullable(),
|
|
57
|
+
priceRange: storefrontPriceRangeSchema.nullable(),
|
|
58
|
+
availability: storefrontAvailabilitySchema,
|
|
59
|
+
badges: z.array(z.string()),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const storefrontFacetsSchema = z.object({
|
|
63
|
+
categories: z.array(
|
|
64
|
+
z.object({
|
|
65
|
+
id: z.string().uuid(),
|
|
66
|
+
name: z.string(),
|
|
67
|
+
slug: z.string().nullable(),
|
|
68
|
+
depth: z.number().int(),
|
|
69
|
+
parentId: z.string().uuid().nullable(),
|
|
70
|
+
count: z.number().int(),
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
tags: z.array(z.object({ slug: z.string(), label: z.string(), count: z.number().int() })),
|
|
74
|
+
priceRange: z.object({ min: z.number(), max: z.number(), currencyCode: z.string() }).nullable(),
|
|
75
|
+
options: z.array(
|
|
76
|
+
z.object({
|
|
77
|
+
code: z.string(),
|
|
78
|
+
label: z.string(),
|
|
79
|
+
values: z.array(z.object({ code: z.string(), label: z.string(), count: z.number().int() })),
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
82
|
+
productTypes: z.array(z.object({ type: z.string(), label: z.string(), count: z.number().int() })),
|
|
83
|
+
availability: z.array(z.object({ state: availabilityStateSchema, count: z.number().int() })),
|
|
84
|
+
availabilityScope: z.literal('page'),
|
|
85
|
+
total: z.number().int(),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const storefrontAppliedFiltersSchema = z.object({
|
|
89
|
+
search: z.string().optional(),
|
|
90
|
+
category: z
|
|
91
|
+
.object({ id: z.string().uuid(), slug: z.string().nullable(), includesDescendants: z.literal(true) })
|
|
92
|
+
.optional(),
|
|
93
|
+
tagSlugs: z.array(z.string()).optional(),
|
|
94
|
+
price: z.object({ min: z.number().nullable(), max: z.number().nullable(), approximate: z.boolean() }).optional(),
|
|
95
|
+
options: z.record(z.string(), z.array(z.string())).optional(),
|
|
96
|
+
productType: z.enum(CATALOG_PRODUCT_TYPES).optional(),
|
|
97
|
+
availability: z.object({ value: z.enum(['in_stock', 'available']), scope: z.literal('page') }).optional(),
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export const storefrontProductListResponseSchema = z.object({
|
|
101
|
+
items: z.array(storefrontProductListItemSchema),
|
|
102
|
+
total: z.number().int(),
|
|
103
|
+
page: z.number().int(),
|
|
104
|
+
pageSize: z.number().int(),
|
|
105
|
+
totalPages: z.number().int(),
|
|
106
|
+
facets: storefrontFacetsSchema.describe(
|
|
107
|
+
'Cross-excluded facet counts over the buyer assortment; priceRange is buyer-priced, availability counts the returned page',
|
|
108
|
+
),
|
|
109
|
+
effectiveLocale: z.string(),
|
|
110
|
+
requestedLocale: z.string().nullable(),
|
|
111
|
+
currencyCode: z.string(),
|
|
112
|
+
taxMode: ecommercePriceDisplayModeSchema,
|
|
113
|
+
appliedFilters: storefrontAppliedFiltersSchema,
|
|
114
|
+
availableSorts: z.array(ecommerceStorefrontProductSortSchema),
|
|
115
|
+
appliedSort: ecommerceStorefrontProductSortSchema,
|
|
116
|
+
priceSort: z.object({ cap: z.number().int(), fallback: ecommercePriceSortFallbackSchema, capExceeded: z.boolean() }),
|
|
117
|
+
sortApproximate: z.boolean(),
|
|
118
|
+
sortUnavailable: z.boolean(),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const dimensionsSchema = z
|
|
122
|
+
.object({
|
|
123
|
+
length: z.number().nullable(),
|
|
124
|
+
width: z.number().nullable(),
|
|
125
|
+
height: z.number().nullable(),
|
|
126
|
+
unit: z.string().nullable(),
|
|
127
|
+
})
|
|
128
|
+
.nullable()
|
|
129
|
+
|
|
130
|
+
const optionSchemaSchema = z
|
|
131
|
+
.object({
|
|
132
|
+
version: z.number().int().optional(),
|
|
133
|
+
name: z.string().nullable().optional(),
|
|
134
|
+
description: z.string().nullable().optional(),
|
|
135
|
+
options: z.array(
|
|
136
|
+
z.object({
|
|
137
|
+
code: z.string(),
|
|
138
|
+
label: z.string(),
|
|
139
|
+
description: z.string().nullable().optional(),
|
|
140
|
+
inputType: z.string(),
|
|
141
|
+
isRequired: z.boolean().optional(),
|
|
142
|
+
isMultiple: z.boolean().optional(),
|
|
143
|
+
choices: z.array(z.object({ code: z.string(), label: z.string().nullable().optional() })).optional(),
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
})
|
|
147
|
+
.nullable()
|
|
148
|
+
|
|
149
|
+
export const storefrontProductDetailResponseSchema = storefrontProductListItemSchema
|
|
150
|
+
.omit({ categories: true })
|
|
151
|
+
.extend({
|
|
152
|
+
description: z.string().nullable(),
|
|
153
|
+
sku: z.string().nullable(),
|
|
154
|
+
media: z.array(z.object({ id: z.string(), url: z.string(), alt: z.string().nullable(), sortOrder: z.number().int() })),
|
|
155
|
+
dimensions: dimensionsSchema,
|
|
156
|
+
weightValue: z.number().nullable(),
|
|
157
|
+
weightUnit: z.string().nullable(),
|
|
158
|
+
categories: z.array(
|
|
159
|
+
z.object({ id: z.string().uuid(), name: z.string(), slug: z.string().nullable(), ancestorIds: z.array(z.string().uuid()) }),
|
|
160
|
+
),
|
|
161
|
+
breadcrumb: z.array(z.object({ id: z.string().uuid(), name: z.string(), slug: z.string().nullable() })),
|
|
162
|
+
optionSchema: optionSchemaSchema,
|
|
163
|
+
variants: z.array(
|
|
164
|
+
z.object({
|
|
165
|
+
id: z.string().uuid(),
|
|
166
|
+
name: z.string(),
|
|
167
|
+
sku: z.string().nullable(),
|
|
168
|
+
optionValues: z.record(z.string(), z.string()),
|
|
169
|
+
isDefault: z.boolean(),
|
|
170
|
+
price: storefrontPriceSchema.nullable(),
|
|
171
|
+
priceTiers: z.array(storefrontPriceTierSchema).describe('Quantity tiers of this variant for this buyer'),
|
|
172
|
+
availability: storefrontAvailabilitySchema,
|
|
173
|
+
dimensions: dimensionsSchema,
|
|
174
|
+
weightValue: z.number().nullable(),
|
|
175
|
+
weightUnit: z.string().nullable(),
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
selectedVariantId: z
|
|
179
|
+
.string()
|
|
180
|
+
.uuid()
|
|
181
|
+
.nullable()
|
|
182
|
+
.describe('The requested variantId when it is one of variants, else the default variant, else null'),
|
|
183
|
+
quantityRules: z.object({
|
|
184
|
+
minOrderQuantity: z.number().nullable(),
|
|
185
|
+
maxOrderQuantity: z.number().nullable(),
|
|
186
|
+
quantityIncrement: z.number().nullable(),
|
|
187
|
+
}),
|
|
188
|
+
priceTiers: z
|
|
189
|
+
.array(storefrontPriceTierSchema)
|
|
190
|
+
.describe('Quantity tiers of selectedVariantId when one is selected, else of the product'),
|
|
191
|
+
relatedProducts: z.array(storefrontProductListItemSchema).max(8),
|
|
192
|
+
seo: z.object({ title: z.string().nullable(), description: z.string().nullable(), canonicalUrl: z.string().nullable() }),
|
|
193
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'
|
|
5
|
+
import { ecommerceStorefrontProductListQuerySchema } from '../../../data/validators'
|
|
6
|
+
import { enforceStorefrontRateLimit } from '../../../lib/storefrontRateLimit'
|
|
7
|
+
import type { StoreContextService } from '../../../lib/storeContextService'
|
|
8
|
+
import { cachedListStorefrontProducts } from '../../../lib/storefrontProductCache'
|
|
9
|
+
import { parseStorefrontProductListQuery } from '../../../lib/storefrontQuery'
|
|
10
|
+
import {
|
|
11
|
+
storefrontErrorSchema,
|
|
12
|
+
storefrontInvalidQueryErrorSchema,
|
|
13
|
+
storefrontRouteErrorResponse,
|
|
14
|
+
storefrontSuccessHeaders,
|
|
15
|
+
runInStoreCacheTenant,
|
|
16
|
+
} from '../storefrontRouteSupport'
|
|
17
|
+
import { storefrontProductListResponseSchema } from './openapiSchemas'
|
|
18
|
+
|
|
19
|
+
export const metadata = {
|
|
20
|
+
path: '/ecommerce/storefront/products',
|
|
21
|
+
GET: {
|
|
22
|
+
requireAuth: false,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ANONYMOUS_CACHE_CONTROL = 'public, max-age=30, stale-while-revalidate=30'
|
|
27
|
+
|
|
28
|
+
export async function GET(req: Request) {
|
|
29
|
+
try {
|
|
30
|
+
const query = parseStorefrontProductListQuery(new URL(req.url).searchParams)
|
|
31
|
+
const container = await createRequestContainer()
|
|
32
|
+
const service = container.resolve('storeContextService') as StoreContextService
|
|
33
|
+
const context = await service.resolve(req, { pathname: query.path ?? '/' })
|
|
34
|
+
return await runInStoreCacheTenant(context, async () => {
|
|
35
|
+
const rateLimited = await enforceStorefrontRateLimit(container, req, context, 'products')
|
|
36
|
+
if (rateLimited) return rateLimited
|
|
37
|
+
const body = await cachedListStorefrontProducts(container, context, query)
|
|
38
|
+
const headers = storefrontSuccessHeaders(context, ANONYMOUS_CACHE_CONTROL)
|
|
39
|
+
if (body.sortApproximate) headers['X-Sort-Approximate'] = 'true'
|
|
40
|
+
if (body.sortUnavailable) headers['X-Sort-Unavailable'] = 'true'
|
|
41
|
+
return NextResponse.json(body, { headers })
|
|
42
|
+
})
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return storefrontRouteErrorResponse(error, {
|
|
45
|
+
message: 'Storefront product listing failed',
|
|
46
|
+
code: 'ecommerce.storefront_products_failed',
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default GET
|
|
52
|
+
|
|
53
|
+
const storefrontProductsTag = 'Ecommerce'
|
|
54
|
+
|
|
55
|
+
const storefrontProductsGetDoc: OpenApiMethodDoc = {
|
|
56
|
+
summary: 'List storefront products for the current buyer',
|
|
57
|
+
description:
|
|
58
|
+
'Public. Resolves the store from the Host header and the optional portal session, then lists the products of the buyer\'s effective assortment with buyer-resolved prices. Unknown, repeated or malformed parameters are rejected with 400. Options use bracket notation (`options[color]=red,blue`). `availability=` is page-scoped: `total` counts the pre-availability set and a page may be short. Facets count each dimension against every other active filter (cross-exclusion); category, tag, option and product-type counts are shared per assortment scope (and empty past 10 000 products in the search universe), `priceRange` spans the buyer\'s own prices over the filtered set without the price filter, and `availability` counts the returned page only. `X-Sort-Approximate: true` marks a price order past the sort cap; `X-Sort-Unavailable: true` marks a declined price sort. Anonymous responses are `public, max-age=30, stale-while-revalidate=30`; authenticated responses are `private, no-store`. Cached server-side for 30 s per buyer context digest. Rate limited per IP and store.',
|
|
59
|
+
tags: [storefrontProductsTag],
|
|
60
|
+
query: ecommerceStorefrontProductListQuerySchema,
|
|
61
|
+
responses: [
|
|
62
|
+
{ status: 200, description: 'Products of the buyer\'s effective assortment.', schema: storefrontProductListResponseSchema },
|
|
63
|
+
],
|
|
64
|
+
errors: [
|
|
65
|
+
{ status: 400, description: 'Unknown, repeated or malformed query parameters', schema: storefrontInvalidQueryErrorSchema },
|
|
66
|
+
{ status: 401, description: 'Portal session invalid or issued for another tenant or organization', schema: storefrontErrorSchema },
|
|
67
|
+
{ status: 403, description: 'Draft store (only when OM_ECOMMERCE_DEV_STORE_SLUG=true)', schema: storefrontErrorSchema },
|
|
68
|
+
{ status: 404, description: 'No store serves this host or path', schema: storefrontErrorSchema },
|
|
69
|
+
{ status: 410, description: 'Store archived', schema: storefrontErrorSchema },
|
|
70
|
+
{ status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },
|
|
71
|
+
{ status: 503, description: 'Store misconfigured (no default channel binding)', schema: storefrontErrorSchema },
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const openApi: OpenApiRouteDoc = {
|
|
76
|
+
tag: storefrontProductsTag,
|
|
77
|
+
summary: 'Public storefront product listing',
|
|
78
|
+
methods: {
|
|
79
|
+
GET: storefrontProductsGetDoc,
|
|
80
|
+
},
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const storefrontSearchSuggestResponseSchema = z.object({
|
|
4
|
+
products: z.array(
|
|
5
|
+
z.object({
|
|
6
|
+
id: z.string().uuid(),
|
|
7
|
+
handle: z.string().nullable(),
|
|
8
|
+
title: z.string(),
|
|
9
|
+
defaultMediaUrl: z.string().nullable(),
|
|
10
|
+
formattedPrice: z
|
|
11
|
+
.string()
|
|
12
|
+
.nullable()
|
|
13
|
+
.describe('The buyer\'s resolved price (the lowest variant price for products priced per variant)'),
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
categories: z.array(
|
|
17
|
+
z.object({
|
|
18
|
+
id: z.string().uuid(),
|
|
19
|
+
name: z.string(),
|
|
20
|
+
slug: z.string().nullable(),
|
|
21
|
+
}),
|
|
22
|
+
),
|
|
23
|
+
suggestions: z.array(z.string()).describe('Query completions; always empty in this phase'),
|
|
24
|
+
effectiveLocale: z.string(),
|
|
25
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'
|
|
5
|
+
import { ecommerceStorefrontSearchSuggestQuerySchema } from '../../../../data/validators'
|
|
6
|
+
import { enforceStorefrontRateLimit } from '../../../../lib/storefrontRateLimit'
|
|
7
|
+
import type { StoreContextService } from '../../../../lib/storeContextService'
|
|
8
|
+
import { parseStorefrontSearchSuggestQuery } from '../../../../lib/storefrontQuery'
|
|
9
|
+
import { cachedSuggestStorefrontSearch } from '../../../../lib/storefrontSearchCache'
|
|
10
|
+
import {
|
|
11
|
+
storefrontErrorSchema,
|
|
12
|
+
storefrontInvalidQueryErrorSchema,
|
|
13
|
+
storefrontRouteErrorResponse,
|
|
14
|
+
storefrontSuccessHeaders,
|
|
15
|
+
runInStoreCacheTenant,
|
|
16
|
+
} from '../../storefrontRouteSupport'
|
|
17
|
+
import { storefrontSearchSuggestResponseSchema } from './openapiSchemas'
|
|
18
|
+
|
|
19
|
+
export const metadata = {
|
|
20
|
+
path: '/ecommerce/storefront/search/suggest',
|
|
21
|
+
GET: {
|
|
22
|
+
requireAuth: false,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ANONYMOUS_CACHE_CONTROL = 'public, max-age=30, stale-while-revalidate=30'
|
|
27
|
+
|
|
28
|
+
export async function GET(req: Request) {
|
|
29
|
+
try {
|
|
30
|
+
const query = parseStorefrontSearchSuggestQuery(new URL(req.url).searchParams)
|
|
31
|
+
const container = await createRequestContainer()
|
|
32
|
+
const service = container.resolve('storeContextService') as StoreContextService
|
|
33
|
+
const context = await service.resolve(req, { pathname: query.path ?? '/' })
|
|
34
|
+
return await runInStoreCacheTenant(context, async () => {
|
|
35
|
+
const rateLimited = await enforceStorefrontRateLimit(container, req, context, 'searchSuggest')
|
|
36
|
+
if (rateLimited) return rateLimited
|
|
37
|
+
const body = await cachedSuggestStorefrontSearch(container, context, query)
|
|
38
|
+
return NextResponse.json(body, { headers: storefrontSuccessHeaders(context, ANONYMOUS_CACHE_CONTROL) })
|
|
39
|
+
})
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return storefrontRouteErrorResponse(error, {
|
|
42
|
+
message: 'Storefront search suggest failed',
|
|
43
|
+
code: 'ecommerce.storefront_search_suggest_failed',
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default GET
|
|
49
|
+
|
|
50
|
+
const storefrontSearchTag = 'Ecommerce'
|
|
51
|
+
|
|
52
|
+
const storefrontSearchSuggestGetDoc: OpenApiMethodDoc = {
|
|
53
|
+
summary: 'Typeahead suggestions for the current buyer',
|
|
54
|
+
description:
|
|
55
|
+
'Public. Resolves the store from the Host header and the optional portal session, then returns up to `limit` products of the buyer\'s effective assortment matching `q`, with the buyer\'s formatted price, and up to `limit` visible categories whose localized name matches `q`. Products are ranked by the `tokens` or `pgvector` search strategy with the assortment scope inside the ranking query, or by the escaped `ILIKE` match when neither is available; the payload is identical either way. A `q` shorter than 2 characters returns empty arrays, never an error. Products whose availability policy hides them when out of stock are omitted. No facets are computed. Anonymous responses are `public, max-age=30, stale-while-revalidate=30`; authenticated responses are `private, no-store`. Cached server-side for 30 s per buyer context digest. Rate limited per IP and store.',
|
|
56
|
+
tags: [storefrontSearchTag],
|
|
57
|
+
query: ecommerceStorefrontSearchSuggestQuerySchema,
|
|
58
|
+
responses: [
|
|
59
|
+
{ status: 200, description: 'Products and categories matching the query.', schema: storefrontSearchSuggestResponseSchema },
|
|
60
|
+
],
|
|
61
|
+
errors: [
|
|
62
|
+
{ status: 400, description: 'Missing `q`, or unknown, repeated or malformed query parameters', schema: storefrontInvalidQueryErrorSchema },
|
|
63
|
+
{ status: 401, description: 'Portal session invalid or issued for another tenant or organization', schema: storefrontErrorSchema },
|
|
64
|
+
{ status: 403, description: 'Draft store (only when OM_ECOMMERCE_DEV_STORE_SLUG=true)', schema: storefrontErrorSchema },
|
|
65
|
+
{ status: 404, description: 'No store serves this host or path', schema: storefrontErrorSchema },
|
|
66
|
+
{ status: 410, description: 'Store archived', schema: storefrontErrorSchema },
|
|
67
|
+
{ status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },
|
|
68
|
+
{ status: 503, description: 'Store misconfigured (no default channel binding)', schema: storefrontErrorSchema },
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const openApi: OpenApiRouteDoc = {
|
|
73
|
+
tag: storefrontSearchTag,
|
|
74
|
+
summary: 'Public storefront search suggestions',
|
|
75
|
+
methods: {
|
|
76
|
+
GET: storefrontSearchSuggestGetDoc,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
4
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
5
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
6
|
+
import { isStorefrontResolutionError } from '../../lib/storeContext'
|
|
7
|
+
import { isStorefrontQueryError, type StorefrontQueryError } from '../../lib/storefrontQuery'
|
|
8
|
+
import type { StoreContext } from '../../lib/types'
|
|
9
|
+
|
|
10
|
+
export const STOREFRONT_VARY_HEADER = 'Cookie, Authorization, X-Locale, Accept-Language'
|
|
11
|
+
export const STOREFRONT_PRIVATE_CACHE_CONTROL = 'private, no-store'
|
|
12
|
+
export const STOREFRONT_ERROR_CACHE_CONTROL = 'no-store'
|
|
13
|
+
|
|
14
|
+
export const storefrontErrorSchema = z.object({ error: z.string() })
|
|
15
|
+
|
|
16
|
+
export const storefrontInvalidQueryErrorSchema = z.object({
|
|
17
|
+
error: z.literal('invalid_query'),
|
|
18
|
+
fields: z.record(z.string(), z.string()),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const logger = createLogger('ecommerce').child({ component: 'storefront-route' })
|
|
22
|
+
|
|
23
|
+
export function storefrontErrorResponse(status: number, body: Record<string, unknown>): NextResponse {
|
|
24
|
+
return NextResponse.json(body, {
|
|
25
|
+
status,
|
|
26
|
+
headers: { 'Cache-Control': STOREFRONT_ERROR_CACHE_CONTROL, Vary: STOREFRONT_VARY_HEADER },
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function storefrontInvalidQueryResponse(error: StorefrontQueryError): NextResponse {
|
|
31
|
+
const fields: Record<string, string> = {}
|
|
32
|
+
for (const issue of error.issues) {
|
|
33
|
+
if (!(issue.parameter in fields)) fields[issue.parameter] = issue.message
|
|
34
|
+
}
|
|
35
|
+
return storefrontErrorResponse(400, { error: 'invalid_query', fields })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Authenticated responses are never stored by a browser or CDN (`private, no-store`); anonymous
|
|
40
|
+
* ones may be, for the given policy. The server-side cache applies to both, keyed on the digest.
|
|
41
|
+
*/
|
|
42
|
+
export function storefrontSuccessHeaders(context: StoreContext, anonymousCacheControl: string): Record<string, string> {
|
|
43
|
+
return {
|
|
44
|
+
'Cache-Control': context.buyer.isAuthenticated ? STOREFRONT_PRIVATE_CACHE_CONTROL : anonymousCacheControl,
|
|
45
|
+
Vary: STOREFRONT_VARY_HEADER,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type StorefrontRouteFailure = { message: string; code: string }
|
|
50
|
+
|
|
51
|
+
/** Query grammar → 400, store resolution → its status with `{ error: code }`, anything else → reported 500. */
|
|
52
|
+
export function storefrontRouteErrorResponse(error: unknown, failure: StorefrontRouteFailure): NextResponse {
|
|
53
|
+
if (isStorefrontQueryError(error)) return storefrontInvalidQueryResponse(error)
|
|
54
|
+
if (isStorefrontResolutionError(error)) return storefrontErrorResponse(error.status, { error: error.code })
|
|
55
|
+
logger.error(failure.message, { err: error })
|
|
56
|
+
getTelemetryRuntime()?.reportError(error, { module: 'ecommerce', code: failure.code })
|
|
57
|
+
return storefrontErrorResponse(500, { error: 'internal_error' })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Runs the rest of a storefront request in the resolved store's cache tenant. Admin writes invalidate
|
|
62
|
+
* cached values (module config, Omnibus results, CRUD lists) in their tenant's namespace; an
|
|
63
|
+
* unauthenticated storefront request would otherwise read the global namespace and keep serving a
|
|
64
|
+
* copy those invalidations never reach.
|
|
65
|
+
*/
|
|
66
|
+
export function runInStoreCacheTenant<T>(context: Pick<StoreContext, 'tenantId'>, fn: () => Promise<T>): Promise<T> {
|
|
67
|
+
return runWithCacheTenant(context.tenantId, fn)
|
|
68
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
4
|
+
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
5
|
+
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
6
|
+
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
|
+
import {
|
|
8
|
+
bridgeLegacyGuard,
|
|
9
|
+
runMutationGuards,
|
|
10
|
+
type MutationGuard,
|
|
11
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
12
|
+
import { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'
|
|
13
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
14
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
15
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
16
|
+
import type { StoreBrandingUpdateInput, StoreBrandingUpdateResult } from '../../../../commands/stores'
|
|
17
|
+
import { ecommerceStoreBrandingSchema, type EcommerceStoreBranding } from '../../../../data/validators'
|
|
18
|
+
import {
|
|
19
|
+
BRANDING_MANAGE_FEATURE,
|
|
20
|
+
resolveBrandingRouteContext,
|
|
21
|
+
resolveGrantedFeatures,
|
|
22
|
+
storeIdParamSchema,
|
|
23
|
+
} from '../../../../lib/storeBrandingRoute'
|
|
24
|
+
import {
|
|
25
|
+
parseBrandingInput,
|
|
26
|
+
STORE_BRANDING_RESOURCE_KIND,
|
|
27
|
+
STORE_BRANDING_UPDATE_COMMAND_ID,
|
|
28
|
+
} from '../../../../lib/storeBranding'
|
|
29
|
+
import { ecommerceInternalErrorBody } from '../../../../lib/crudSupport'
|
|
30
|
+
|
|
31
|
+
export const metadata = {
|
|
32
|
+
PUT: { requireAuth: true, requireFeatures: [BRANDING_MANAGE_FEATURE] },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const logger = createLogger('ecommerce').child({ component: 'store-branding-route' })
|
|
36
|
+
|
|
37
|
+
type RouteParams = { params?: { id?: string } | Promise<{ id?: string }> }
|
|
38
|
+
|
|
39
|
+
async function readStoreId(ctx: RouteParams): Promise<string | null> {
|
|
40
|
+
const params = await ctx.params
|
|
41
|
+
const parsed = storeIdParamSchema.safeParse(params?.id)
|
|
42
|
+
return parsed.success ? parsed.data : null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function readJsonBody(req: Request): Promise<{ ok: true; body: unknown } | { ok: false }> {
|
|
46
|
+
const text = await req.text()
|
|
47
|
+
if (text.trim().length === 0) return { ok: true, body: {} }
|
|
48
|
+
try {
|
|
49
|
+
return { ok: true, body: JSON.parse(text) }
|
|
50
|
+
} catch {
|
|
51
|
+
return { ok: false }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function invalidBranding(fieldErrors: Record<string, string>) {
|
|
56
|
+
const [message] = Object.values(fieldErrors)
|
|
57
|
+
return NextResponse.json({ error: message, fieldErrors }, { status: 400 })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function PUT(req: Request, ctx: RouteParams) {
|
|
61
|
+
try {
|
|
62
|
+
const context = await resolveBrandingRouteContext(req)
|
|
63
|
+
const { translate } = await resolveTranslations()
|
|
64
|
+
const storeId = await readStoreId(ctx)
|
|
65
|
+
if (!storeId) return NextResponse.json({ error: translate('ecommerce.errors.storeNotFound', 'The selected store does not exist in this organization.') }, { status: 404 })
|
|
66
|
+
|
|
67
|
+
const raw = await readJsonBody(req)
|
|
68
|
+
if (!raw.ok) {
|
|
69
|
+
return NextResponse.json(
|
|
70
|
+
{ error: translate('ecommerce.errors.invalidJsonBody', 'The request body is not valid JSON.') },
|
|
71
|
+
{ status: 400 },
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
const parsed = parseBrandingInput(raw.body, translate)
|
|
75
|
+
if (!parsed.ok) return invalidBranding(parsed.fieldErrors)
|
|
76
|
+
let branding: EcommerceStoreBranding = parsed.branding
|
|
77
|
+
|
|
78
|
+
const legacyGuard = bridgeLegacyGuard(context.container)
|
|
79
|
+
const guards: MutationGuard[] = [...getAllMutationGuardInstances()]
|
|
80
|
+
if (legacyGuard) guards.push(legacyGuard)
|
|
81
|
+
const guardInput = {
|
|
82
|
+
tenantId: context.tenantId,
|
|
83
|
+
organizationId: context.organizationId,
|
|
84
|
+
userId: context.auth.sub ?? '',
|
|
85
|
+
resourceKind: STORE_BRANDING_RESOURCE_KIND,
|
|
86
|
+
resourceId: storeId,
|
|
87
|
+
operation: 'update' as const,
|
|
88
|
+
requestMethod: req.method,
|
|
89
|
+
requestHeaders: req.headers,
|
|
90
|
+
}
|
|
91
|
+
const guardResult = await runMutationGuards(
|
|
92
|
+
guards,
|
|
93
|
+
{ ...guardInput, mutationPayload: { ...branding } },
|
|
94
|
+
{ userFeatures: await resolveGrantedFeatures(context) },
|
|
95
|
+
)
|
|
96
|
+
if (!guardResult.ok) {
|
|
97
|
+
const blocked = guardResult.errorBody ?? {
|
|
98
|
+
error: translate('ecommerce.errors.operationBlocked', 'This change was blocked. Review the store and try again.'),
|
|
99
|
+
}
|
|
100
|
+
return NextResponse.json(blocked, {
|
|
101
|
+
status: guardResult.errorStatus ?? 422,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
if (guardResult.modifiedPayload) {
|
|
105
|
+
const merged = parseBrandingInput({ ...branding, ...guardResult.modifiedPayload }, translate)
|
|
106
|
+
if (!merged.ok) return invalidBranding(merged.fieldErrors)
|
|
107
|
+
branding = merged.branding
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const commandBus = context.container.resolve('commandBus') as CommandBus
|
|
111
|
+
const { result, logEntry } = await commandBus.execute<StoreBrandingUpdateInput, StoreBrandingUpdateResult>(
|
|
112
|
+
STORE_BRANDING_UPDATE_COMMAND_ID,
|
|
113
|
+
{
|
|
114
|
+
input: { id: storeId, tenantId: context.tenantId, organizationId: context.organizationId, branding },
|
|
115
|
+
ctx: context.commandCtx,
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
for (const callback of guardResult.afterSuccessCallbacks) {
|
|
120
|
+
if (!callback.guard.afterSuccess) continue
|
|
121
|
+
try {
|
|
122
|
+
await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null })
|
|
123
|
+
} catch (callbackError) {
|
|
124
|
+
logger.error('Mutation guard afterSuccess callback failed', {
|
|
125
|
+
guardId: callback.guard.id,
|
|
126
|
+
err: callbackError,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const response = NextResponse.json({ id: result.id, updatedAt: result.updatedAt, branding: result.branding })
|
|
132
|
+
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
133
|
+
response.headers.set(
|
|
134
|
+
'x-om-operation',
|
|
135
|
+
serializeOperationMetadata({
|
|
136
|
+
id: logEntry.id,
|
|
137
|
+
undoToken: logEntry.undoToken,
|
|
138
|
+
commandId: logEntry.commandId,
|
|
139
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
140
|
+
resourceKind: logEntry.resourceKind ?? STORE_BRANDING_RESOURCE_KIND,
|
|
141
|
+
resourceId: logEntry.resourceId ?? result.id,
|
|
142
|
+
executedAt:
|
|
143
|
+
logEntry.createdAt instanceof Date
|
|
144
|
+
? logEntry.createdAt.toISOString()
|
|
145
|
+
: typeof logEntry.createdAt === 'string'
|
|
146
|
+
? logEntry.createdAt
|
|
147
|
+
: new Date().toISOString(),
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
return response
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
154
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err)
|
|
155
|
+
if (interceptorRejection) {
|
|
156
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })
|
|
157
|
+
}
|
|
158
|
+
logger.error('ecommerce.stores.branding.PUT failed', { err })
|
|
159
|
+
return NextResponse.json(await ecommerceInternalErrorBody(), { status: 500 })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const brandingResponseSchema = z.object({
|
|
164
|
+
id: z.string().uuid(),
|
|
165
|
+
updatedAt: z.string().describe('The store updated_at after the write; send it as the expected version of the next save.'),
|
|
166
|
+
branding: ecommerceStoreBrandingSchema,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const errorSchema = z.object({ error: z.string() }).passthrough()
|
|
170
|
+
|
|
171
|
+
const fieldErrorsSchema = z.object({
|
|
172
|
+
error: z.string(),
|
|
173
|
+
fieldErrors: z.record(z.string(), z.string()),
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const conflictSchema = z.object({
|
|
177
|
+
error: z.string(),
|
|
178
|
+
code: z.string(),
|
|
179
|
+
currentUpdatedAt: z.string(),
|
|
180
|
+
expectedUpdatedAt: z.string(),
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
export const openApi: OpenApiRouteDoc = {
|
|
184
|
+
tag: 'Ecommerce',
|
|
185
|
+
summary: 'Store branding',
|
|
186
|
+
pathParams: z.object({ id: z.string().uuid() }),
|
|
187
|
+
methods: {
|
|
188
|
+
PUT: {
|
|
189
|
+
summary: 'Replace the branding of a store',
|
|
190
|
+
description:
|
|
191
|
+
'Command route behind the Branding tab. Replaces settings.branding with the validated body (omitted or empty keys are cleared) and merges it into the stored settings server-side, so contact, display and SEO values are never touched. Requires ecommerce.branding.manage. Runs the mutation guard registry as an update, honours the x-om-ext-optimistic-lock-expected-updated-at header against the store updated_at (409 on a stale version), is undoable, and emits ecommerce.store.branding_updated, which evicts the storefront caches of the store.',
|
|
192
|
+
tags: ['Ecommerce'],
|
|
193
|
+
requestBody: { schema: ecommerceStoreBrandingSchema },
|
|
194
|
+
responses: [{ status: 200, description: 'Branding saved', schema: brandingResponseSchema }],
|
|
195
|
+
errors: [
|
|
196
|
+
{ status: 400, description: 'A branding value is invalid; fieldErrors maps each rejected key to a message', schema: fieldErrorsSchema },
|
|
197
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
198
|
+
{ status: 403, description: 'Missing ecommerce.branding.manage', schema: errorSchema },
|
|
199
|
+
{ status: 404, description: 'The store does not exist in the selected organization', schema: errorSchema },
|
|
200
|
+
{ status: 409, description: 'The store changed since it was loaded (optimistic lock)', schema: conflictSchema },
|
|
201
|
+
{ status: 422, description: 'Blocked by a mutation guard', schema: errorSchema },
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
}
|