From 2c710ad4164af51396847e85c63eec421d2ffd54 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Wed, 18 Feb 2026 13:27:31 +0100 Subject: [PATCH] feat: storefront subscription access guard + module-driven nav + URL rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add StorefrontAccessMiddleware that blocks storefront access for stores without an active subscription, returning a multilingual unavailable page (en/fr/de/lb) for page requests and JSON 403 for API requests. Multi-platform aware: resolves subscription for detected platform with fallback to primary. Also includes yesterday's session work: - Module-driven storefront navigation via FrontendType.STOREFRONT menu declarations - shop/ → storefront/ URL rename across 30+ templates - Subscription context (tier_code) passed to storefront templates Co-Authored-By: Claude Opus 4.6 --- app/modules/cart/definition.py | 27 +- .../cart/templates/cart/storefront/cart.html | 20 +- app/modules/catalog/definition.py | 30 + .../catalog/storefront/category.html | 18 +- .../templates/catalog/storefront/product.html | 22 +- .../catalog/storefront/products.html | 14 +- .../templates/catalog/storefront/search.html | 14 +- .../catalog/storefront/wishlist.html | 22 +- .../checkout/storefront/checkout.html | 28 +- .../templates/cms/store/content-pages.html | 4 +- .../cms/storefront/landing-default.html | 12 +- .../cms/storefront/landing-full.html | 14 +- .../cms/storefront/landing-minimal.html | 8 +- .../cms/storefront/landing-modern.html | 6 +- app/modules/core/utils/page_context.py | 32 + app/modules/customers/definition.py | 37 + .../customers/storefront/addresses.html | 12 +- .../customers/storefront/dashboard.html | 18 +- .../customers/storefront/forgot-password.html | 8 +- .../templates/customers/storefront/login.html | 12 +- .../customers/storefront/profile.html | 20 +- .../customers/storefront/register.html | 8 +- .../customers/storefront/reset-password.html | 12 +- app/modules/loyalty/definition.py | 16 + .../loyalty/storefront/dashboard.html | 6 +- .../loyalty/storefront/enroll-success.html | 4 +- app/modules/messaging/definition.py | 16 + .../messaging/storefront/messages.html | 22 +- app/modules/orders/definition.py | 16 + .../orders/storefront/order-detail.html | 22 +- .../templates/orders/storefront/orders.html | 12 +- app/templates/storefront/base.html | 52 +- app/templates/storefront/errors/400.html | 4 +- app/templates/storefront/errors/401.html | 6 +- app/templates/storefront/errors/403.html | 6 +- app/templates/storefront/errors/404.html | 8 +- app/templates/storefront/errors/422.html | 4 +- app/templates/storefront/errors/429.html | 4 +- app/templates/storefront/errors/500.html | 4 +- app/templates/storefront/errors/502.html | 4 +- app/templates/storefront/errors/base.html | 8 +- app/templates/storefront/errors/generic.html | 4 +- app/templates/storefront/unavailable.html | 89 ++ main.py | 34 +- middleware/storefront_access.py | 189 +++++ .../unit/middleware/test_storefront_access.py | 787 ++++++++++++++++++ 46 files changed, 1484 insertions(+), 231 deletions(-) create mode 100644 app/templates/storefront/unavailable.html create mode 100644 middleware/storefront_access.py create mode 100644 tests/unit/middleware/test_storefront_access.py diff --git a/app/modules/cart/definition.py b/app/modules/cart/definition.py index 13261e89..4c0d668f 100644 --- a/app/modules/cart/definition.py +++ b/app/modules/cart/definition.py @@ -6,7 +6,13 @@ This module provides shopping cart functionality for customer storefronts. It is session-based and does not require customer authentication. """ -from app.modules.base import ModuleDefinition, PermissionDefinition +from app.modules.base import ( + MenuItemDefinition, + MenuSectionDefinition, + ModuleDefinition, + PermissionDefinition, +) +from app.modules.enums import FrontendType # ============================================================================= # Router Lazy Imports @@ -53,7 +59,24 @@ cart_module = ModuleDefinition( ), ], # Cart is storefront-only - no admin/store menus needed - menu_items={}, + menus={ + FrontendType.STOREFRONT: [ + MenuSectionDefinition( + id="actions", + label_key=None, + order=10, + items=[ + MenuItemDefinition( + id="cart", + label_key="storefront.actions.cart", + icon="shopping-cart", + route="storefront/cart", + order=20, + ), + ], + ), + ], + }, ) diff --git a/app/modules/cart/templates/cart/storefront/cart.html b/app/modules/cart/templates/cart/storefront/cart.html index 11779574..b35a3a26 100644 --- a/app/modules/cart/templates/cart/storefront/cart.html +++ b/app/modules/cart/templates/cart/storefront/cart.html @@ -13,7 +13,7 @@ @@ -39,7 +39,7 @@

