# Wizamart Multi-Tenant URL Routing Guide ## Quick Answer **How do customers access a vendor's shop in Wizamart?** There are three ways depending on the deployment mode: **⚠️ Important:** This guide describes **customer-facing shop routes**. For vendor dashboard/management routes, see [Vendor Frontend Architecture](../../frontend/vendor/architecture.md). The shop uses `/vendors/{code}/shop/*` (plural) in path-based mode, while the vendor dashboard uses `/vendor/{code}/*` (singular). ### 1. **SUBDOMAIN MODE** (Production - Recommended) ``` https://VENDOR_SUBDOMAIN.platform.com/shop/products Example: https://acme.wizamart.com/shop/products https://techpro.wizamart.com/shop/categories/electronics ``` ### 2. **CUSTOM DOMAIN MODE** (Production - Premium) ``` https://VENDOR_CUSTOM_DOMAIN/shop/products Example: https://store.acmecorp.com/shop/products https://shop.techpro.io/shop/cart ``` ### 3. **PATH-BASED MODE** (Development Only) ``` http://localhost:PORT/vendors/VENDOR_CODE/shop/products Example: http://localhost:8000/vendors/acme/shop/products http://localhost:8000/vendors/techpro/shop/checkout ``` --- ## Three Deployment Modes Explained ### 1. SUBDOMAIN MODE (Production - Recommended) **URL Pattern:** `https://VENDOR_SUBDOMAIN.platform.com/shop/...` **Example:** - Vendor subdomain: `acme` - Platform domain: `wizamart.com` - Customer Shop URL: `https://acme.wizamart.com/shop/products` - Product Detail: `https://acme.wizamart.com/shop/products/123` **How It Works:** 1. Customer visits `https://acme.wizamart.com/shop/products` 2. `vendor_context_middleware` detects subdomain `"acme"` 3. Queries: `SELECT * FROM vendors WHERE subdomain = 'acme'` 4. Finds Vendor with ID=1 (ACME Store) 5. Sets `request.state.vendor = Vendor(ACME Store)` 6. `context_middleware` detects it's a SHOP request 7. `theme_context_middleware` loads ACME's theme 8. Routes to `shop_pages.py` → `shop_products_page()` 9. Renders template with ACME's colors, logo, and products **Advantages:** - Single SSL certificate for all vendors (*.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/shop/...` **Example:** - Vendor name: "ACME Store" - Custom domain: `store.acme-corp.com` - Customer Shop URL: `https://store.acme-corp.com/shop/products` **Database Setup:** ```sql -- vendors table id | name | subdomain 1 | ACME Store | acme -- vendor_domains table (links custom domains to vendors) id | vendor_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/shop/products` 2. `vendor_context_middleware` detects custom domain (not *.wizamart.com, not localhost) 3. Normalizes domain to `"store.acme-corp.com"` 4. Queries: `SELECT * FROM vendor_domains WHERE domain = 'store.acme-corp.com'` 5. Finds `VendorDomain` with `vendor_id = 1` 6. Joins to get `Vendor(ACME Store)` 7. Rest is same as subdomain mode... **Advantages:** - Professional branding with vendor's own domain - Better for premium vendors - Vendor controls the domain **Considerations:** - Each vendor needs their own SSL certificate - Vendor must own and configure the domain --- ### 3. PATH-BASED MODE (Development Only) **URL Pattern:** `http://localhost:PORT/vendors/VENDOR_CODE/shop/...` **Example:** - Development: `http://localhost:8000/vendors/acme/shop/products` - With port: `http://localhost:8000/vendors/acme/shop/products/123` **How It Works:** 1. Developer visits `http://localhost:8000/vendors/acme/shop/products` 2. `vendor_context_middleware` detects path-based routing pattern `/vendors/acme/...` 3. Extracts vendor code `"acme"` from the path 4. Looks up Vendor: `SELECT * FROM vendors WHERE subdomain = 'acme'` 5. Sets `request.state.vendor = Vendor(acme)` 6. Routes to shop pages **Advantages:** - Perfect for local development - No need to configure DNS/domains - Test multiple vendors easily without domain setup **Limitations:** - Only for development (not production-ready) - All vendors share same localhost address --- ## Complete Route Examples ### Subdomain/Custom Domain (PRODUCTION) ``` https://acme.wizamart.com/shop/ → Homepage https://acme.wizamart.com/shop/products → Product Catalog https://acme.wizamart.com/shop/products/123 → Product Detail https://acme.wizamart.com/shop/categories/electronics → Category Page https://acme.wizamart.com/shop/cart → Shopping Cart https://acme.wizamart.com/shop/checkout → Checkout https://acme.wizamart.com/shop/search?q=laptop → Search Results https://acme.wizamart.com/shop/account/login → Customer Login https://acme.wizamart.com/shop/account/dashboard → Account Dashboard (Auth Required) https://acme.wizamart.com/shop/account/orders → Order History (Auth Required) https://acme.wizamart.com/shop/account/profile → Profile (Auth Required) ``` ### Path-Based (DEVELOPMENT) ``` http://localhost:8000/vendors/acme/shop/ → Homepage http://localhost:8000/vendors/acme/shop/products → Products http://localhost:8000/vendors/acme/shop/products/123 → Product Detail http://localhost:8000/vendors/acme/shop/cart → Cart http://localhost:8000/vendors/acme/shop/checkout → Checkout http://localhost:8000/vendors/acme/shop/account/login → Login ``` ### API Endpoints (Same for All Modes) ``` GET /api/v1/public/vendors/1/products → Get vendor products GET /api/v1/public/vendors/1/products/123 → Get product details POST /api/v1/public/vendors/1/products/{id}/reviews → Add product review ``` --- ## How Vendor Isolation Works ### Multi-Layer Enforcement **Layer 1: URL Routing** - Vendor is detected from subdomain, custom domain, or path - Each vendor gets their own request context **Layer 2: Middleware** - `request.state.vendor` is set to the detected Vendor object - All downstream code can access the vendor **Layer 3: Database Queries** - All queries must include `WHERE vendor_id = ?` - Product queries: `SELECT * FROM products WHERE vendor_id = 1` - Order queries: `SELECT * FROM orders WHERE vendor_id = 1` **Layer 4: API Authorization** - Endpoints verify the vendor matches the request vendor - Customers can only see their own vendor's products ### Example: No Cross-Vendor Leakage ```python # Customer on acme.wizamart.com tries to access TechPro's products # They make API call to /api/v1/public/vendors/2/products # Backend checks: vendor = get_vendor_from_request(request) # Returns Vendor(id=1, name="ACME") if vendor.id != requested_vendor_id: # if 1 != 2 raise UnauthorizedShopAccessException() ``` --- ## Request Lifecycle: Complete Flow ### Scenario: Customer visits `https://acme.wizamart.com/shop/products` ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. REQUEST ARRIVES │ └─────────────────────────────────────────────────────────────────┘ method: GET host: acme.wizamart.com path: /shop/products ┌─────────────────────────────────────────────────────────────────┐ │ 2. MIDDLEWARE CHAIN │ └─────────────────────────────────────────────────────────────────┘ A) vendor_context_middleware ├─ Detects host: "acme.wizamart.com" ├─ Extracts subdomain: "acme" ├─ Queries: SELECT * FROM vendors WHERE subdomain = 'acme' └─ Sets: request.state.vendor = Vendor(ACME Store) B) context_middleware ├─ Checks path: "/shop/products" ├─ Has request.state.vendor? YES └─ Sets: request.state.context_type = RequestContext.SHOP C) theme_context_middleware ├─ Queries: SELECT * FROM vendor_themes WHERE vendor_id = 1 └─ Sets: request.state.theme = {...ACME's theme...} ┌─────────────────────────────────────────────────────────────────┐ │ 3. ROUTE MATCHING │ └─────────────────────────────────────────────────────────────────┘ Path: /shop/products Matches: @router.get("/shop/products") Handler: shop_products_page(request) ┌─────────────────────────────────────────────────────────────────┐ │ 4. HANDLER EXECUTES │ └─────────────────────────────────────────────────────────────────┘ @router.get("/shop/products", response_class=HTMLResponse) async def shop_products_page(request: Request): return templates.TemplateResponse( "shop/products.html", {"request": request} ) ┌─────────────────────────────────────────────────────────────────┐ │ 5. TEMPLATE RENDERS │ └─────────────────────────────────────────────────────────────────┘ Template accesses: ├─ request.state.vendor.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/public/vendors/1/products`) .then(data => renderProducts(data.products, {theme})) ┌─────────────────────────────────────────────────────────────────┐ │ 7. RESPONSE SENT │ └─────────────────────────────────────────────────────────────────┘ HTML with ACME's colors, logo, and products ``` --- ## Theme Integration Each vendor's shop 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.vendor.name }}

