Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
25 KiB
25 KiB
Multi-Domain Architecture Diagram
Current vs New Architecture
BEFORE (Current Setup)
┌─────────────────────────────────────────────────────────────────┐
│ Your FastAPI Application │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Store Context Middleware │ │
│ │ │ │
│ │ Check Host header: │ │
│ │ • store1.platform.com → Query Store.subdomain │ │
│ │ • /store/store1/ → Query Store.subdomain │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Database: stores table │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ id │ subdomain │ name │ is_active │ │ │
│ │ ├────┼───────────┼─────────────┼─────────────────────┤ │ │
│ │ │ 1 │ store1 │ Shop Alpha │ true │ │ │
│ │ │ 2 │ store2 │ Shop Beta │ true │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Customers access via:
→ store1.platform.com (production)
→ /store/store1/ (development)
AFTER (With Custom Domains)
┌─────────────────────────────────────────────────────────────────┐
│ Your FastAPI Application │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Enhanced Store Context Middleware │ │
│ │ │ │
│ │ Priority 1: Check if custom domain │ │
│ │ • customdomain1.com → Query StoreDomain.domain │ │
│ │ │ │
│ │ Priority 2: Check if subdomain │ │
│ │ • store1.platform.com → Query Store.subdomain │ │
│ │ │ │
│ │ Priority 3: Check if path-based │ │
│ │ • /store/store1/ → Query Store.subdomain │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Database: stores table │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ id │ subdomain │ name │ is_active │ │ │
│ │ ├────┼───────────┼─────────────┼─────────────────────┤ │ │
│ │ │ 1 │ store1 │ Shop Alpha │ true │ │ │
│ │ │ 2 │ store2 │ Shop Beta │ true │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ NEW TABLE: store_domains │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ id │ store_id │ domain │ is_verified │ │ │
│ │ ├────┼───────────┼───────────────────┼───────────────┤ │ │
│ │ │ 1 │ 1 │ customdomain1.com │ true │ │ │
│ │ │ 2 │ 1 │ shop.alpha.com │ true │ │ │
│ │ │ 3 │ 2 │ customdomain2.com │ true │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Customers can now access via:
→ customdomain1.com (custom domain - Store 1)
→ shop.alpha.com (custom domain - Store 1)
→ customdomain2.com (custom domain - Store 2)
→ store1.platform.com (subdomain - still works!)
→ /store/store1/ (path-based - still works!)
Request Flow Diagram
Scenario 1: Customer visits customdomain1.com
┌──────────────────────┐
│ Customer Browser │
│ │
│ Visit: │
│ customdomain1.com │
└──────────┬───────────┘
│
│ HTTP Request
│ Host: customdomain1.com
↓
┌──────────────────────┐
│ DNS Resolution │
│ │
│ customdomain1.com │
│ ↓ │
│ 123.45.67.89 │ (Your server IP)
└──────────┬───────────┘
│
│ Routes to server
↓
┌──────────────────────┐
│ Nginx/Web Server │
│ │
│ Receives request │
│ server_name _; │ (Accept ALL domains)
│ │
│ Proxy to FastAPI │
│ with Host header │
└──────────┬───────────┘
│
│ proxy_set_header Host $host
↓
┌─────────────────────────────────────────────────────────┐
│ FastAPI Application │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Store Context Middleware │ │
│ │ │ │
│ │ host = "customdomain1.com" │ │
│ │ │ │
│ │ Step 1: Is it a custom domain? │ │
│ │ not host.endswith("platform.com") → YES │ │
│ │ │ │
│ │ Step 2: Query store_domains table │ │
│ │ SELECT * FROM store_domains │ │
│ │ WHERE domain = 'customdomain1.com' │ │
│ │ AND is_active = true │ │
│ │ AND is_verified = true │ │
│ │ │ │
│ │ Result: store_id = 1 │ │
│ │ │ │
│ │ Step 3: Load Store 1 │ │
│ │ SELECT * FROM stores WHERE id = 1 │ │
│ │ │ │
│ │ Step 4: Set request state │ │
│ │ request.state.store = Store(id=1, ...) │ │
│ └───────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Route Handler │ │
│ │ │ │
│ │ @router.get("/") │ │
│ │ def shop_home(request): │ │
│ │ store = request.state.store # Store 1 │ │
│ │ │ │
│ │ # All queries auto-scoped to Store 1 │ │
│ │ products = get_products(store.id) │ │
│ │ │ │
│ │ return render("shop.html", { │ │
│ │ "store": store, │ │
│ │ "products": products │ │
│ │ }) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ HTML Response
↓
┌──────────────────────┐
│ Customer Browser │
│ │
│ Sees: │
│ Store 1's shop │
│ at customdomain1.com│
└──────────────────────┘
Scenario 2: Customer visits store1.platform.com (subdomain)
Customer → DNS → Server → Nginx → FastAPI
FastAPI Middleware:
host = "store1.platform.com"
Step 1: Custom domain? NO (ends with .platform.com)
Step 2: Subdomain? YES
Extract "store1"
Query: SELECT * FROM stores
WHERE subdomain = 'store1'
Result: Store 1
request.state.store = Store 1
Route → Render Store 1's shop
Scenario 3: Development - localhost:8000/store/store1/
Customer → localhost:8000/store/store1/
FastAPI Middleware:
host = "localhost:8000"
path = "/store/store1/"
Step 1: Custom domain? NO (localhost)
Step 2: Subdomain? NO (localhost has no subdomain)
Step 3: Path-based? YES
Extract "store1" from path
Query: SELECT * FROM stores
WHERE subdomain = 'store1'
Result: Store 1
request.state.store = Store 1
request.state.clean_path = "/" (strip /store/store1)
Route → Render Store 1's shop
Database Relationships
┌─────────────────────────────────────────┐
│ stores │
├─────────────────────────────────────────┤
│ id (PK) │
│ subdomain (UNIQUE) │
│ name │
│ is_active │
│ ... │
└─────────────────┬───────────────────────┘
│
│ One-to-Many
│
┌─────────┴──────────┐
│ │
↓ ↓
┌───────────────────┐ ┌─────────────────────┐
│ store_domains │ │ products │
├───────────────────┤ ├─────────────────────┤
│ id (PK) │ │ id (PK) │
│ store_id (FK) │ │ store_id (FK) │
│ domain (UNIQUE) │ │ name │
│ is_primary │ │ price │
│ is_active │ │ ... │
│ is_verified │ └─────────────────────┘
│ verification_token│
│ ... │
└───────────────────┘
Example Data:
stores:
id=1, subdomain='store1', name='Shop Alpha'
id=2, subdomain='store2', name='Shop Beta'
store_domains:
id=1, store_id=1, domain='customdomain1.com', is_verified=true
id=2, store_id=1, domain='shop.alpha.com', is_verified=true
id=3, store_id=2, domain='customdomain2.com', is_verified=true
products:
id=1, store_id=1, name='Product A' ← Belongs to Store 1
id=2, store_id=1, name='Product B' ← Belongs to Store 1
id=3, store_id=2, name='Product C' ← Belongs to Store 2
Middleware Decision Tree
[HTTP Request Received]
│
↓
┌───────────────┐
│ Extract Host │
│ from headers │
└───────┬───────┘
│
↓
┌─────────────────────────┐
│ Is admin request? │
│ (admin.* or /admin) │
└────┬────────────────┬───┘
│ YES │ NO
↓ │
[Skip store detection] │
Admin routing │
↓
┌────────────────────────────┐
│ Does host end with │
│ .platform.com or localhost?│
└────┬───────────────────┬───┘
│ NO │ YES
│ │
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ CUSTOM DOMAIN │ │ Check for subdomain │
│ │ │ or path prefix │
│ Query: │ │ │
│ store_domains table │ │ Query: │
│ WHERE domain = host │ │ stores table │
│ │ │ WHERE subdomain = X │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
│ │
└─────────┬───────────────┘
│
↓
┌─────────────────┐
│ Store found? │
└────┬────────┬───┘
│ YES │ NO
↓ ↓
[Set request.state.store] [404 or homepage]
│
↓
[Continue to route handler]
Full System Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Internet │
└────────────────────────────┬────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
↓ ↓ ↓
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ customdomain1. │ │ store1. │ │ admin. │
│ com │ │ platform.com │ │ platform.com │
│ │ │ │ │ │
│ DNS → Server IP │ │ DNS → Server IP │ │ DNS → Server IP │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────┼───────────────────┘
│
↓
┌──────────────────────────────┐
│ Cloudflare / Load Balancer │
│ (Optional) │
│ - SSL Termination │
│ - DDoS Protection │
│ - CDN │
└──────────────┬───────────────┘
│
↓
┌──────────────────────────────┐
│ Nginx / Web Server │
│ │
│ server_name _; │ ← Accept ALL domains
│ proxy_pass FastAPI; │
│ proxy_set_header Host; │ ← Pass domain info
└──────────────┬───────────────┘
│
↓
┌────────────────────────────────────────────────────────────────┐
│ FastAPI Application │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Middleware Stack │ │
│ │ 1. CORS │ │
│ │ 2. Store Context ← Detects store from domain │ │
│ │ 3. Auth │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Route Handlers │ │
│ │ - Shop pages (store-scoped) │ │
│ │ - Admin pages │ │
│ │ - API endpoints │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Database Queries │ │
│ │ All queries filtered by: │ │
│ │ WHERE store_id = request.state.store.id │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
│
↓
┌──────────────────────────────┐
│ PostgreSQL Database │
│ │
│ Tables: │
│ - stores │
│ - store_domains ← NEW! │
│ - products │
│ - customers │
│ - orders │
└──────────────────────────────┘
DNS Configuration Examples
Store 1 wants to use customdomain1.com
At Domain Registrar (GoDaddy/Namecheap/etc):
Type: A
Name: @
Value: 123.45.67.89 (your server IP)
TTL: 3600
Type: A
Name: www
Value: 123.45.67.89
TTL: 3600
Type: TXT
Name: _orion-verify
Value: abc123xyz (verification token from your platform)
TTL: 3600
After DNS propagates (5-15 mins):
- Customer visits customdomain1.com
- DNS resolves to your server
- Nginx accepts request
- FastAPI middleware queries store_domains table
- Finds store_id = 1
- Shows Store 1's shop