From 272b62fbd356feb6e09ddf7f526c04f335ad344c Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Mon, 23 Feb 2026 23:56:26 +0100 Subject: [PATCH] docs: update documentation for platform-aware storefront routing Update 8 documentation files to reflect new URL scheme: - Dev: /platforms/{code}/storefront/{store_code}/ - Prod: subdomain.platform.lu/ (root path = storefront) - Rename DEFAULT_PLATFORM_CODE to MAIN_PLATFORM_CODE - Replace hardcoded platform_id=1 with dynamic values - Update route examples, middleware descriptions, code samples Co-Authored-By: Claude Opus 4.6 --- docs/api/storefront-api-reference.md | 8 +- docs/architecture/menu-management.md | 8 +- docs/architecture/metrics-provider-pattern.md | 14 +- docs/architecture/module-system.md | 2 +- docs/architecture/multi-platform-cms.md | 10 +- docs/architecture/url-routing/overview.md | 186 +++++++++--------- ...rm-cms-architecture-implementation-plan.md | 14 +- docs/backend/middleware-reference.md | 2 +- 8 files changed, 126 insertions(+), 118 deletions(-) diff --git a/docs/api/storefront-api-reference.md b/docs/api/storefront-api-reference.md index 3cf2ee7f..3922298d 100644 --- a/docs/api/storefront-api-reference.md +++ b/docs/api/storefront-api-reference.md @@ -24,16 +24,16 @@ The Storefront API provides customer-facing endpoints for browsing products, man All Storefront API endpoints automatically receive store context from the `StoreContextMiddleware`: -1. **Browser makes API call** from storefront page (e.g., `/stores/orion/storefront/products`) -2. **Browser automatically sends Referer header**: `http://localhost:8000/stores/orion/storefront/products` +1. **Browser makes API call** from storefront page (e.g., `/platforms/oms/storefront/ORION/products`) +2. **Browser automatically sends Referer header**: `http://localhost:8000/platforms/oms/storefront/ORION/products` 3. **Middleware extracts store** from Referer path/subdomain/domain -4. **Middleware sets** `request.state.store = ` +4. **Middleware sets** `request.state.store = ` 5. **API endpoint accesses store**: `store = request.state.store` 6. **No store_id needed in URL!** ### Supported Store Detection Methods -- **Path-based**: `/stores/orion/storefront/products` → extracts `orion` +- **Path-based (dev)**: `/platforms/oms/storefront/ORION/products` → extracts `ORION` - **Subdomain**: `orion.platform.com` → extracts `orion` - **Custom domain**: `customshop.com` → looks up store by domain diff --git a/docs/architecture/menu-management.md b/docs/architecture/menu-management.md index 9b27cbfd..162e574e 100644 --- a/docs/architecture/menu-management.md +++ b/docs/architecture/menu-management.md @@ -176,7 +176,7 @@ from app.modules.core.services.menu_discovery_service import menu_discovery_serv sections = menu_discovery_service.get_menu_for_frontend( db=db, frontend_type=FrontendType.ADMIN, - platform_id=1, + platform_id=platform.id, user_id=current_user.id, is_super_admin=current_user.is_super_admin, ) @@ -185,7 +185,7 @@ sections = menu_discovery_service.get_menu_for_frontend( all_items = menu_discovery_service.get_all_menu_items( db=db, frontend_type=FrontendType.ADMIN, - platform_id=1, + platform_id=platform.id, ) ``` @@ -285,7 +285,7 @@ from app.modules.enums import FrontendType # Platform "OMS" hides inventory from admin panel AdminMenuConfig( frontend_type=FrontendType.ADMIN, - platform_id=1, + platform_id=oms_platform.id, # OMS platform menu_item_id="inventory", is_visible=False ) @@ -293,7 +293,7 @@ AdminMenuConfig( # Platform "OMS" hides letzshop from store dashboard AdminMenuConfig( frontend_type=FrontendType.STORE, - platform_id=1, + platform_id=oms_platform.id, # OMS platform menu_item_id="letzshop", is_visible=False ) diff --git a/docs/architecture/metrics-provider-pattern.md b/docs/architecture/metrics-provider-pattern.md index 017dcf7c..97a642f8 100644 --- a/docs/architecture/metrics-provider-pattern.md +++ b/docs/architecture/metrics-provider-pattern.md @@ -361,12 +361,12 @@ Platform context flows through middleware and JWT tokens: ┌─────────────────────────────────────────────────────────────────────┐ │ Route Handler (Dashboard) │ │ │ -│ # Get platform_id from middleware or JWT token │ -│ platform = getattr(request.state, "platform", None) │ -│ platform_id = platform.id if platform else 1 │ +│ # Get platform from require_platform dependency │ +│ platform = Depends(require_platform) # Raises 400 if missing │ +│ platform_id = platform.id │ │ │ │ # Or from JWT for API routes │ -│ platform_id = current_user.token_platform_id or 1 │ +│ platform_id = current_user.token_platform_id │ └─────────────────────────────────────────────────────────────────────┘ ``` @@ -392,13 +392,11 @@ Platform context flows through middleware and JWT tokens: def get_store_dashboard_stats( request: Request, current_user: UserContext = Depends(get_current_store_api), + platform=Depends(require_platform), db: Session = Depends(get_db), ): store_id = current_user.token_store_id - - # Get platform from middleware - platform = getattr(request.state, "platform", None) - platform_id = platform.id if platform else 1 + platform_id = platform.id # Get aggregated metrics from all enabled modules metrics = stats_aggregator.get_store_dashboard_stats( diff --git a/docs/architecture/module-system.md b/docs/architecture/module-system.md index 0e25aad3..c2f5e373 100644 --- a/docs/architecture/module-system.md +++ b/docs/architecture/module-system.md @@ -572,7 +572,7 @@ def _get_platform_context(request: Any, db: Any, platform: Any) -> dict[str, Any """Provide CMS context for platform/marketing pages.""" from app.modules.cms.services import content_page_service - platform_id = platform.id if platform else 1 + platform_id = platform.id if platform else None header_pages = content_page_service.list_platform_pages( db, platform_id=platform_id, header_only=True, include_unpublished=False diff --git a/docs/architecture/multi-platform-cms.md b/docs/architecture/multi-platform-cms.md index 0a26ed63..eadbace8 100644 --- a/docs/architecture/multi-platform-cms.md +++ b/docs/architecture/multi-platform-cms.md @@ -38,13 +38,13 @@ Content pages follow a three-tier inheritance model: When a customer visits a store page (e.g., `/stores/shopname/about`): ``` -Customer visits: /stores/shopname/about +Customer visits: /platforms/oms/storefront/shopname/about │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 1: Check Store Override │ │ SELECT * FROM content_pages │ -│ WHERE platform_id=1 AND store_id=123 AND slug='about' │ +│ WHERE platform_id=:platform_id AND store_id=123 AND slug='about' │ │ Found? → Return store's custom "About" page │ └─────────────────────────────────────────────────────────────────────┘ │ Not found @@ -52,7 +52,7 @@ Customer visits: /stores/shopname/about ┌─────────────────────────────────────────────────────────────────────┐ │ Step 2: Check Store Default │ │ SELECT * FROM content_pages │ -│ WHERE platform_id=1 AND store_id IS NULL │ +│ WHERE platform_id=:platform_id AND store_id IS NULL │ │ AND is_platform_page=FALSE AND slug='about' │ │ Found? → Return platform's default "About" template │ └─────────────────────────────────────────────────────────────────────┘ @@ -152,7 +152,7 @@ Request: GET /platforms/oms/stores/shopname/about │ Route Handler (shop_pages.py) │ │ - Gets platform_id from request.state.platform │ │ - Calls content_page_service.get_page_for_store( │ -│ platform_id=1, store_id=123, slug='about' │ +│ platform_id=platform.id, store_id=123, slug='about' │ │ ) │ │ - Service handles three-tier resolution │ └─────────────────────────────────────────────────────────────────────┘ @@ -169,7 +169,7 @@ Request: GET /about ┌─────────────────────────────────────────────────────────────────────┐ │ PlatformContextMiddleware │ │ - No /platforms/ prefix detected │ -│ - Uses DEFAULT_PLATFORM_CODE = 'main' │ +│ - Uses MAIN_PLATFORM_CODE = 'main' │ │ - Sets request.state.platform = Platform(code='main') │ │ - Path unchanged: /about │ └─────────────────────────────────────────────────────────────────────┘ diff --git a/docs/architecture/url-routing/overview.md b/docs/architecture/url-routing/overview.md index 398046e9..8f887f3b 100644 --- a/docs/architecture/url-routing/overview.md +++ b/docs/architecture/url-routing/overview.md @@ -6,33 +6,38 @@ There are three ways depending on the deployment mode: -**⚠️ Important:** This guide describes **customer-facing storefront routes**. For store dashboard/management routes, see [Store Frontend Architecture](../../frontend/store/architecture.md). The storefront uses `/stores/{code}/storefront/*` (plural) in path-based mode, while the store dashboard uses `/store/{code}/*` (singular). +**⚠️ Important:** This guide describes **customer-facing storefront routes**. For store dashboard/management routes, see [Store Frontend Architecture](../../frontend/store/architecture.md). The storefront uses `/platforms/{platform_code}/storefront/{store_code}/*` in dev path-based mode, while the store dashboard uses `/platforms/{platform_code}/store/{store_code}/*`. In production, the domain IS the storefront (root path `/`), and staff access is at `/store/`. ### 1. **SUBDOMAIN MODE** (Production - Recommended) ``` -https://STORE_SUBDOMAIN.platform.com/storefront/products +https://STORE_SUBDOMAIN.platform-domain.lu/ +https://STORE_SUBDOMAIN.platform-domain.lu/products +https://STORE_SUBDOMAIN.platform-domain.lu/cart Example: -https://acme.orion.lu/storefront/products -https://techpro.orion.lu/storefront/categories/electronics +https://acme.omsflow.lu/ +https://acme.omsflow.lu/products +https://techpro.rewardflow.lu/account/dashboard ``` ### 2. **CUSTOM DOMAIN MODE** (Production - Premium) ``` -https://STORE_CUSTOM_DOMAIN/storefront/products +https://STORE_CUSTOM_DOMAIN/ +https://STORE_CUSTOM_DOMAIN/products Example: -https://store.acmecorp.com/storefront/products -https://shop.techpro.io/storefront/cart +https://store.acmecorp.com/ +https://shop.techpro.io/cart ``` ### 3. **PATH-BASED MODE** (Development Only) ``` -http://localhost:PORT/platforms/PLATFORM_CODE/stores/STORE_CODE/storefront/products +http://localhost:PORT/platforms/PLATFORM_CODE/storefront/STORE_CODE/ +http://localhost:PORT/platforms/PLATFORM_CODE/storefront/STORE_CODE/products Example: -http://localhost:8000/platforms/oms/stores/acme/storefront/products -http://localhost:8000/platforms/loyalty/stores/techpro/storefront/checkout +http://localhost:8000/platforms/oms/storefront/ACME/products +http://localhost:8000/platforms/loyalty/storefront/TECHPRO/cart ``` --- @@ -51,7 +56,7 @@ Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its ow | `/about` | Main marketing site about page | | `/platforms/oms/` | OMS platform homepage | | `/platforms/oms/pricing` | OMS platform pricing page | -| `/platforms/oms/stores/{code}/storefront/` | Store storefront on OMS | +| `/platforms/oms/storefront/{code}/` | Store storefront on OMS | | `/platforms/oms/admin/` | Admin panel for OMS platform | | `/platforms/oms/store/{code}/` | Store dashboard on OMS | | `/platforms/loyalty/` | Loyalty platform homepage | @@ -67,10 +72,11 @@ Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its ow | `omsflow.lu/pricing` | OMS platform pricing page | | `omsflow.lu/admin/` | Admin panel for OMS platform | | `omsflow.lu/store/{code}/` | Store dashboard on OMS | -| `https://mybakery.lu/storefront/` | Store storefront (store's custom domain) | +| `mybakery.omsflow.lu/` | Store storefront (subdomain) | +| `https://mybakery.lu/` | Store storefront (custom domain) | | `rewardflow.lu/` | Loyalty platform homepage | -**Note:** In production, stores configure their own custom domains for storefronts. The platform domain (e.g., `omsflow.lu`) is used for admin and store dashboards, while storefronts use store-owned domains. +**Note:** In production, storefronts are accessed via subdomain (`store.omsflow.lu`) or custom domain (`mybakery.lu`). The root path `/` IS the storefront — the `PlatformContextMiddleware` internally rewrites it to `/storefront/`. Staff dashboards are at `/store/` on the same domain. ### Quick Reference by Platform @@ -79,14 +85,15 @@ Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its ow Dev: Platform: http://localhost:8000/platforms/oms/ Admin: http://localhost:8000/platforms/oms/admin/ - Store: http://localhost:8000/platforms/oms/store/{store_code}/ - Storefront: http://localhost:8000/platforms/oms/stores/{store_code}/storefront/ + Store: http://localhost:8000/platforms/oms/store/{store_code}/ + Storefront: http://localhost:8000/platforms/oms/storefront/{store_code}/ Prod: Platform: https://omsflow.lu/ Admin: https://omsflow.lu/admin/ - Store: https://omsflow.lu/store/{store_code}/ - Storefront: https://mybakery.lu/storefront/ (store's custom domain) + Store: https://{store}.omsflow.lu/store/ + Storefront: https://{store}.omsflow.lu/ (subdomain) + Storefront: https://mybakery.lu/ (custom domain) ``` #### For "loyalty" Platform @@ -94,14 +101,15 @@ Prod: Dev: Platform: http://localhost:8000/platforms/loyalty/ Admin: http://localhost:8000/platforms/loyalty/admin/ - Store: http://localhost:8000/platforms/loyalty/store/{store_code}/ - Storefront: http://localhost:8000/platforms/loyalty/stores/{store_code}/storefront/ + Store: http://localhost:8000/platforms/loyalty/store/{store_code}/ + Storefront: http://localhost:8000/platforms/loyalty/storefront/{store_code}/ Prod: Platform: https://rewardflow.lu/ Admin: https://rewardflow.lu/admin/ - Store: https://rewardflow.lu/store/{store_code}/ - Storefront: https://myrewards.lu/storefront/ (store's custom domain) + Store: https://{store}.rewardflow.lu/store/ + Storefront: https://{store}.rewardflow.lu/ (subdomain) + Storefront: https://myrewards.lu/ (custom domain) ``` ### Platform Routing Logic @@ -151,24 +159,24 @@ Request arrives ### 1. SUBDOMAIN MODE (Production - Recommended) -**URL Pattern:** `https://STORE_SUBDOMAIN.platform.com/storefront/...` +**URL Pattern:** `https://STORE_SUBDOMAIN.platform-domain/` (root path = storefront) **Example:** - Store subdomain: `acme` -- Platform domain: `orion.lu` -- Customer Storefront URL: `https://acme.orion.lu/storefront/products` -- Product Detail: `https://acme.orion.lu/storefront/products/123` +- Platform domain: `omsflow.lu` +- Customer Storefront URL: `https://acme.omsflow.lu/` +- Product Catalog: `https://acme.omsflow.lu/products` +- Staff Dashboard: `https://acme.omsflow.lu/store/dashboard` **How It Works:** -1. Customer visits `https://acme.orion.lu/storefront/products` -2. `store_context_middleware` detects subdomain `"acme"` -3. Queries: `SELECT * FROM stores WHERE subdomain = 'acme'` -4. Finds Store with ID=1 (ACME Store) +1. Customer visits `https://acme.omsflow.lu/products` +2. `PlatformContextMiddleware` detects subdomain `"acme"`, resolves platform from root domain `omsflow.lu` +3. Middleware rewrites path: `/products` → `/storefront/products` (internal) +4. `store_context_middleware` detects subdomain, queries: `SELECT * FROM stores WHERE subdomain = 'acme'` 5. Sets `request.state.store = Store(ACME Store)` -6. `context_middleware` detects it's a STOREFRONT request +6. `frontend_type_middleware` detects STOREFRONT from `/storefront` path prefix 7. `theme_context_middleware` loads ACME's theme -8. Routes to `storefront_pages.py` → `storefront_products_page()` -9. Renders template with ACME's colors, logo, and products +8. Routes to storefront handler, renders with ACME's theme and products **Advantages:** - Single SSL certificate for all stores (*.orion.lu) @@ -179,12 +187,12 @@ Request arrives ### 2. CUSTOM DOMAIN MODE (Production - Premium) -**URL Pattern:** `https://CUSTOM_DOMAIN/storefront/...` +**URL Pattern:** `https://CUSTOM_DOMAIN/` (root path = storefront) **Example:** - Store name: "ACME Store" - Custom domain: `store.acme-corp.com` -- Customer Storefront URL: `https://store.acme-corp.com/storefront/products` +- Customer Storefront URL: `https://store.acme-corp.com/products` **Database Setup:** ```sql @@ -198,13 +206,12 @@ id | store_id | domain | is_active | is_verified ``` **How It Works:** -1. Customer visits `https://store.acme-corp.com/storefront/products` -2. `store_context_middleware` detects custom domain (not *.orion.lu, not localhost) -3. Normalizes domain to `"store.acme-corp.com"` -4. Queries: `SELECT * FROM store_domains WHERE domain = 'store.acme-corp.com'` -5. Finds `StoreDomain` with `store_id = 1` -6. Joins to get `Store(ACME Store)` -7. Rest is same as subdomain mode... +1. Customer visits `https://store.acme-corp.com/products` +2. `PlatformContextMiddleware` detects custom domain, resolves platform via `StoreDomain` lookup +3. Middleware rewrites path: `/products` → `/storefront/products` (internal) +4. `store_context_middleware` detects custom domain, queries `store_domains` table +5. Finds `StoreDomain` with `store_id = 1`, joins to get `Store(ACME Store)` +6. Rest is same as subdomain mode... **Advantages:** - Professional branding with store's own domain @@ -219,20 +226,19 @@ id | store_id | domain | is_active | is_verified ### 3. PATH-BASED MODE (Development Only) -**URL Pattern:** `http://localhost:PORT/platforms/PLATFORM_CODE/stores/STORE_CODE/storefront/...` +**URL Pattern:** `http://localhost:PORT/platforms/PLATFORM_CODE/storefront/STORE_CODE/...` **Example:** -- Development: `http://localhost:8000/platforms/oms/stores/acme/storefront/products` -- With port: `http://localhost:8000/platforms/loyalty/stores/acme/storefront/products/123` +- Development: `http://localhost:8000/platforms/oms/storefront/ACME/products` +- With port: `http://localhost:8000/platforms/loyalty/storefront/ACME/cart` **How It Works:** -1. Developer visits `http://localhost:8000/platforms/oms/stores/acme/storefront/products` -2. Platform middleware detects `/platforms/oms/` prefix, sets platform context -3. `store_context_middleware` detects path-based routing pattern `/stores/acme/...` -4. Extracts store code `"acme"` from the path -5. Looks up Store: `SELECT * FROM stores WHERE subdomain = 'acme'` -6. Sets `request.state.store = Store(acme)` -7. Routes to storefront pages +1. Developer visits `http://localhost:8000/platforms/oms/storefront/ACME/products` +2. `PlatformContextMiddleware` detects `/platforms/oms/` prefix, sets platform context, strips prefix +3. `store_context_middleware` detects `/storefront/ACME/...` pattern, extracts store code `"ACME"` +4. Looks up Store: `SELECT * FROM stores WHERE store_code = 'ACME'` +5. Sets `request.state.store = Store(ACME)` +6. Routes to storefront pages **Advantages:** - Perfect for local development @@ -249,34 +255,37 @@ id | store_id | domain | is_active | is_verified ### Subdomain/Custom Domain (PRODUCTION) ``` -https://acme.orion.lu/storefront/ → Homepage -https://acme.orion.lu/storefront/products → Product Catalog -https://acme.orion.lu/storefront/products/123 → Product Detail -https://acme.orion.lu/storefront/categories/electronics → Category Page -https://acme.orion.lu/storefront/cart → Shopping Cart -https://acme.orion.lu/storefront/checkout → Checkout -https://acme.orion.lu/storefront/search?q=laptop → Search Results -https://acme.orion.lu/storefront/account/login → Customer Login -https://acme.orion.lu/storefront/account/dashboard → Account Dashboard (Auth Required) -https://acme.orion.lu/storefront/account/orders → Order History (Auth Required) -https://acme.orion.lu/storefront/account/profile → Profile (Auth Required) +https://acme.omsflow.lu/ → Homepage +https://acme.omsflow.lu/products → Product Catalog +https://acme.omsflow.lu/products/123 → Product Detail +https://acme.omsflow.lu/categories/electronics → Category Page +https://acme.omsflow.lu/cart → Shopping Cart +https://acme.omsflow.lu/checkout → Checkout +https://acme.omsflow.lu/search?q=laptop → Search Results +https://acme.omsflow.lu/account/login → Customer Login +https://acme.omsflow.lu/account/dashboard → Account Dashboard (Auth Required) +https://acme.omsflow.lu/account/orders → Order History (Auth Required) +https://acme.omsflow.lu/store/dashboard → Staff Dashboard (Auth Required) ``` +Note: In production, the root path `/` is the storefront. The `PlatformContextMiddleware` +internally rewrites paths to `/storefront/` for route matching. Staff access is at `/store/`. + ### Path-Based (DEVELOPMENT) ``` -http://localhost:8000/platforms/oms/stores/acme/storefront/ → Homepage -http://localhost:8000/platforms/oms/stores/acme/storefront/products → Products -http://localhost:8000/platforms/oms/stores/acme/storefront/products/123 → Product Detail -http://localhost:8000/platforms/oms/stores/acme/storefront/cart → Cart -http://localhost:8000/platforms/oms/stores/acme/storefront/checkout → Checkout -http://localhost:8000/platforms/oms/stores/acme/storefront/account/login → Login +http://localhost:8000/platforms/oms/storefront/ACME/ → Homepage +http://localhost:8000/platforms/oms/storefront/ACME/products → Products +http://localhost:8000/platforms/oms/storefront/ACME/products/123 → Product Detail +http://localhost:8000/platforms/oms/storefront/ACME/cart → Cart +http://localhost:8000/platforms/oms/storefront/ACME/checkout → Checkout +http://localhost:8000/platforms/oms/storefront/ACME/account/login → Login ``` ### API Endpoints (Same for All Modes) ``` -GET /api/v1/storefront/stores/1/products → Get store products -GET /api/v1/storefront/stores/1/products/123 → Get product details -POST /api/v1/storefront/stores/1/products/{id}/reviews → Add product review +GET /api/v1/storefront/products → Get store products (store from middleware) +GET /api/v1/storefront/products/123 → Get product details +POST /api/v1/storefront/products/{id}/reviews → Add product review ``` --- @@ -304,8 +313,8 @@ POST /api/v1/storefront/stores/1/products/{id}/reviews → Add product review ### Example: No Cross-Store Leakage ```python -# Customer on acme.orion.lu tries to access TechPro's products -# They make API call to /api/v1/storefront/stores/2/products +# Customer on acme.omsflow.lu tries to access TechPro's products +# Store context is set to ACME by middleware — all queries scoped to ACME # Backend checks: store = get_store_from_request(request) # Returns Store(id=1, name="ACME") @@ -458,35 +467,36 @@ In Jinja2 template: ## Path-Based Routing Implementation -**Current Solution: Double Router Mounting** +**Current Solution: Double Router Mounting + Path Rewriting** -The application handles path-based routing by registering storefront routes **twice** with different prefixes: +The application handles routing by registering storefront routes **twice** with different prefixes: ```python # In main.py app.include_router(storefront_pages.router, prefix="/storefront") -app.include_router(storefront_pages.router, prefix="/stores/{store_code}/storefront") +app.include_router(storefront_pages.router, prefix="/storefront/{store_code}") ``` **How This Works:** -1. **For Subdomain/Custom Domain Mode:** - - URL: `https://acme.orion.lu/storefront/products` +1. **For Subdomain/Custom Domain Mode (Production):** + - URL: `https://acme.omsflow.lu/products` + - `PlatformContextMiddleware` detects subdomain, rewrites path: `/products` → `/storefront/products` - Matches: First router with `/storefront` prefix - Route: `@router.get("/products")` → Full path: `/storefront/products` 2. **For Path-Based Development Mode:** - - URL: `http://localhost:8000/platforms/oms/stores/acme/storefront/products` + - URL: `http://localhost:8000/platforms/oms/storefront/ACME/products` - Platform middleware strips `/platforms/oms/` prefix, sets platform context - - Matches: Second router with `/stores/{store_code}/storefront` prefix - - Route: `@router.get("/products")` → Full path: `/stores/{store_code}/storefront/products` + - Matches: Second router with `/storefront/{store_code}` prefix + - Route: `@router.get("/products")` → Full path: `/storefront/{store_code}/products` - Bonus: `store_code` available as path parameter! **Benefits:** -- ✅ No middleware complexity or path manipulation -- ✅ FastAPI native routing -- ✅ Explicit and maintainable -- ✅ Store code accessible via path parameter when needed +- ✅ Clean separation: `/storefront/` = customer, `/store/` = staff +- ✅ Production URLs are clean (root path = storefront) +- ✅ No `/storefront/` prefix visible to production customers +- ✅ Internal path rewriting handled by ASGI middleware - ✅ Both deployment modes supported cleanly --- @@ -511,9 +521,9 @@ Set-Cookie: customer_token=eyJ...; Path=/storefront; HttpOnly; SameSite=Lax | Mode | URL | Use Case | SSL | DNS | |------|-----|----------|-----|-----| -| Subdomain | `store.platform.com/storefront` | Production (standard) | *.platform.com | Add subdomains | -| Custom Domain | `store-domain.com/storefront` | Production (premium) | Per store | Store configures | -| Path-Based | `localhost:8000/platforms/{p}/stores/{v}/storefront` | Development only | None | None | +| Subdomain | `store.platform.com/` | Production (standard) | *.platform.com | Add subdomains | +| Custom Domain | `store-domain.com/` | Production (premium) | Per store | Store configures | +| Path-Based | `localhost:8000/platforms/{p}/storefront/{v}/` | Development only | None | None | --- diff --git a/docs/archive/multi-platform-cms-architecture-implementation-plan.md b/docs/archive/multi-platform-cms-architecture-implementation-plan.md index 88eca8b9..f422d44e 100644 --- a/docs/archive/multi-platform-cms-architecture-implementation-plan.md +++ b/docs/archive/multi-platform-cms-architecture-implementation-plan.md @@ -190,7 +190,7 @@ if settings.environment == "development": Development (using /platforms/ prefix): - [x] `localhost:9999/platforms/oms/` → OMS homepage - [x] `localhost:9999/platforms/oms/pricing` → OMS pricing page -- [x] `localhost:9999/platforms/oms/stores/{code}/` → Store storefront +- [x] `localhost:9999/platforms/oms/storefront/{code}/` → Store storefront - [x] `localhost:9999/platforms/loyalty/` → Loyalty homepage --- @@ -246,7 +246,7 @@ Inserts Loyalty platform with: | Task | File | Status | |------|------|--------| | Update middleware URL detection | `middleware/platform_context.py` | ✅ | -| Change DEFAULT_PLATFORM_CODE | `middleware/platform_context.py` | ✅ | +| Change MAIN_PLATFORM_CODE | `middleware/platform_context.py` | ✅ | | Remove hardcoded /oms, /loyalty routes | `main.py` | ✅ | | Update platform_pages.py homepage | `app/routes/platform_pages.py` | ✅ | | Add 'main' platform migration | `alembic/versions/z6g7h8i9j0k1_...py` | ✅ | @@ -324,19 +324,19 @@ Included in `docs/architecture/multi-platform-cms.md`: ### Three-Tier Content Resolution ``` -Customer visits: omsflow.lu/stores/orion/about +Customer visits: orion.omsflow.lu/about │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Tier 1: Store Override │ -│ WHERE platform_id=1 AND store_id=123 AND slug='about' │ +│ WHERE platform_id=:pid AND store_id=123 AND slug='about' │ │ Found? → Return store's custom page │ └─────────────────────────────────────────────────────────────┘ │ Not found ▼ ┌─────────────────────────────────────────────────────────────┐ │ Tier 2: Store Default │ -│ WHERE platform_id=1 AND store_id IS NULL │ +│ WHERE platform_id=:pid AND store_id IS NULL │ │ AND is_platform_page=FALSE AND slug='about' │ │ Found? → Return platform default │ └─────────────────────────────────────────────────────────────┘ @@ -383,8 +383,8 @@ curl -s localhost:9999/platforms/loyalty/ | grep -o ".*" # 5. Verify middleware detection python -c " -from middleware.platform_context import PlatformContextMiddleware, DEFAULT_PLATFORM_CODE -print(f'DEFAULT_PLATFORM_CODE: {DEFAULT_PLATFORM_CODE}') +from middleware.platform_context import PlatformContextMiddleware, MAIN_PLATFORM_CODE +print(f'MAIN_PLATFORM_CODE: {MAIN_PLATFORM_CODE}') # Expected: main " ``` diff --git a/docs/backend/middleware-reference.md b/docs/backend/middleware-reference.md index eea94fed..9bf20c58 100644 --- a/docs/backend/middleware-reference.md +++ b/docs/backend/middleware-reference.md @@ -258,7 +258,7 @@ graph TD 5. **LanguageMiddleware** resolves language based on frontend type 6. **ThemeContextMiddleware** loads store theme based on context -**Note:** Path-based routing (e.g., `/stores/{code}/storefront/*`) is handled by double router mounting in `main.py`, not by middleware. +**Note:** Path-based routing (e.g., `/platforms/{platform_code}/storefront/{store_code}/*`) is handled by double router mounting in `main.py`, not by middleware. In production (subdomain/custom domain), `PlatformContextMiddleware` rewrites the path to prepend `/storefront/` internally. ---