@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,78 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
|
|
4
|
+
import { ecommerceStorefrontProductListQuerySchema } from "../../../data/validators.js";
|
|
5
|
+
import { enforceStorefrontRateLimit } from "../../../lib/storefrontRateLimit.js";
|
|
6
|
+
import { cachedListStorefrontProducts } from "../../../lib/storefrontProductCache.js";
|
|
7
|
+
import { parseStorefrontProductListQuery } from "../../../lib/storefrontQuery.js";
|
|
8
|
+
import {
|
|
9
|
+
storefrontErrorSchema,
|
|
10
|
+
storefrontInvalidQueryErrorSchema,
|
|
11
|
+
storefrontRouteErrorResponse,
|
|
12
|
+
storefrontSuccessHeaders,
|
|
13
|
+
runInStoreCacheTenant
|
|
14
|
+
} from "../storefrontRouteSupport.js";
|
|
15
|
+
import { storefrontProductListResponseSchema } from "./openapiSchemas.js";
|
|
16
|
+
const metadata = {
|
|
17
|
+
path: "/ecommerce/storefront/products",
|
|
18
|
+
GET: {
|
|
19
|
+
requireAuth: false
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const ANONYMOUS_CACHE_CONTROL = "public, max-age=30, stale-while-revalidate=30";
|
|
23
|
+
async function GET(req) {
|
|
24
|
+
try {
|
|
25
|
+
const query = parseStorefrontProductListQuery(new URL(req.url).searchParams);
|
|
26
|
+
const container = await createRequestContainer();
|
|
27
|
+
const service = container.resolve("storeContextService");
|
|
28
|
+
const context = await service.resolve(req, { pathname: query.path ?? "/" });
|
|
29
|
+
return await runInStoreCacheTenant(context, async () => {
|
|
30
|
+
const rateLimited = await enforceStorefrontRateLimit(container, req, context, "products");
|
|
31
|
+
if (rateLimited) return rateLimited;
|
|
32
|
+
const body = await cachedListStorefrontProducts(container, context, query);
|
|
33
|
+
const headers = storefrontSuccessHeaders(context, ANONYMOUS_CACHE_CONTROL);
|
|
34
|
+
if (body.sortApproximate) headers["X-Sort-Approximate"] = "true";
|
|
35
|
+
if (body.sortUnavailable) headers["X-Sort-Unavailable"] = "true";
|
|
36
|
+
return NextResponse.json(body, { headers });
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return storefrontRouteErrorResponse(error, {
|
|
40
|
+
message: "Storefront product listing failed",
|
|
41
|
+
code: "ecommerce.storefront_products_failed"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var route_default = GET;
|
|
46
|
+
const storefrontProductsTag = "Ecommerce";
|
|
47
|
+
const storefrontProductsGetDoc = {
|
|
48
|
+
summary: "List storefront products for the current buyer",
|
|
49
|
+
description: "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.",
|
|
50
|
+
tags: [storefrontProductsTag],
|
|
51
|
+
query: ecommerceStorefrontProductListQuerySchema,
|
|
52
|
+
responses: [
|
|
53
|
+
{ status: 200, description: "Products of the buyer's effective assortment.", schema: storefrontProductListResponseSchema }
|
|
54
|
+
],
|
|
55
|
+
errors: [
|
|
56
|
+
{ status: 400, description: "Unknown, repeated or malformed query parameters", schema: storefrontInvalidQueryErrorSchema },
|
|
57
|
+
{ status: 401, description: "Portal session invalid or issued for another tenant or organization", schema: storefrontErrorSchema },
|
|
58
|
+
{ status: 403, description: "Draft store (only when OM_ECOMMERCE_DEV_STORE_SLUG=true)", schema: storefrontErrorSchema },
|
|
59
|
+
{ status: 404, description: "No store serves this host or path", schema: storefrontErrorSchema },
|
|
60
|
+
{ status: 410, description: "Store archived", schema: storefrontErrorSchema },
|
|
61
|
+
{ status: 429, description: "Too many requests", schema: rateLimitErrorSchema },
|
|
62
|
+
{ status: 503, description: "Store misconfigured (no default channel binding)", schema: storefrontErrorSchema }
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
const openApi = {
|
|
66
|
+
tag: storefrontProductsTag,
|
|
67
|
+
summary: "Public storefront product listing",
|
|
68
|
+
methods: {
|
|
69
|
+
GET: storefrontProductsGetDoc
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export {
|
|
73
|
+
GET,
|
|
74
|
+
route_default as default,
|
|
75
|
+
metadata,
|
|
76
|
+
openApi
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/ecommerce/api/storefront/products/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { ecommerceStorefrontProductListQuerySchema } from '../../../data/validators'\nimport { enforceStorefrontRateLimit } from '../../../lib/storefrontRateLimit'\nimport type { StoreContextService } from '../../../lib/storeContextService'\nimport { cachedListStorefrontProducts } from '../../../lib/storefrontProductCache'\nimport { parseStorefrontProductListQuery } from '../../../lib/storefrontQuery'\nimport {\n storefrontErrorSchema,\n storefrontInvalidQueryErrorSchema,\n storefrontRouteErrorResponse,\n storefrontSuccessHeaders,\n runInStoreCacheTenant,\n} from '../storefrontRouteSupport'\nimport { storefrontProductListResponseSchema } from './openapiSchemas'\n\nexport const metadata = {\n path: '/ecommerce/storefront/products',\n GET: {\n requireAuth: false,\n },\n}\n\nconst ANONYMOUS_CACHE_CONTROL = 'public, max-age=30, stale-while-revalidate=30'\n\nexport async function GET(req: Request) {\n try {\n const query = parseStorefrontProductListQuery(new URL(req.url).searchParams)\n const container = await createRequestContainer()\n const service = container.resolve('storeContextService') as StoreContextService\n const context = await service.resolve(req, { pathname: query.path ?? '/' })\n return await runInStoreCacheTenant(context, async () => {\n const rateLimited = await enforceStorefrontRateLimit(container, req, context, 'products')\n if (rateLimited) return rateLimited\n const body = await cachedListStorefrontProducts(container, context, query)\n const headers = storefrontSuccessHeaders(context, ANONYMOUS_CACHE_CONTROL)\n if (body.sortApproximate) headers['X-Sort-Approximate'] = 'true'\n if (body.sortUnavailable) headers['X-Sort-Unavailable'] = 'true'\n return NextResponse.json(body, { headers })\n })\n } catch (error) {\n return storefrontRouteErrorResponse(error, {\n message: 'Storefront product listing failed',\n code: 'ecommerce.storefront_products_failed',\n })\n }\n}\n\nexport default GET\n\nconst storefrontProductsTag = 'Ecommerce'\n\nconst storefrontProductsGetDoc: OpenApiMethodDoc = {\n summary: 'List storefront products for the current buyer',\n description:\n '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.',\n tags: [storefrontProductsTag],\n query: ecommerceStorefrontProductListQuerySchema,\n responses: [\n { status: 200, description: 'Products of the buyer\\'s effective assortment.', schema: storefrontProductListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Unknown, repeated or malformed query parameters', schema: storefrontInvalidQueryErrorSchema },\n { status: 401, description: 'Portal session invalid or issued for another tenant or organization', schema: storefrontErrorSchema },\n { status: 403, description: 'Draft store (only when OM_ECOMMERCE_DEV_STORE_SLUG=true)', schema: storefrontErrorSchema },\n { status: 404, description: 'No store serves this host or path', schema: storefrontErrorSchema },\n { status: 410, description: 'Store archived', schema: storefrontErrorSchema },\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n { status: 503, description: 'Store misconfigured (no default channel binding)', schema: storefrontErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: storefrontProductsTag,\n summary: 'Public storefront product listing',\n methods: {\n GET: storefrontProductsGetDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AAEvC,SAAS,4BAA4B;AACrC,SAAS,iDAAiD;AAC1D,SAAS,kCAAkC;AAE3C,SAAS,oCAAoC;AAC7C,SAAS,uCAAuC;AAChD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2CAA2C;AAE7C,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,KAAK;AAAA,IACH,aAAa;AAAA,EACf;AACF;AAEA,MAAM,0BAA0B;AAEhC,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,QAAQ,gCAAgC,IAAI,IAAI,IAAI,GAAG,EAAE,YAAY;AAC3E,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAAQ,qBAAqB;AACvD,UAAM,UAAU,MAAM,QAAQ,QAAQ,KAAK,EAAE,UAAU,MAAM,QAAQ,IAAI,CAAC;AAC1E,WAAO,MAAM,sBAAsB,SAAS,YAAY;AACtD,YAAM,cAAc,MAAM,2BAA2B,WAAW,KAAK,SAAS,UAAU;AACxF,UAAI,YAAa,QAAO;AACxB,YAAM,OAAO,MAAM,6BAA6B,WAAW,SAAS,KAAK;AACzE,YAAM,UAAU,yBAAyB,SAAS,uBAAuB;AACzE,UAAI,KAAK,gBAAiB,SAAQ,oBAAoB,IAAI;AAC1D,UAAI,KAAK,gBAAiB,SAAQ,oBAAoB,IAAI;AAC1D,aAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,CAAC;AAAA,IAC5C,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,6BAA6B,OAAO;AAAA,MACzC,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAEA,IAAO,gBAAQ;AAEf,MAAM,wBAAwB;AAE9B,MAAM,2BAA6C;AAAA,EACjD,SAAS;AAAA,EACT,aACE;AAAA,EACF,MAAM,CAAC,qBAAqB;AAAA,EAC5B,OAAO;AAAA,EACP,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,iDAAkD,QAAQ,oCAAoC;AAAA,EAC5H;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,mDAAmD,QAAQ,kCAAkC;AAAA,IACzH,EAAE,QAAQ,KAAK,aAAa,uEAAuE,QAAQ,sBAAsB;AAAA,IACjI,EAAE,QAAQ,KAAK,aAAa,4DAA4D,QAAQ,sBAAsB;AAAA,IACtH,EAAE,QAAQ,KAAK,aAAa,qCAAqC,QAAQ,sBAAsB;AAAA,IAC/F,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,sBAAsB;AAAA,IAC5E,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,IAC9E,EAAE,QAAQ,KAAK,aAAa,oDAAoD,QAAQ,sBAAsB;AAAA,EAChH;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const storefrontSearchSuggestResponseSchema = z.object({
|
|
3
|
+
products: z.array(
|
|
4
|
+
z.object({
|
|
5
|
+
id: z.string().uuid(),
|
|
6
|
+
handle: z.string().nullable(),
|
|
7
|
+
title: z.string(),
|
|
8
|
+
defaultMediaUrl: z.string().nullable(),
|
|
9
|
+
formattedPrice: z.string().nullable().describe("The buyer's resolved price (the lowest variant price for products priced per variant)")
|
|
10
|
+
})
|
|
11
|
+
),
|
|
12
|
+
categories: z.array(
|
|
13
|
+
z.object({
|
|
14
|
+
id: z.string().uuid(),
|
|
15
|
+
name: z.string(),
|
|
16
|
+
slug: z.string().nullable()
|
|
17
|
+
})
|
|
18
|
+
),
|
|
19
|
+
suggestions: z.array(z.string()).describe("Query completions; always empty in this phase"),
|
|
20
|
+
effectiveLocale: z.string()
|
|
21
|
+
});
|
|
22
|
+
export {
|
|
23
|
+
storefrontSearchSuggestResponseSchema
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=openapiSchemas.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\n\nexport const storefrontSearchSuggestResponseSchema = z.object({\n products: z.array(\n z.object({\n id: z.string().uuid(),\n handle: z.string().nullable(),\n title: z.string(),\n defaultMediaUrl: z.string().nullable(),\n formattedPrice: z\n .string()\n .nullable()\n .describe('The buyer\\'s resolved price (the lowest variant price for products priced per variant)'),\n }),\n ),\n categories: z.array(\n z.object({\n id: z.string().uuid(),\n name: z.string(),\n slug: z.string().nullable(),\n }),\n ),\n suggestions: z.array(z.string()).describe('Query completions; always empty in this phase'),\n effectiveLocale: z.string(),\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,wCAAwC,EAAE,OAAO;AAAA,EAC5D,UAAU,EAAE;AAAA,IACV,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,MAC5B,OAAO,EAAE,OAAO;AAAA,MAChB,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,MACrC,gBAAgB,EACb,OAAO,EACP,SAAS,EACT,SAAS,uFAAwF;AAAA,IACtG,CAAC;AAAA,EACH;AAAA,EACA,YAAY,EAAE;AAAA,IACZ,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,MAAM,EAAE,OAAO;AAAA,MACf,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,IAC5B,CAAC;AAAA,EACH;AAAA,EACA,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,+CAA+C;AAAA,EACzF,iBAAiB,EAAE,OAAO;AAC5B,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
3
|
+
import { rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
|
|
4
|
+
import { ecommerceStorefrontSearchSuggestQuerySchema } from "../../../../data/validators.js";
|
|
5
|
+
import { enforceStorefrontRateLimit } from "../../../../lib/storefrontRateLimit.js";
|
|
6
|
+
import { parseStorefrontSearchSuggestQuery } from "../../../../lib/storefrontQuery.js";
|
|
7
|
+
import { cachedSuggestStorefrontSearch } from "../../../../lib/storefrontSearchCache.js";
|
|
8
|
+
import {
|
|
9
|
+
storefrontErrorSchema,
|
|
10
|
+
storefrontInvalidQueryErrorSchema,
|
|
11
|
+
storefrontRouteErrorResponse,
|
|
12
|
+
storefrontSuccessHeaders,
|
|
13
|
+
runInStoreCacheTenant
|
|
14
|
+
} from "../../storefrontRouteSupport.js";
|
|
15
|
+
import { storefrontSearchSuggestResponseSchema } from "./openapiSchemas.js";
|
|
16
|
+
const metadata = {
|
|
17
|
+
path: "/ecommerce/storefront/search/suggest",
|
|
18
|
+
GET: {
|
|
19
|
+
requireAuth: false
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const ANONYMOUS_CACHE_CONTROL = "public, max-age=30, stale-while-revalidate=30";
|
|
23
|
+
async function GET(req) {
|
|
24
|
+
try {
|
|
25
|
+
const query = parseStorefrontSearchSuggestQuery(new URL(req.url).searchParams);
|
|
26
|
+
const container = await createRequestContainer();
|
|
27
|
+
const service = container.resolve("storeContextService");
|
|
28
|
+
const context = await service.resolve(req, { pathname: query.path ?? "/" });
|
|
29
|
+
return await runInStoreCacheTenant(context, async () => {
|
|
30
|
+
const rateLimited = await enforceStorefrontRateLimit(container, req, context, "searchSuggest");
|
|
31
|
+
if (rateLimited) return rateLimited;
|
|
32
|
+
const body = await cachedSuggestStorefrontSearch(container, context, query);
|
|
33
|
+
return NextResponse.json(body, { headers: storefrontSuccessHeaders(context, ANONYMOUS_CACHE_CONTROL) });
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return storefrontRouteErrorResponse(error, {
|
|
37
|
+
message: "Storefront search suggest failed",
|
|
38
|
+
code: "ecommerce.storefront_search_suggest_failed"
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
var route_default = GET;
|
|
43
|
+
const storefrontSearchTag = "Ecommerce";
|
|
44
|
+
const storefrontSearchSuggestGetDoc = {
|
|
45
|
+
summary: "Typeahead suggestions for the current buyer",
|
|
46
|
+
description: "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.",
|
|
47
|
+
tags: [storefrontSearchTag],
|
|
48
|
+
query: ecommerceStorefrontSearchSuggestQuerySchema,
|
|
49
|
+
responses: [
|
|
50
|
+
{ status: 200, description: "Products and categories matching the query.", schema: storefrontSearchSuggestResponseSchema }
|
|
51
|
+
],
|
|
52
|
+
errors: [
|
|
53
|
+
{ status: 400, description: "Missing `q`, or unknown, repeated or malformed query parameters", schema: storefrontInvalidQueryErrorSchema },
|
|
54
|
+
{ status: 401, description: "Portal session invalid or issued for another tenant or organization", schema: storefrontErrorSchema },
|
|
55
|
+
{ status: 403, description: "Draft store (only when OM_ECOMMERCE_DEV_STORE_SLUG=true)", schema: storefrontErrorSchema },
|
|
56
|
+
{ status: 404, description: "No store serves this host or path", schema: storefrontErrorSchema },
|
|
57
|
+
{ status: 410, description: "Store archived", schema: storefrontErrorSchema },
|
|
58
|
+
{ status: 429, description: "Too many requests", schema: rateLimitErrorSchema },
|
|
59
|
+
{ status: 503, description: "Store misconfigured (no default channel binding)", schema: storefrontErrorSchema }
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
const openApi = {
|
|
63
|
+
tag: storefrontSearchTag,
|
|
64
|
+
summary: "Public storefront search suggestions",
|
|
65
|
+
methods: {
|
|
66
|
+
GET: storefrontSearchSuggestGetDoc
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export {
|
|
70
|
+
GET,
|
|
71
|
+
route_default as default,
|
|
72
|
+
metadata,
|
|
73
|
+
openApi
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/ecommerce/api/storefront/search/suggest/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { ecommerceStorefrontSearchSuggestQuerySchema } from '../../../../data/validators'\nimport { enforceStorefrontRateLimit } from '../../../../lib/storefrontRateLimit'\nimport type { StoreContextService } from '../../../../lib/storeContextService'\nimport { parseStorefrontSearchSuggestQuery } from '../../../../lib/storefrontQuery'\nimport { cachedSuggestStorefrontSearch } from '../../../../lib/storefrontSearchCache'\nimport {\n storefrontErrorSchema,\n storefrontInvalidQueryErrorSchema,\n storefrontRouteErrorResponse,\n storefrontSuccessHeaders,\n runInStoreCacheTenant,\n} from '../../storefrontRouteSupport'\nimport { storefrontSearchSuggestResponseSchema } from './openapiSchemas'\n\nexport const metadata = {\n path: '/ecommerce/storefront/search/suggest',\n GET: {\n requireAuth: false,\n },\n}\n\nconst ANONYMOUS_CACHE_CONTROL = 'public, max-age=30, stale-while-revalidate=30'\n\nexport async function GET(req: Request) {\n try {\n const query = parseStorefrontSearchSuggestQuery(new URL(req.url).searchParams)\n const container = await createRequestContainer()\n const service = container.resolve('storeContextService') as StoreContextService\n const context = await service.resolve(req, { pathname: query.path ?? '/' })\n return await runInStoreCacheTenant(context, async () => {\n const rateLimited = await enforceStorefrontRateLimit(container, req, context, 'searchSuggest')\n if (rateLimited) return rateLimited\n const body = await cachedSuggestStorefrontSearch(container, context, query)\n return NextResponse.json(body, { headers: storefrontSuccessHeaders(context, ANONYMOUS_CACHE_CONTROL) })\n })\n } catch (error) {\n return storefrontRouteErrorResponse(error, {\n message: 'Storefront search suggest failed',\n code: 'ecommerce.storefront_search_suggest_failed',\n })\n }\n}\n\nexport default GET\n\nconst storefrontSearchTag = 'Ecommerce'\n\nconst storefrontSearchSuggestGetDoc: OpenApiMethodDoc = {\n summary: 'Typeahead suggestions for the current buyer',\n description:\n '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.',\n tags: [storefrontSearchTag],\n query: ecommerceStorefrontSearchSuggestQuerySchema,\n responses: [\n { status: 200, description: 'Products and categories matching the query.', schema: storefrontSearchSuggestResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing `q`, or unknown, repeated or malformed query parameters', schema: storefrontInvalidQueryErrorSchema },\n { status: 401, description: 'Portal session invalid or issued for another tenant or organization', schema: storefrontErrorSchema },\n { status: 403, description: 'Draft store (only when OM_ECOMMERCE_DEV_STORE_SLUG=true)', schema: storefrontErrorSchema },\n { status: 404, description: 'No store serves this host or path', schema: storefrontErrorSchema },\n { status: 410, description: 'Store archived', schema: storefrontErrorSchema },\n { status: 429, description: 'Too many requests', schema: rateLimitErrorSchema },\n { status: 503, description: 'Store misconfigured (no default channel binding)', schema: storefrontErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: storefrontSearchTag,\n summary: 'Public storefront search suggestions',\n methods: {\n GET: storefrontSearchSuggestGetDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AAEvC,SAAS,4BAA4B;AACrC,SAAS,mDAAmD;AAC5D,SAAS,kCAAkC;AAE3C,SAAS,yCAAyC;AAClD,SAAS,qCAAqC;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,6CAA6C;AAE/C,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,KAAK;AAAA,IACH,aAAa;AAAA,EACf;AACF;AAEA,MAAM,0BAA0B;AAEhC,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,QAAQ,kCAAkC,IAAI,IAAI,IAAI,GAAG,EAAE,YAAY;AAC7E,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,UAAU,UAAU,QAAQ,qBAAqB;AACvD,UAAM,UAAU,MAAM,QAAQ,QAAQ,KAAK,EAAE,UAAU,MAAM,QAAQ,IAAI,CAAC;AAC1E,WAAO,MAAM,sBAAsB,SAAS,YAAY;AACtD,YAAM,cAAc,MAAM,2BAA2B,WAAW,KAAK,SAAS,eAAe;AAC7F,UAAI,YAAa,QAAO;AACxB,YAAM,OAAO,MAAM,8BAA8B,WAAW,SAAS,KAAK;AAC1E,aAAO,aAAa,KAAK,MAAM,EAAE,SAAS,yBAAyB,SAAS,uBAAuB,EAAE,CAAC;AAAA,IACxG,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,6BAA6B,OAAO;AAAA,MACzC,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAEA,IAAO,gBAAQ;AAEf,MAAM,sBAAsB;AAE5B,MAAM,gCAAkD;AAAA,EACtD,SAAS;AAAA,EACT,aACE;AAAA,EACF,MAAM,CAAC,mBAAmB;AAAA,EAC1B,OAAO;AAAA,EACP,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,+CAA+C,QAAQ,sCAAsC;AAAA,EAC3H;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,mEAAmE,QAAQ,kCAAkC;AAAA,IACzI,EAAE,QAAQ,KAAK,aAAa,uEAAuE,QAAQ,sBAAsB;AAAA,IACjI,EAAE,QAAQ,KAAK,aAAa,4DAA4D,QAAQ,sBAAsB;AAAA,IACtH,EAAE,QAAQ,KAAK,aAAa,qCAAqC,QAAQ,sBAAsB;AAAA,IAC/F,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,sBAAsB;AAAA,IAC5E,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,qBAAqB;AAAA,IAC9E,EAAE,QAAQ,KAAK,aAAa,oDAAoD,QAAQ,sBAAsB;AAAA,EAChH;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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.js";
|
|
7
|
+
import { isStorefrontQueryError } from "../../lib/storefrontQuery.js";
|
|
8
|
+
const STOREFRONT_VARY_HEADER = "Cookie, Authorization, X-Locale, Accept-Language";
|
|
9
|
+
const STOREFRONT_PRIVATE_CACHE_CONTROL = "private, no-store";
|
|
10
|
+
const STOREFRONT_ERROR_CACHE_CONTROL = "no-store";
|
|
11
|
+
const storefrontErrorSchema = z.object({ error: z.string() });
|
|
12
|
+
const storefrontInvalidQueryErrorSchema = z.object({
|
|
13
|
+
error: z.literal("invalid_query"),
|
|
14
|
+
fields: z.record(z.string(), z.string())
|
|
15
|
+
});
|
|
16
|
+
const logger = createLogger("ecommerce").child({ component: "storefront-route" });
|
|
17
|
+
function storefrontErrorResponse(status, body) {
|
|
18
|
+
return NextResponse.json(body, {
|
|
19
|
+
status,
|
|
20
|
+
headers: { "Cache-Control": STOREFRONT_ERROR_CACHE_CONTROL, Vary: STOREFRONT_VARY_HEADER }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function storefrontInvalidQueryResponse(error) {
|
|
24
|
+
const fields = {};
|
|
25
|
+
for (const issue of error.issues) {
|
|
26
|
+
if (!(issue.parameter in fields)) fields[issue.parameter] = issue.message;
|
|
27
|
+
}
|
|
28
|
+
return storefrontErrorResponse(400, { error: "invalid_query", fields });
|
|
29
|
+
}
|
|
30
|
+
function storefrontSuccessHeaders(context, anonymousCacheControl) {
|
|
31
|
+
return {
|
|
32
|
+
"Cache-Control": context.buyer.isAuthenticated ? STOREFRONT_PRIVATE_CACHE_CONTROL : anonymousCacheControl,
|
|
33
|
+
Vary: STOREFRONT_VARY_HEADER
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function storefrontRouteErrorResponse(error, failure) {
|
|
37
|
+
if (isStorefrontQueryError(error)) return storefrontInvalidQueryResponse(error);
|
|
38
|
+
if (isStorefrontResolutionError(error)) return storefrontErrorResponse(error.status, { error: error.code });
|
|
39
|
+
logger.error(failure.message, { err: error });
|
|
40
|
+
getTelemetryRuntime()?.reportError(error, { module: "ecommerce", code: failure.code });
|
|
41
|
+
return storefrontErrorResponse(500, { error: "internal_error" });
|
|
42
|
+
}
|
|
43
|
+
function runInStoreCacheTenant(context, fn) {
|
|
44
|
+
return runWithCacheTenant(context.tenantId, fn);
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
STOREFRONT_ERROR_CACHE_CONTROL,
|
|
48
|
+
STOREFRONT_PRIVATE_CACHE_CONTROL,
|
|
49
|
+
STOREFRONT_VARY_HEADER,
|
|
50
|
+
runInStoreCacheTenant,
|
|
51
|
+
storefrontErrorResponse,
|
|
52
|
+
storefrontErrorSchema,
|
|
53
|
+
storefrontInvalidQueryErrorSchema,
|
|
54
|
+
storefrontInvalidQueryResponse,
|
|
55
|
+
storefrontRouteErrorResponse,
|
|
56
|
+
storefrontSuccessHeaders
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=storefrontRouteSupport.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { isStorefrontResolutionError } from '../../lib/storeContext'\nimport { isStorefrontQueryError, type StorefrontQueryError } from '../../lib/storefrontQuery'\nimport type { StoreContext } from '../../lib/types'\n\nexport const STOREFRONT_VARY_HEADER = 'Cookie, Authorization, X-Locale, Accept-Language'\nexport const STOREFRONT_PRIVATE_CACHE_CONTROL = 'private, no-store'\nexport const STOREFRONT_ERROR_CACHE_CONTROL = 'no-store'\n\nexport const storefrontErrorSchema = z.object({ error: z.string() })\n\nexport const storefrontInvalidQueryErrorSchema = z.object({\n error: z.literal('invalid_query'),\n fields: z.record(z.string(), z.string()),\n})\n\nconst logger = createLogger('ecommerce').child({ component: 'storefront-route' })\n\nexport function storefrontErrorResponse(status: number, body: Record<string, unknown>): NextResponse {\n return NextResponse.json(body, {\n status,\n headers: { 'Cache-Control': STOREFRONT_ERROR_CACHE_CONTROL, Vary: STOREFRONT_VARY_HEADER },\n })\n}\n\nexport function storefrontInvalidQueryResponse(error: StorefrontQueryError): NextResponse {\n const fields: Record<string, string> = {}\n for (const issue of error.issues) {\n if (!(issue.parameter in fields)) fields[issue.parameter] = issue.message\n }\n return storefrontErrorResponse(400, { error: 'invalid_query', fields })\n}\n\n/**\n * Authenticated responses are never stored by a browser or CDN (`private, no-store`); anonymous\n * ones may be, for the given policy. The server-side cache applies to both, keyed on the digest.\n */\nexport function storefrontSuccessHeaders(context: StoreContext, anonymousCacheControl: string): Record<string, string> {\n return {\n 'Cache-Control': context.buyer.isAuthenticated ? STOREFRONT_PRIVATE_CACHE_CONTROL : anonymousCacheControl,\n Vary: STOREFRONT_VARY_HEADER,\n }\n}\n\nexport type StorefrontRouteFailure = { message: string; code: string }\n\n/** Query grammar \u2192 400, store resolution \u2192 its status with `{ error: code }`, anything else \u2192 reported 500. */\nexport function storefrontRouteErrorResponse(error: unknown, failure: StorefrontRouteFailure): NextResponse {\n if (isStorefrontQueryError(error)) return storefrontInvalidQueryResponse(error)\n if (isStorefrontResolutionError(error)) return storefrontErrorResponse(error.status, { error: error.code })\n logger.error(failure.message, { err: error })\n getTelemetryRuntime()?.reportError(error, { module: 'ecommerce', code: failure.code })\n return storefrontErrorResponse(500, { error: 'internal_error' })\n}\n\n/**\n * Runs the rest of a storefront request in the resolved store's cache tenant. Admin writes invalidate\n * cached values (module config, Omnibus results, CRUD lists) in their tenant's namespace; an\n * unauthenticated storefront request would otherwise read the global namespace and keep serving a\n * copy those invalidations never reach.\n */\nexport function runInStoreCacheTenant<T>(context: Pick<StoreContext, 'tenantId'>, fn: () => Promise<T>): Promise<T> {\n return runWithCacheTenant(context.tenantId, fn)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,mCAAmC;AAC5C,SAAS,8BAAyD;AAG3D,MAAM,yBAAyB;AAC/B,MAAM,mCAAmC;AACzC,MAAM,iCAAiC;AAEvC,MAAM,wBAAwB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAE5D,MAAM,oCAAoC,EAAE,OAAO;AAAA,EACxD,OAAO,EAAE,QAAQ,eAAe;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC;AACzC,CAAC;AAED,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,mBAAmB,CAAC;AAEzE,SAAS,wBAAwB,QAAgB,MAA6C;AACnG,SAAO,aAAa,KAAK,MAAM;AAAA,IAC7B;AAAA,IACA,SAAS,EAAE,iBAAiB,gCAAgC,MAAM,uBAAuB;AAAA,EAC3F,CAAC;AACH;AAEO,SAAS,+BAA+B,OAA2C;AACxF,QAAM,SAAiC,CAAC;AACxC,aAAW,SAAS,MAAM,QAAQ;AAChC,QAAI,EAAE,MAAM,aAAa,QAAS,QAAO,MAAM,SAAS,IAAI,MAAM;AAAA,EACpE;AACA,SAAO,wBAAwB,KAAK,EAAE,OAAO,iBAAiB,OAAO,CAAC;AACxE;AAMO,SAAS,yBAAyB,SAAuB,uBAAuD;AACrH,SAAO;AAAA,IACL,iBAAiB,QAAQ,MAAM,kBAAkB,mCAAmC;AAAA,IACpF,MAAM;AAAA,EACR;AACF;AAKO,SAAS,6BAA6B,OAAgB,SAA+C;AAC1G,MAAI,uBAAuB,KAAK,EAAG,QAAO,+BAA+B,KAAK;AAC9E,MAAI,4BAA4B,KAAK,EAAG,QAAO,wBAAwB,MAAM,QAAQ,EAAE,OAAO,MAAM,KAAK,CAAC;AAC1G,SAAO,MAAM,QAAQ,SAAS,EAAE,KAAK,MAAM,CAAC;AAC5C,sBAAoB,GAAG,YAAY,OAAO,EAAE,QAAQ,aAAa,MAAM,QAAQ,KAAK,CAAC;AACrF,SAAO,wBAAwB,KAAK,EAAE,OAAO,iBAAiB,CAAC;AACjE;AAQO,SAAS,sBAAyB,SAAyC,IAAkC;AAClH,SAAO,mBAAmB,QAAQ,UAAU,EAAE;AAChD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
4
|
+
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
5
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
|
+
import {
|
|
7
|
+
bridgeLegacyGuard,
|
|
8
|
+
runMutationGuards
|
|
9
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard-registry";
|
|
10
|
+
import { getAllMutationGuardInstances } from "@open-mercato/shared/lib/crud/mutation-guard-store";
|
|
11
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
12
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
13
|
+
import { ecommerceStoreBrandingSchema } from "../../../../data/validators.js";
|
|
14
|
+
import {
|
|
15
|
+
BRANDING_MANAGE_FEATURE,
|
|
16
|
+
resolveBrandingRouteContext,
|
|
17
|
+
resolveGrantedFeatures,
|
|
18
|
+
storeIdParamSchema
|
|
19
|
+
} from "../../../../lib/storeBrandingRoute.js";
|
|
20
|
+
import {
|
|
21
|
+
parseBrandingInput,
|
|
22
|
+
STORE_BRANDING_RESOURCE_KIND,
|
|
23
|
+
STORE_BRANDING_UPDATE_COMMAND_ID
|
|
24
|
+
} from "../../../../lib/storeBranding.js";
|
|
25
|
+
import { ecommerceInternalErrorBody } from "../../../../lib/crudSupport.js";
|
|
26
|
+
const metadata = {
|
|
27
|
+
PUT: { requireAuth: true, requireFeatures: [BRANDING_MANAGE_FEATURE] }
|
|
28
|
+
};
|
|
29
|
+
const logger = createLogger("ecommerce").child({ component: "store-branding-route" });
|
|
30
|
+
async function readStoreId(ctx) {
|
|
31
|
+
const params = await ctx.params;
|
|
32
|
+
const parsed = storeIdParamSchema.safeParse(params?.id);
|
|
33
|
+
return parsed.success ? parsed.data : null;
|
|
34
|
+
}
|
|
35
|
+
async function readJsonBody(req) {
|
|
36
|
+
const text = await req.text();
|
|
37
|
+
if (text.trim().length === 0) return { ok: true, body: {} };
|
|
38
|
+
try {
|
|
39
|
+
return { ok: true, body: JSON.parse(text) };
|
|
40
|
+
} catch {
|
|
41
|
+
return { ok: false };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function invalidBranding(fieldErrors) {
|
|
45
|
+
const [message] = Object.values(fieldErrors);
|
|
46
|
+
return NextResponse.json({ error: message, fieldErrors }, { status: 400 });
|
|
47
|
+
}
|
|
48
|
+
async function PUT(req, ctx) {
|
|
49
|
+
try {
|
|
50
|
+
const context = await resolveBrandingRouteContext(req);
|
|
51
|
+
const { translate } = await resolveTranslations();
|
|
52
|
+
const storeId = await readStoreId(ctx);
|
|
53
|
+
if (!storeId) return NextResponse.json({ error: translate("ecommerce.errors.storeNotFound", "The selected store does not exist in this organization.") }, { status: 404 });
|
|
54
|
+
const raw = await readJsonBody(req);
|
|
55
|
+
if (!raw.ok) {
|
|
56
|
+
return NextResponse.json(
|
|
57
|
+
{ error: translate("ecommerce.errors.invalidJsonBody", "The request body is not valid JSON.") },
|
|
58
|
+
{ status: 400 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const parsed = parseBrandingInput(raw.body, translate);
|
|
62
|
+
if (!parsed.ok) return invalidBranding(parsed.fieldErrors);
|
|
63
|
+
let branding = parsed.branding;
|
|
64
|
+
const legacyGuard = bridgeLegacyGuard(context.container);
|
|
65
|
+
const guards = [...getAllMutationGuardInstances()];
|
|
66
|
+
if (legacyGuard) guards.push(legacyGuard);
|
|
67
|
+
const guardInput = {
|
|
68
|
+
tenantId: context.tenantId,
|
|
69
|
+
organizationId: context.organizationId,
|
|
70
|
+
userId: context.auth.sub ?? "",
|
|
71
|
+
resourceKind: STORE_BRANDING_RESOURCE_KIND,
|
|
72
|
+
resourceId: storeId,
|
|
73
|
+
operation: "update",
|
|
74
|
+
requestMethod: req.method,
|
|
75
|
+
requestHeaders: req.headers
|
|
76
|
+
};
|
|
77
|
+
const guardResult = await runMutationGuards(
|
|
78
|
+
guards,
|
|
79
|
+
{ ...guardInput, mutationPayload: { ...branding } },
|
|
80
|
+
{ userFeatures: await resolveGrantedFeatures(context) }
|
|
81
|
+
);
|
|
82
|
+
if (!guardResult.ok) {
|
|
83
|
+
const blocked = guardResult.errorBody ?? {
|
|
84
|
+
error: translate("ecommerce.errors.operationBlocked", "This change was blocked. Review the store and try again.")
|
|
85
|
+
};
|
|
86
|
+
return NextResponse.json(blocked, {
|
|
87
|
+
status: guardResult.errorStatus ?? 422
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (guardResult.modifiedPayload) {
|
|
91
|
+
const merged = parseBrandingInput({ ...branding, ...guardResult.modifiedPayload }, translate);
|
|
92
|
+
if (!merged.ok) return invalidBranding(merged.fieldErrors);
|
|
93
|
+
branding = merged.branding;
|
|
94
|
+
}
|
|
95
|
+
const commandBus = context.container.resolve("commandBus");
|
|
96
|
+
const { result, logEntry } = await commandBus.execute(
|
|
97
|
+
STORE_BRANDING_UPDATE_COMMAND_ID,
|
|
98
|
+
{
|
|
99
|
+
input: { id: storeId, tenantId: context.tenantId, organizationId: context.organizationId, branding },
|
|
100
|
+
ctx: context.commandCtx
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
for (const callback of guardResult.afterSuccessCallbacks) {
|
|
104
|
+
if (!callback.guard.afterSuccess) continue;
|
|
105
|
+
try {
|
|
106
|
+
await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null });
|
|
107
|
+
} catch (callbackError) {
|
|
108
|
+
logger.error("Mutation guard afterSuccess callback failed", {
|
|
109
|
+
guardId: callback.guard.id,
|
|
110
|
+
err: callbackError
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const response = NextResponse.json({ id: result.id, updatedAt: result.updatedAt, branding: result.branding });
|
|
115
|
+
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
116
|
+
response.headers.set(
|
|
117
|
+
"x-om-operation",
|
|
118
|
+
serializeOperationMetadata({
|
|
119
|
+
id: logEntry.id,
|
|
120
|
+
undoToken: logEntry.undoToken,
|
|
121
|
+
commandId: logEntry.commandId,
|
|
122
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
123
|
+
resourceKind: logEntry.resourceKind ?? STORE_BRANDING_RESOURCE_KIND,
|
|
124
|
+
resourceId: logEntry.resourceId ?? result.id,
|
|
125
|
+
executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : typeof logEntry.createdAt === "string" ? logEntry.createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return response;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
132
|
+
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
133
|
+
if (interceptorRejection) {
|
|
134
|
+
return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
135
|
+
}
|
|
136
|
+
logger.error("ecommerce.stores.branding.PUT failed", { err });
|
|
137
|
+
return NextResponse.json(await ecommerceInternalErrorBody(), { status: 500 });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const brandingResponseSchema = z.object({
|
|
141
|
+
id: z.string().uuid(),
|
|
142
|
+
updatedAt: z.string().describe("The store updated_at after the write; send it as the expected version of the next save."),
|
|
143
|
+
branding: ecommerceStoreBrandingSchema
|
|
144
|
+
});
|
|
145
|
+
const errorSchema = z.object({ error: z.string() }).passthrough();
|
|
146
|
+
const fieldErrorsSchema = z.object({
|
|
147
|
+
error: z.string(),
|
|
148
|
+
fieldErrors: z.record(z.string(), z.string())
|
|
149
|
+
});
|
|
150
|
+
const conflictSchema = z.object({
|
|
151
|
+
error: z.string(),
|
|
152
|
+
code: z.string(),
|
|
153
|
+
currentUpdatedAt: z.string(),
|
|
154
|
+
expectedUpdatedAt: z.string()
|
|
155
|
+
});
|
|
156
|
+
const openApi = {
|
|
157
|
+
tag: "Ecommerce",
|
|
158
|
+
summary: "Store branding",
|
|
159
|
+
pathParams: z.object({ id: z.string().uuid() }),
|
|
160
|
+
methods: {
|
|
161
|
+
PUT: {
|
|
162
|
+
summary: "Replace the branding of a store",
|
|
163
|
+
description: "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.",
|
|
164
|
+
tags: ["Ecommerce"],
|
|
165
|
+
requestBody: { schema: ecommerceStoreBrandingSchema },
|
|
166
|
+
responses: [{ status: 200, description: "Branding saved", schema: brandingResponseSchema }],
|
|
167
|
+
errors: [
|
|
168
|
+
{ status: 400, description: "A branding value is invalid; fieldErrors maps each rejected key to a message", schema: fieldErrorsSchema },
|
|
169
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
170
|
+
{ status: 403, description: "Missing ecommerce.branding.manage", schema: errorSchema },
|
|
171
|
+
{ status: 404, description: "The store does not exist in the selected organization", schema: errorSchema },
|
|
172
|
+
{ status: 409, description: "The store changed since it was loaded (optimistic lock)", schema: conflictSchema },
|
|
173
|
+
{ status: 422, description: "Blocked by a mutation guard", schema: errorSchema }
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
export {
|
|
179
|
+
PUT,
|
|
180
|
+
metadata,
|
|
181
|
+
openApi
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/ecommerce/api/stores/%5Bid%5D/branding/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { StoreBrandingUpdateInput, StoreBrandingUpdateResult } from '../../../../commands/stores'\nimport { ecommerceStoreBrandingSchema, type EcommerceStoreBranding } from '../../../../data/validators'\nimport {\n BRANDING_MANAGE_FEATURE,\n resolveBrandingRouteContext,\n resolveGrantedFeatures,\n storeIdParamSchema,\n} from '../../../../lib/storeBrandingRoute'\nimport {\n parseBrandingInput,\n STORE_BRANDING_RESOURCE_KIND,\n STORE_BRANDING_UPDATE_COMMAND_ID,\n} from '../../../../lib/storeBranding'\nimport { ecommerceInternalErrorBody } from '../../../../lib/crudSupport'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: [BRANDING_MANAGE_FEATURE] },\n}\n\nconst logger = createLogger('ecommerce').child({ component: 'store-branding-route' })\n\ntype RouteParams = { params?: { id?: string } | Promise<{ id?: string }> }\n\nasync function readStoreId(ctx: RouteParams): Promise<string | null> {\n const params = await ctx.params\n const parsed = storeIdParamSchema.safeParse(params?.id)\n return parsed.success ? parsed.data : null\n}\n\nasync function readJsonBody(req: Request): Promise<{ ok: true; body: unknown } | { ok: false }> {\n const text = await req.text()\n if (text.trim().length === 0) return { ok: true, body: {} }\n try {\n return { ok: true, body: JSON.parse(text) }\n } catch {\n return { ok: false }\n }\n}\n\nfunction invalidBranding(fieldErrors: Record<string, string>) {\n const [message] = Object.values(fieldErrors)\n return NextResponse.json({ error: message, fieldErrors }, { status: 400 })\n}\n\nexport async function PUT(req: Request, ctx: RouteParams) {\n try {\n const context = await resolveBrandingRouteContext(req)\n const { translate } = await resolveTranslations()\n const storeId = await readStoreId(ctx)\n if (!storeId) return NextResponse.json({ error: translate('ecommerce.errors.storeNotFound', 'The selected store does not exist in this organization.') }, { status: 404 })\n\n const raw = await readJsonBody(req)\n if (!raw.ok) {\n return NextResponse.json(\n { error: translate('ecommerce.errors.invalidJsonBody', 'The request body is not valid JSON.') },\n { status: 400 },\n )\n }\n const parsed = parseBrandingInput(raw.body, translate)\n if (!parsed.ok) return invalidBranding(parsed.fieldErrors)\n let branding: EcommerceStoreBranding = parsed.branding\n\n const legacyGuard = bridgeLegacyGuard(context.container)\n const guards: MutationGuard[] = [...getAllMutationGuardInstances()]\n if (legacyGuard) guards.push(legacyGuard)\n const guardInput = {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: context.auth.sub ?? '',\n resourceKind: STORE_BRANDING_RESOURCE_KIND,\n resourceId: storeId,\n operation: 'update' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n const guardResult = await runMutationGuards(\n guards,\n { ...guardInput, mutationPayload: { ...branding } },\n { userFeatures: await resolveGrantedFeatures(context) },\n )\n if (!guardResult.ok) {\n const blocked = guardResult.errorBody ?? {\n error: translate('ecommerce.errors.operationBlocked', 'This change was blocked. Review the store and try again.'),\n }\n return NextResponse.json(blocked, {\n status: guardResult.errorStatus ?? 422,\n })\n }\n if (guardResult.modifiedPayload) {\n const merged = parseBrandingInput({ ...branding, ...guardResult.modifiedPayload }, translate)\n if (!merged.ok) return invalidBranding(merged.fieldErrors)\n branding = merged.branding\n }\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<StoreBrandingUpdateInput, StoreBrandingUpdateResult>(\n STORE_BRANDING_UPDATE_COMMAND_ID,\n {\n input: { id: storeId, tenantId: context.tenantId, organizationId: context.organizationId, branding },\n ctx: context.commandCtx,\n },\n )\n\n for (const callback of guardResult.afterSuccessCallbacks) {\n if (!callback.guard.afterSuccess) continue\n try {\n await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null })\n } catch (callbackError) {\n logger.error('Mutation guard afterSuccess callback failed', {\n guardId: callback.guard.id,\n err: callbackError,\n })\n }\n }\n\n const response = NextResponse.json({ id: result.id, updatedAt: result.updatedAt, branding: result.branding })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? STORE_BRANDING_RESOURCE_KIND,\n resourceId: logEntry.resourceId ?? result.id,\n executedAt:\n logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n logger.error('ecommerce.stores.branding.PUT failed', { err })\n return NextResponse.json(await ecommerceInternalErrorBody(), { status: 500 })\n }\n}\n\nconst brandingResponseSchema = z.object({\n id: z.string().uuid(),\n updatedAt: z.string().describe('The store updated_at after the write; send it as the expected version of the next save.'),\n branding: ecommerceStoreBrandingSchema,\n})\n\nconst errorSchema = z.object({ error: z.string() }).passthrough()\n\nconst fieldErrorsSchema = z.object({\n error: z.string(),\n fieldErrors: z.record(z.string(), z.string()),\n})\n\nconst conflictSchema = z.object({\n error: z.string(),\n code: z.string(),\n currentUpdatedAt: z.string(),\n expectedUpdatedAt: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Ecommerce',\n summary: 'Store branding',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n PUT: {\n summary: 'Replace the branding of a store',\n description:\n '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.',\n tags: ['Ecommerce'],\n requestBody: { schema: ecommerceStoreBrandingSchema },\n responses: [{ status: 200, description: 'Branding saved', schema: brandingResponseSchema }],\n errors: [\n { status: 400, description: 'A branding value is invalid; fieldErrors maps each rejected key to a message', schema: fieldErrorsSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Missing ecommerce.branding.manage', schema: errorSchema },\n { status: 404, description: 'The store does not exist in the selected organization', schema: errorSchema },\n { status: 409, description: 'The store changed since it was loaded (optimistic lock)', schema: conflictSchema },\n { status: 422, description: 'Blocked by a mutation guard', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
6
|
+
import { ecommerceStoreBrandingSchema } from "../../../../data/validators.js";
|
|
7
|
+
import {
|
|
8
|
+
buildBrandingDeclarations,
|
|
9
|
+
renderBrandingRootRule,
|
|
10
|
+
renderBrandingStyleBlock
|
|
11
|
+
} from "../../../../lib/brandingStyles.js";
|
|
12
|
+
import { parseBrandingInput } from "../../../../lib/storeBranding.js";
|
|
13
|
+
import { ecommerceInternalErrorBody } from "../../../../lib/crudSupport.js";
|
|
14
|
+
import {
|
|
15
|
+
BRANDING_MANAGE_FEATURE,
|
|
16
|
+
requireScopedStore,
|
|
17
|
+
resolveBrandingRouteContext,
|
|
18
|
+
storeIdParamSchema
|
|
19
|
+
} from "../../../../lib/storeBrandingRoute.js";
|
|
20
|
+
const metadata = {
|
|
21
|
+
GET: { requireAuth: true, requireFeatures: [BRANDING_MANAGE_FEATURE] }
|
|
22
|
+
};
|
|
23
|
+
const logger = createLogger("ecommerce").child({ component: "store-branding-preview-route" });
|
|
24
|
+
function readQueryValues(url) {
|
|
25
|
+
const values = {};
|
|
26
|
+
for (const [key, value] of new URL(url).searchParams.entries()) values[key] = value;
|
|
27
|
+
return values;
|
|
28
|
+
}
|
|
29
|
+
async function GET(req, ctx) {
|
|
30
|
+
try {
|
|
31
|
+
const context = await resolveBrandingRouteContext(req);
|
|
32
|
+
const { translate } = await resolveTranslations();
|
|
33
|
+
const params = await ctx.params;
|
|
34
|
+
const storeId = storeIdParamSchema.safeParse(params?.id);
|
|
35
|
+
if (!storeId.success) {
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{ error: translate("ecommerce.errors.storeNotFound", "The selected store does not exist in this organization.") },
|
|
38
|
+
{ status: 404 }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
await requireScopedStore(context, storeId.data);
|
|
42
|
+
const parsed = parseBrandingInput(readQueryValues(req.url), translate);
|
|
43
|
+
if (!parsed.ok) {
|
|
44
|
+
const [message] = Object.values(parsed.fieldErrors);
|
|
45
|
+
return NextResponse.json({ error: message, fieldErrors: parsed.fieldErrors }, { status: 400 });
|
|
46
|
+
}
|
|
47
|
+
return NextResponse.json(
|
|
48
|
+
{
|
|
49
|
+
css: renderBrandingRootRule(parsed.branding),
|
|
50
|
+
styleBlock: renderBrandingStyleBlock(parsed.branding),
|
|
51
|
+
declarations: buildBrandingDeclarations(parsed.branding)
|
|
52
|
+
},
|
|
53
|
+
{ headers: { "Cache-Control": "no-store" } }
|
|
54
|
+
);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
57
|
+
logger.error("ecommerce.stores.preview-branding.GET failed", { err });
|
|
58
|
+
return NextResponse.json(await ecommerceInternalErrorBody(), { status: 500 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const previewResponseSchema = z.object({
|
|
62
|
+
css: z.string().describe("The :root rule that the storefront emits for these values."),
|
|
63
|
+
styleBlock: z.string().describe("The same rule wrapped in the style element the storefront server-renders."),
|
|
64
|
+
declarations: z.array(z.object({ property: z.string(), value: z.string() }))
|
|
65
|
+
});
|
|
66
|
+
const errorSchema = z.object({ error: z.string() }).passthrough();
|
|
67
|
+
const openApi = {
|
|
68
|
+
tag: "Ecommerce",
|
|
69
|
+
summary: "Store branding preview",
|
|
70
|
+
pathParams: z.object({ id: z.string().uuid() }),
|
|
71
|
+
methods: {
|
|
72
|
+
GET: {
|
|
73
|
+
summary: "Preview the stylesheet for candidate branding values",
|
|
74
|
+
description: "Validates the candidate branding values passed as query parameters with the same rules as the save route and returns the CSS the storefront would emit. Nothing is persisted. Requires ecommerce.branding.manage.",
|
|
75
|
+
tags: ["Ecommerce"],
|
|
76
|
+
query: ecommerceStoreBrandingSchema,
|
|
77
|
+
responses: [{ status: 200, description: "Rendered branding stylesheet", schema: previewResponseSchema }],
|
|
78
|
+
errors: [
|
|
79
|
+
{ status: 400, description: "A branding value is invalid", schema: errorSchema },
|
|
80
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
81
|
+
{ status: 403, description: "Missing ecommerce.branding.manage", schema: errorSchema },
|
|
82
|
+
{ status: 404, description: "The store does not exist in the selected organization", schema: errorSchema }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
export {
|
|
88
|
+
GET,
|
|
89
|
+
metadata,
|
|
90
|
+
openApi
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=route.js.map
|