Add some products to get started!

- + Browse Products @@ -55,8 +55,8 @@ {# Item Image #}
@@ -153,7 +153,7 @@ Proceed to Checkout - + Continue Shopping @@ -218,7 +218,7 @@ document.addEventListener('alpine:init', () => { try { console.log(`[SHOP] Loading cart for session ${this.sessionId}...`); - const response = await fetch(`/api/v1/shop/cart/${this.sessionId}`); + const response = await fetch(`/api/v1/storefront/cart/${this.sessionId}`); if (response.ok) { const data = await response.json(); @@ -245,7 +245,7 @@ document.addEventListener('alpine:init', () => { try { console.log('[SHOP] Updating quantity:', productId, newQuantity); const response = await fetch( - `/api/v1/shop/cart/${this.sessionId}/items/${productId}`, + `/api/v1/storefront/cart/${this.sessionId}/items/${productId}`, { method: 'PUT', headers: { @@ -280,7 +280,7 @@ document.addEventListener('alpine:init', () => { try { console.log('[SHOP] Removing item:', productId); const response = await fetch( - `/api/v1/shop/cart/${this.sessionId}/items/${productId}`, + `/api/v1/storefront/cart/${this.sessionId}/items/${productId}`, { method: 'DELETE' } @@ -307,9 +307,9 @@ document.addEventListener('alpine:init', () => { if (!token) { // Redirect to login with return URL - window.location.href = '{{ base_url }}shop/account/login?return={{ base_url }}shop/checkout'; + window.location.href = '{{ base_url }}storefront/account/login?return={{ base_url }}storefront/checkout'; } else { - window.location.href = '{{ base_url }}shop/checkout'; + window.location.href = '{{ base_url }}storefront/checkout'; } } }; diff --git a/app/modules/catalog/definition.py b/app/modules/catalog/definition.py index b7dc0618..b5431954 100644 --- a/app/modules/catalog/definition.py +++ b/app/modules/catalog/definition.py @@ -125,6 +125,36 @@ catalog_module = ModuleDefinition( ], ), ], + FrontendType.STOREFRONT: [ + MenuSectionDefinition( + id="nav", + label_key=None, + order=10, + items=[ + MenuItemDefinition( + id="products", + label_key="storefront.nav.products", + icon="shopping-bag", + route="storefront/products", + order=10, + ), + ], + ), + MenuSectionDefinition( + id="actions", + label_key=None, + order=10, + items=[ + MenuItemDefinition( + id="search", + label_key="storefront.actions.search", + icon="search", + route="", + order=10, + ), + ], + ), + ], }, # Metrics provider for dashboard statistics metrics_provider=_get_metrics_provider, diff --git a/app/modules/catalog/templates/catalog/storefront/category.html b/app/modules/catalog/templates/catalog/storefront/category.html index 25fd85a6..cad1237f 100644 --- a/app/modules/catalog/templates/catalog/storefront/category.html +++ b/app/modules/catalog/templates/catalog/storefront/category.html @@ -13,7 +13,7 @@ @@ -61,14 +61,14 @@