Welcome to {{ request.state.vendor.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. Vendor Perspective - Vendors 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 vendor is being accessed - All business logic remains vendor-unaware - Database queries automatically filtered by vendor - 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 vendor-scoped cookies - **Database:** All tables have `vendor_id` foreign key --- ## Path-Based Routing Implementation **Current Solution: Double Router Mounting** The application handles path-based routing by registering shop routes **twice** with different prefixes: ```python # In main.py app.include_router(shop_pages.router, prefix="/shop") app.include_router(shop_pages.router, prefix="/vendors/{vendor_code}/shop") ``` **How This Works:** 1. **For Subdomain/Custom Domain Mode:** - URL: `https://acme.wizamart.com/shop/products` - Matches: First router with `/shop` prefix - Route: `@router.get("/products")` → Full path: `/shop/products` 2. **For Path-Based Development Mode:** - URL: `http://localhost:8000/vendors/acme/shop/products` - Matches: Second router with `/vendors/{vendor_code}/shop` prefix - Route: `@router.get("/products")` → Full path: `/vendors/{vendor_code}/shop/products` - Bonus: `vendor_code` available as path parameter! **Benefits:** - ✅ No middleware complexity or path manipulation - ✅ FastAPI native routing - ✅ Explicit and maintainable - ✅ Vendor code accessible via path parameter when needed - ✅ Both deployment modes supported cleanly --- ## Authentication in Multi-Tenant Shop Customer authentication uses vendor-scoped cookies: ```python # Login sets cookie scoped to vendor's shop Set-Cookie: customer_token=eyJ...; Path=/shop; HttpOnly; SameSite=Lax # This prevents: # - Tokens leaking across vendors # - Cross-site request forgery # - Cookie scope confusion in multi-tenant setup ``` --- ## Summary Table | Mode | URL | Use Case | SSL | DNS | |------|-----|----------|-----|-----| | Subdomain | `vendor.platform.com/shop` | Production (standard) | *.platform.com | Add subdomains | | Custom Domain | `vendor-domain.com/shop` | Production (premium) | Per vendor | Vendor configures | | Path-Based | `localhost:8000/vendors/v/shop` | 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 vendors with different themes 5. **For Scaling:** Consider CDN for vendor-specific assets --- Generated: November 7, 2025 Wizamart Version: Current Development