# Wizamart Multi-Tenant URL Routing - QUICK REFERENCE

## TL;DR - Customer Shop URLs

### Production
```
Subdomain:      https://acme.wizamart.com/shop/products
Custom Domain:  https://store.acme-corp.com/shop/products
```

### Development
```
Path-Based:     http://localhost:8000/vendor/acme/shop/products
```

---

## URL Breakdown

### Subdomain Mode
```
https://acme.wizamart.com/shop/products
        ↑    ↑              ↑    ↑
        |    |              |    └─ Route: /shop/products
        |    |              └────── Context: /shop (shop frontend)
        |    └─────────────────────── Platform domain
        └──────────────────────────── Vendor subdomain (from vendors.subdomain)
```

### Custom Domain Mode
```
https://store.acme-corp.com/shop/products
        ↑                  ↑    ↑
        |                  |    └─ Route: /shop/products
        |                  └────── Context: /shop (shop frontend)
        └──────────────────────── Custom domain (from vendor_domains.domain)
```

### Path-Based Mode (Dev)
```
http://localhost:8000/vendor/acme/shop/products
        ↑              ↑      ↑    ↑    ↑
        |              |      |    |    └─ Route: /shop/products
        |              |      |    └────── Context: /shop
        |              |      └─────────── Vendor code: acme
        |              └────────────────── Development prefix: /vendor
        └─────────────────────────────── Localhost
```

---

## Request Flow (In Order)

```
1. Request arrives
   ↓
2. vendor_context_middleware
   → Detects vendor from host/path
   → Sets request.state.vendor = Vendor(...)
   ↓
3. context_middleware
   → Determines RequestContext.SHOP
   → Sets request.state.context_type
   ↓
4. theme_context_middleware
   → Loads vendor theme
   → Sets request.state.theme = {...}
   ↓
5. FastAPI route matching
   → Matches /shop/products
   ↓
6. Handler executes
   → Returns HTML template
   ↓
7. Template renders
   → Uses vendor theme
   → Uses vendor name
   ↓
8. JavaScript API call
   → fetch(/api/v1/public/vendors/{vendor_id}/products)
   ↓
9. Response sent to browser
   → Fully branded shop
```

---

## Detection Priority (Highest to Lowest)

```
1️⃣  Custom Domain  (e.g., store.acme-corp.com)
    └─ Queries: vendor_domains WHERE domain = 'store.acme-corp.com'

2️⃣  Subdomain      (e.g., acme.wizamart.com)
    └─ Queries: vendors WHERE subdomain = 'acme'

3️⃣  Path-Based     (e.g., /vendor/acme/...)
    └─ Queries: vendors WHERE subdomain = 'acme'
```

---

## All Shop Routes

| Path | Handler | Auth Required | Purpose |
|------|---------|---|---------|
| `/shop/` | `shop_products_page()` | ❌ | Homepage |
| `/shop/products` | `shop_products_page()` | ❌ | Product catalog |
| `/shop/products/{id}` | `shop_product_detail_page()` | ❌ | Product detail |
| `/shop/categories/{slug}` | `shop_category_page()` | ❌ | Category products |
| `/shop/cart` | `shop_cart_page()` | ❌ | Shopping cart |
| `/shop/checkout` | `shop_checkout_page()` | ❌ | Checkout |
| `/shop/search` | `shop_search_page()` | ❌ | Search results |
| `/shop/account/register` | `shop_register_page()` | ❌ | Customer registration |
| `/shop/account/login` | `shop_login_page()` | ❌ | Customer login |
| `/shop/account/dashboard` | `shop_account_dashboard_page()` | ✅ | Account dashboard |
| `/shop/account/orders` | `shop_orders_page()` | ✅ | Order history |
| `/shop/account/orders/{id}` | `shop_order_detail_page()` | ✅ | Order details |
| `/shop/account/profile` | `shop_profile_page()` | ✅ | Profile |
| `/shop/account/addresses` | `shop_addresses_page()` | ✅ | Address management |
| `/shop/account/wishlist` | `shop_wishlist_page()` | ✅ | Wishlist |
| `/shop/account/settings` | `shop_settings_page()` | ✅ | Account settings |
| `/shop/about` | `shop_about_page()` | ❌ | About us |
| `/shop/contact` | `shop_contact_page()` | ❌ | Contact |
| `/shop/privacy` | `shop_privacy_page()` | ❌ | Privacy policy |
| `/shop/terms` | `shop_terms_page()` | ❌ | Terms of service |

---

## API Endpoints (Vendor-Scoped)

All public API endpoints follow this pattern:

```
GET  /api/v1/public/vendors/{vendor_id}/products
GET  /api/v1/public/vendors/{vendor_id}/products/{product_id}
GET  /api/v1/public/vendors/{vendor_id}/products/search
POST /api/v1/public/vendors/{vendor_id}/cart
```

These work from **ANY** domain/subdomain because `vendor_id` is explicit.

---

## Request State Contents

After middleware chain, handler receives:

