# Wizamart Multi-Tenant URL Routing Guide ## Quick Answer **How do customers access a store's storefront in Wizamart?** 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). ### 1. **SUBDOMAIN MODE** (Production - Recommended) ``` https://STORE_SUBDOMAIN.platform.com/storefront/products Example: https://acme.wizamart.com/storefront/products https://techpro.wizamart.com/storefront/categories/electronics ``` ### 2. **CUSTOM DOMAIN MODE** (Production - Premium) ``` https://STORE_CUSTOM_DOMAIN/storefront/products Example: https://store.acmecorp.com/storefront/products https://shop.techpro.io/storefront/cart ``` ### 3. **PATH-BASED MODE** (Development Only) ``` http://localhost:PORT/platforms/PLATFORM_CODE/stores/STORE_CODE/storefront/products Example: http://localhost:8000/platforms/oms/stores/acme/storefront/products http://localhost:8000/platforms/loyalty/stores/techpro/storefront/checkout ``` --- ## Multi-Platform URL Routing Wizamart supports multiple platforms (OMS, Loyalty, Site Builder), each with its own marketing site and store ecosystem. ### Platform URL Structure #### Development Mode (localhost) | URL | What it serves | |-----|----------------| | `/` | Main marketing site homepage (`main` platform) | | `/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/admin/` | Admin panel for OMS platform | | `/platforms/oms/store/{code}/` | Store dashboard on OMS | | `/platforms/loyalty/` | Loyalty platform homepage | | `/platforms/loyalty/features` | Loyalty platform features page | #### Production Mode (custom domains) | URL | What it serves | |-----|----------------| | `wizamart.lu/` | Main marketing site homepage | | `wizamart.lu/about` | Main marketing site about page | | `oms.lu/` | OMS platform homepage | | `oms.lu/pricing` | OMS platform pricing page | | `oms.lu/admin/` | Admin panel for OMS platform | | `oms.lu/store/{code}/` | Store dashboard on OMS | | `https://mybakery.lu/storefront/` | Store storefront (store's custom domain) | | `loyalty.lu/` | Loyalty platform homepage | **Note:** In production, stores configure their own custom domains for storefronts. The platform domain (e.g., `oms.lu`) is used for admin and store dashboards, while storefronts use store-owned domains. ### Quick Reference by Platform #### For "oms" Platform ``` 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/ Prod: Platform: https://oms.lu/ Admin: https://oms.lu/admin/ Store: https://oms.lu/store/{store_code}/ Storefront: https://mybakery.lu/storefront/ (store's custom domain) ``` #### For "loyalty" Platform ``` 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/ Prod: Platform: https://loyalty.lu/ Admin: https://loyalty.lu/admin/ Store: https://loyalty.lu/store/{store_code}/ Storefront: https://myrewards.lu/storefront/ (store's custom domain) ``` ### Platform Routing Logic ``` Request arrives │ ▼ ┌─────────────────────────────────────┐ │ Check: Is this production domain? │ │ (oms.lu, loyalty.lu, etc.) │ └─────────────────────────────────────┘ │ ├── YES → Route to that platform │ ▼ NO (localhost) ┌─────────────────────────────────────┐ │ Check: Does path start with │ │ /platforms/{code}/ ? │ └─────────────────────────────────────┘ │ ├── YES → Strip prefix, route to platform │ /platforms/oms/pricing → /pricing on OMS │ ▼ NO ┌─────────────────────────────────────┐ │ Route to MAIN MARKETING SITE │ │ (no platform context) │ │ /faq → Main site FAQ page │ └─────────────────────────────────────┘ ``` ### Platform Codes | Platform | Code | Dev URL | Prod Domain | |----------|------|---------|-------------| | Main Marketing | `main` | `localhost:8000/` | `wizamart.lu` | | OMS | `oms` | `localhost:8000/platforms/oms/` | `oms.lu` | | Loyalty | `loyalty` | `localhost:8000/platforms/loyalty/` | `loyalty.lu` | | Site Builder | `site-builder` | `localhost:8000/platforms/site-builder/` | `sitebuilder.lu` | **See:** [Multi-Platform CMS Architecture](../multi-platform-cms.md) for content management details. --- ## Three Deployment Modes Explained ### 1. SUBDOMAIN MODE (Production - Recommended) **URL Pattern:** `https://STORE_SUBDOMAIN.platform.com/storefront/...` **Example:** - Store subdomain: `acme` - Platform domain: `wizamart.com` - Customer Storefront URL: `https://acme.wizamart.com/storefront/products` - Product Detail: `https://acme.wizamart.com/storefront/products/123` **How It Works:** 1. Customer visits `https://acme.wizamart.com/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) 5. Sets `request.state.store = Store(ACME Store)` 6. `context_middleware` detects it's a STOREFRONT request 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 **Advantages:** - Single SSL certificate for all stores (*.wizamart.com) - Easy to manage DNS (just add subdomains) - Customers don't need to bring their own domain --- ### 2. CUSTOM DOMAIN MODE (Production - Premium) **URL Pattern:** `https://CUSTOM_DOMAIN/storefront/...` **Example:** - Store name: "ACME Store" - Custom domain: `store.acme-corp.com` - Customer Storefront URL: `https://store.acme-corp.com/storefront/products` **Database Setup:** ```sql -- stores table id | name | subdomain 1 | ACME Store | acme -- store_domains table (links custom domains to stores) id | store_id | domain | is_active | is_verified 1 | 1 | store.acme-corp.com | true | true ``` **How It Works:** 1. Customer visits `https://store.acme-corp.com/storefront/products` 2. `store_context_middleware` detects custom domain (not *.wizamart.com, 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... **Advantages:** - Professional branding with store's own domain - Better for premium stores - Store controls the domain **Considerations:** - Each store needs their own SSL certificate - Store must own and configure the domain --- ### 3. PATH-BASED MODE (Development Only) **URL Pattern:** `http://localhost:PORT/platforms/PLATFORM_CODE/stores/STORE_CODE/storefront/...` **Example:** - Development: `http://localhost:8000/platforms/oms/stores/acme/storefront/products` - With port: `http://localhost:8000/platforms/loyalty/stores/acme/storefront/products/123` **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 **Advantages:** - Perfect for local development - No need to configure DNS/domains - Test multiple stores and platforms easily without domain setup **Limitations:** - Only for development (not production-ready) - All stores share same localhost address --- ## Complete Route Examples ### Subdomain/Custom Domain (PRODUCTION) ``` https://acme.wizamart.com/storefront/ → Homepage https://acme.wizamart.com/storefront/products → Product Catalog https://acme.wizamart.com/storefront/products/123 → Product Detail https://acme.wizamart.com/storefront/categories/electronics → Category Page https://acme.wizamart.com/storefront/cart → Shopping Cart https://acme.wizamart.com/storefront/checkout → Checkout https://acme.wizamart.com/storefront/search?q=laptop → Search Results https://acme.wizamart.com/storefront/account/login → Customer Login https://acme.wizamart.com/storefront/account/dashboard → Account Dashboard (Auth Required) https://acme.wizamart.com/storefront/account/orders → Order History (Auth Required) https://acme.wizamart.com/storefront/account/profile → Profile (Auth Required) ``` ### 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 ``` ### 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 ``` --- ## How Store Isolation Works ### Multi-Layer Enforcement **Layer 1: URL Routing** - Store is detected from subdomain, custom domain, or path - Each store gets their own request context **Layer 2: Middleware** - `request.state.store` is set to the detected Store object - All downstream code can access the store **Layer 3: Database Queries** - All queries must include `WHERE store_id = ?` - Product queries: `SELECT * FROM products WHERE store_id = 1` - Order queries: `SELECT * FROM orders WHERE store_id = 1` **Layer 4: API Authorization** - Endpoints verify the store matches the request store - Customers can only see their own store's products ### Example: No Cross-Store Leakage ```python # Customer on acme.wizamart.com tries to access TechPro's products # They make API call to /api/v1/storefront/stores/2/products # Backend checks: store = get_store_from_request(request) # Returns Store(id=1, name="ACME") if store.id != requested_store_id: # if 1 != 2 raise UnauthorizedStorefrontAccessException() ``` --- ## Request Lifecycle: Complete Flow ### Scenario: Customer visits `https://acme.wizamart.com/storefront/products` ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. REQUEST ARRIVES │ └─────────────────────────────────────────────────────────────────┘ method: GET host: acme.wizamart.com path: /storefront/products ┌─────────────────────────────────────────────────────────────────┐ │ 2. MIDDLEWARE CHAIN │ └─────────────────────────────────────────────────────────────────┘ A) store_context_middleware ├─ Detects host: "acme.wizamart.com" ├─ Extracts subdomain: "acme" ├─ Queries: SELECT * FROM stores WHERE subdomain = 'acme' └─ Sets: request.state.store = Store(ACME Store) B) context_middleware ├─ Checks path: "/storefront/products" ├─ Has request.state.store? YES └─ Sets: request.state.context_type = RequestContext.STOREFRONT C) theme_context_middleware ├─ Queries: SELECT * FROM store_themes WHERE store_id = 1 └─ Sets: request.state.theme = {...ACME's theme...} ┌─────────────────────────────────────────────────────────────────┐ │ 3. ROUTE MATCHING │ └─────────────────────────────────────────────────────────────────┘ Path: /storefront/products Matches: @router.get("/storefront/products") Handler: storefront_products_page(request) ┌─────────────────────────────────────────────────────────────────┐ │ 4. HANDLER EXECUTES │ └─────────────────────────────────────────────────────────────────┘ @router.get("/storefront/products", response_class=HTMLResponse) async def storefront_products_page(request: Request): return templates.TemplateResponse( "storefront/products.html", {"request": request} ) ┌─────────────────────────────────────────────────────────────────┐ │ 5. TEMPLATE RENDERS │ └─────────────────────────────────────────────────────────────────┘ Template accesses: ├─ request.state.store.name → "ACME Store" ├─ request.state.theme.colors.primary → "#FF6B6B" ├─ request.state.theme.branding.logo → "acme-logo.png" └─ Products will load via JavaScript API call ┌─────────────────────────────────────────────────────────────────┐ │ 6. JAVASCRIPT LOADS PRODUCTS (Client-Side) │ └─────────────────────────────────────────────────────────────────┘ fetch(`/api/v1/storefront/stores/1/products`) .then(data => renderProducts(data.products, {theme})) ┌─────────────────────────────────────────────────────────────────┐ │ 7. RESPONSE SENT │ └─────────────────────────────────────────────────────────────────┘ HTML with ACME's colors, logo, and products ``` --- ## Theme Integration Each store's storefront is fully branded with their custom theme: ```python # Theme loaded for https://acme.wizamart.com request.state.theme = { "theme_name": "modern", "colors": { "primary": "#FF6B6B", "secondary": "#FF8787", "accent": "#FF5252", "background": "#ffffff", "text": "#1f2937" }, "branding": { "logo": "acme-logo.png", "favicon": "acme-favicon.ico", "banner": "acme-banner.jpg" }, "fonts": { "heading": "Poppins, sans-serif", "body": "Inter, sans-serif" } } ``` In Jinja2 template: ```html {{ request.state.store.name }}

