docs: update routing docs and seed script for production routing changes
Some checks failed
Some checks failed
Reflect the production routing refactor (ce5b54f): document store dashboard
double-mounting, per-platform subdomain overrides via StorePlatform.custom_subdomain,
get_resolved_store_code dependency, and /merchants/ reserved path. Update seed
script to populate custom_subdomain and StoreDomain.platform_id for demo data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,11 +13,16 @@ There are three ways depending on the deployment mode:
|
||||
https://STORE_SUBDOMAIN.platform-domain.lu/
|
||||
https://STORE_SUBDOMAIN.platform-domain.lu/products
|
||||
https://STORE_SUBDOMAIN.platform-domain.lu/cart
|
||||
https://STORE_SUBDOMAIN.platform-domain.lu/store/dashboard (staff)
|
||||
|
||||
Example:
|
||||
https://acme.omsflow.lu/
|
||||
https://acme.omsflow.lu/products
|
||||
https://techpro.rewardflow.lu/account/dashboard
|
||||
https://acme.omsflow.lu/store/dashboard (staff)
|
||||
|
||||
Per-platform subdomain override:
|
||||
https://wizatech-rewards.rewardflow.lu/ (same store as wizatech.omsflow.lu)
|
||||
```
|
||||
|
||||
### 2. **CUSTOM DOMAIN MODE** (Production - Premium)
|
||||
@@ -227,7 +232,9 @@ Request arrives
|
||||
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'`
|
||||
4. `store_context_middleware` performs two-step subdomain lookup:
|
||||
- First: `SELECT * FROM store_platforms WHERE custom_subdomain = 'acme'` (per-platform override)
|
||||
- Fallback: `SELECT * FROM stores WHERE subdomain = 'acme'` (standard subdomain)
|
||||
5. Sets `request.state.store = Store(ACME Store)`
|
||||
6. `frontend_type_middleware` detects STOREFRONT from `/storefront` path prefix
|
||||
7. `theme_context_middleware` loads ACME's theme
|
||||
@@ -310,6 +317,7 @@ id | store_id | domain | is_active | is_verified
|
||||
|
||||
### Subdomain/Custom Domain (PRODUCTION)
|
||||
```
|
||||
Storefront (customer-facing):
|
||||
https://acme.omsflow.lu/ → Homepage
|
||||
https://acme.omsflow.lu/products → Product Catalog
|
||||
https://acme.omsflow.lu/products/123 → Product Detail
|
||||
@@ -320,7 +328,16 @@ 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)
|
||||
|
||||
Store Dashboard (staff):
|
||||
https://acme.omsflow.lu/store/dashboard → Staff Dashboard (Auth Required)
|
||||
https://acme.omsflow.lu/store/products → Manage Products
|
||||
https://acme.omsflow.lu/store/orders → Manage Orders
|
||||
https://acme.omsflow.lu/store/login → Staff Login
|
||||
|
||||
Per-platform subdomain override:
|
||||
https://wizatech-rewards.rewardflow.lu/ → Same store as wizatech.omsflow.lu
|
||||
https://wizatech-rewards.rewardflow.lu/store/dashboard → Staff dashboard on loyalty platform
|
||||
```
|
||||
|
||||
Note: In production, the root path `/` is the storefront. The `PlatformContextMiddleware`
|
||||
@@ -524,28 +541,46 @@ In Jinja2 template:
|
||||
|
||||
**Current Solution: Double Router Mounting + Path Rewriting**
|
||||
|
||||
The application handles routing by registering storefront routes **twice** with different prefixes:
|
||||
The application handles routing by registering both storefront and store dashboard routes **twice** with different prefixes:
|
||||
|
||||
```python
|
||||
# In main.py
|
||||
# In main.py — Storefront routes (customer-facing)
|
||||
app.include_router(storefront_pages.router, prefix="/storefront")
|
||||
app.include_router(storefront_pages.router, prefix="/storefront/{store_code}")
|
||||
|
||||
# In main.py — Store dashboard routes (staff management)
|
||||
app.include_router(store_pages.router, prefix="/store")
|
||||
app.include_router(store_pages.router, prefix="/store/{store_code}")
|
||||
```
|
||||
|
||||
**How This Works:**
|
||||
|
||||
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`
|
||||
- Storefront: `https://acme.omsflow.lu/products` → path rewritten to `/storefront/products` → matches first storefront mount
|
||||
- Dashboard: `https://acme.omsflow.lu/store/dashboard` → matches first store mount at `/store`
|
||||
- Store resolved by middleware via `request.state.store`
|
||||
|
||||
2. **For Path-Based Development Mode:**
|
||||
- URL: `http://localhost:8000/platforms/oms/storefront/ACME/products`
|
||||
- Platform middleware strips `/platforms/oms/` prefix, sets platform context
|
||||
- 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!
|
||||
- Storefront: `http://localhost:8000/platforms/oms/storefront/ACME/products` → matches second storefront mount at `/storefront/{store_code}`
|
||||
- Dashboard: `http://localhost:8000/platforms/oms/store/ACME/dashboard` → matches second store mount at `/store/{store_code}`
|
||||
- `store_code` available as path parameter
|
||||
|
||||
### `get_resolved_store_code` Dependency
|
||||
|
||||
Route handlers use the `get_resolved_store_code` dependency to transparently obtain the store code regardless of deployment mode:
|
||||
|
||||
```python
|
||||
async def get_resolved_store_code(request: Request) -> str:
|
||||
# 1. Path parameter from double-mount (/store/{store_code}/...)
|
||||
store_code = request.path_params.get("store_code")
|
||||
if store_code:
|
||||
return store_code
|
||||
# 2. Middleware-resolved store (subdomain or custom domain)
|
||||
store = getattr(request.state, "store", None)
|
||||
if store:
|
||||
return store.store_code
|
||||
raise HTTPException(status_code=404, detail="Store not found")
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Clean separation: `/storefront/` = customer, `/store/` = staff
|
||||
@@ -553,6 +588,43 @@ app.include_router(storefront_pages.router, prefix="/storefront/{store_code}")
|
||||
- ✅ No `/storefront/` prefix visible to production customers
|
||||
- ✅ Internal path rewriting handled by ASGI middleware
|
||||
- ✅ Both deployment modes supported cleanly
|
||||
- ✅ `get_resolved_store_code` abstracts store resolution for handlers
|
||||
|
||||
---
|
||||
|
||||
## Per-Platform Subdomain Overrides
|
||||
|
||||
Stores that are active on multiple platforms can have a **custom subdomain** per platform via `StorePlatform.custom_subdomain`. This allows a single store to appear under different subdomains on different platform domains.
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
Store: WizaTech (subdomain: "wizatech")
|
||||
├── OMS platform → wizatech.omsflow.lu (uses Store.subdomain)
|
||||
└── Loyalty platform → wizatech-rewards.rewardflow.lu (uses StorePlatform.custom_subdomain)
|
||||
```
|
||||
|
||||
**Database:**
|
||||
```sql
|
||||
-- stores table
|
||||
id | store_code | subdomain
|
||||
1 | WIZATECH | wizatech
|
||||
|
||||
-- store_platforms table
|
||||
id | store_id | platform_id | custom_subdomain
|
||||
1 | 1 | 1 (oms) | NULL -- uses store.subdomain = "wizatech"
|
||||
2 | 1 | 2 (loyalty) | wizatech-rewards -- overrides to "wizatech-rewards"
|
||||
```
|
||||
|
||||
**Resolution order** (in `store_context_middleware`):
|
||||
1. Check `StorePlatform.custom_subdomain` for a match on the current platform
|
||||
2. Fall back to `Store.subdomain` for the standard lookup
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Brand differentiation**: A store selling electronics via OMS and running a loyalty program wants different branding per platform
|
||||
- **Subdomain conflicts**: Two unrelated stores might use the same subdomain on different platforms — custom subdomains resolve the collision
|
||||
- **Marketing**: Platform-specific landing URLs for campaigns (e.g., `wizatech-rewards.rewardflow.lu` for loyalty-specific promotions)
|
||||
|
||||
---
|
||||
|
||||
@@ -592,5 +664,5 @@ Set-Cookie: customer_token=eyJ...; Path=/storefront; HttpOnly; SameSite=Lax
|
||||
|
||||
---
|
||||
|
||||
Generated: January 30, 2026
|
||||
Generated: February 26, 2026
|
||||
Orion Version: Current Development
|
||||
|
||||
Reference in New Issue
Block a user