```python
request.state.vendor = Vendor(
    id=1,
    name="ACME Store",
    subdomain="acme",
    is_active=True,
    # ... other fields
)

request.state.vendor_context = {
    "detection_method": "subdomain",  # or "custom_domain" or "path"
    "subdomain": "acme",              # if subdomain/path mode
    "domain": "store.acme-corp.com",  # if custom_domain mode
    "host": "acme.wizamart.com"       # original host
}

request.state.context_type = RequestContext.SHOP  # or ADMIN, VENDOR_DASHBOARD, etc.

request.state.theme = {
    "theme_name": "modern",
    "colors": {
        "primary": "#FF6B6B",
        "secondary": "#FF8787",
        # ...
    },
    "branding": {
        "logo": "acme-logo.png",
        "favicon": "acme-favicon.ico",
        # ...
    },
    "fonts": {
        "heading": "Poppins, sans-serif",
        "body": "Inter, sans-serif"
    }
}

request.state.clean_path = "/shop/products"  # For path-based dev mode
```

---

## Vendor Isolation (Multi-Layer)

```
Layer 1: URL Detection
         ├─ Subdomain: vendor from host
         ├─ Custom domain: vendor from VendorDomain table
         └─ Path: vendor from URL path

Layer 2: Middleware
         └─ request.state.vendor set to correct Vendor object

Layer 3: Database Queries
         └─ WHERE vendor_id = ?
            (EVERY query includes vendor filter)

Layer 4: API Authorization
         └─ Verify vendor matches request context
            (No cross-vendor access possible)
```

---

## Common Scenarios

### Scenario 1: Customer on ACME's subdomain
```
URL: https://acme.wizamart.com/shop/products
Vendor detected: Vendor(id=1, subdomain="acme")
Database query: SELECT products WHERE vendor_id = 1 AND is_active = true
Result: ACME's products
```

### Scenario 2: Customer on TechPro's custom domain
```
URL: https://shop.techpro.io/shop/products
Vendor detected: Via VendorDomain(domain="shop.techpro.io", vendor_id=2)
Database query: SELECT products WHERE vendor_id = 2 AND is_active = true
Result: TechPro's products
```

### Scenario 3: Developer testing locally
```
URL: http://localhost:8000/vendor/acme/shop/products
Vendor detected: Vendor(id=1, subdomain="acme") via /vendor/acme/
Database query: SELECT products WHERE vendor_id = 1
Result: ACME's products
```

### Scenario 4: Cross-vendor attack attempt
```
Attack: Customer on acme.wizamart.com tries to access TechPro's API
URL: https://acme.wizamart.com/api/v1/public/vendors/2/products
Backend check: Is vendor_id in request (2) same as request.state.vendor.id (1)?
Result: ❌ UnauthorizedShopAccessException
```

---

## Middleware Files

```
middleware/
├─ vendor_context.py        ← Detects vendor from host/path
├─ theme_context.py         ← Loads vendor theme
├─ context_middleware.py     ← Determines request context type
└─ logging_middleware.py     ← Logs all requests
```

## Route Files

```
app/routes/
├─ shop_pages.py            ← Customer shop pages
├─ vendor_pages.py          ← Vendor dashboard pages
└─ admin_pages.py           ← Admin pages
```

---

## Environment Configuration

```python
# app/core/config.py

platform_domain = "wizamart.com"  # Platform domain for subdomains

# Allows all *.wizamart.com subdomains
allowed_hosts = ["*"]
```

---

## Development vs Production

### Development
```
HTTP  (not HTTPS - no SSL)
localhost:8000
Path-based routing: /vendor/vendor_code/...
No DNS setup needed
All vendors share same localhost
```

### Production
```
HTTPS (required - SSL certs)
Subdomain:   *.wizamart.com (wildcard SSL cert)
Custom:      vendor.domain.com (vendor's SSL cert)
DNS setup needed for subdomains/custom domains
Vendors have separate endpoints
```

---

## Debugging Tips

### Check which vendor was detected
```python
# In template or handler:
{{ request.state.vendor.name }}        # "ACME Store"
{{ request.state.vendor.subdomain }}   # "acme"
{{ request.state.context_type }}       # "shop"
```

### Check which theme was loaded
```python
# In template:
{{ request.state.theme.colors.primary }}   # "#FF6B6B"
{{ request.state.theme.branding.logo }}    # "acme-logo.png"
```

### Check vendor detection logs
```bash
# In server logs, look for:
[OK] Vendor found via subdomain: acme -> ACME Store
[WARNING] Vendor not found for context: {...}
```

### Test with curl
```bash
# Subdomain mode
curl -H "Host: acme.wizamart.com" http://localhost:8000/shop/products

# Path-based mode
curl http://localhost:8000/vendor/acme/shop/products
```

---

## Quick Links in Code

- Vendor detection: `middleware/vendor_context.py:VendorContextManager.detect_vendor_context()`
- Theme loading: `middleware/theme_context.py:ThemeContextManager.get_vendor_theme()`
- Context detection: `middleware/context_middleware.py:ContextManager.detect_context()`
- Shop routes: `app/routes/shop_pages.py`
- Main app setup: `main.py`

---

## Reference

- Platform: Wizamart
- Mode: Multi-tenant SaaS e-commerce
- Framework: FastAPI + SQLAlchemy + Jinja2
- Frontend: Alpine.js + Tailwind CSS
- Auth: JWT + Vendor-scoped cookies

Generated: November 7, 2025