Welcome to {{ request.state.store.name }}

``` --- ## Key Points for Understanding ### 1. Customer Perspective - Customers just visit a URL (like any normal e-commerce site) - They have no awareness it's a multi-tenant platform - Each store looks completely separate and branded ### 2. Store Perspective - Stores can use a subdomain (free/standard): `acme.wizamart.com` - Or their own custom domain (premium): `store.acme-corp.com` - Both routes go to the exact same backend code ### 3. Developer Perspective - The middleware layer detects which store is being accessed - All business logic remains store-unaware - Database queries automatically filtered by store - No risk of data leakage because of multi-layer isolation ### 4. Tech Stack - **Frontend:** Jinja2 templates + Alpine.js + Tailwind CSS - **Backend:** FastAPI + SQLAlchemy - **Auth:** JWT with store-scoped cookies - **Database:** All tables have `store_id` foreign key --- ## Path-Based Routing Implementation **Current Solution: Double Router Mounting** The application handles path-based 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") ``` **How This Works:** 1. **For Subdomain/Custom Domain Mode:** - URL: `https://acme.wizamart.com/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` - 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` - 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 - ✅ Both deployment modes supported cleanly --- ## Authentication in Multi-Tenant Storefront Customer authentication uses store-scoped cookies: ```python # Login sets cookie scoped to store's storefront Set-Cookie: customer_token=eyJ...; Path=/storefront; HttpOnly; SameSite=Lax # This prevents: # - Tokens leaking across stores # - Cross-site request forgery # - Cookie scope confusion in multi-tenant setup ``` --- ## Summary Table | 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 | --- ## Next Steps 1. **For Production:** Use subdomain or custom domain mode 2. **For Development:** Use path-based mode locally 3. **For Deployment:** Configure DNS for subdomains or custom domains 4. **For Testing:** Create test stores with different themes 5. **For Scaling:** Consider CDN for store-specific assets --- Generated: January 30, 2026 Wizamart Version: Current Development