417 lines
25 KiB
Markdown
417 lines
25 KiB
Markdown
# Multi-Domain Architecture Diagram
|
|
|
|
## Current vs New Architecture
|
|
|
|
### BEFORE (Current Setup)
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Your FastAPI Application │
|
|
│ │
|
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
│ │ Vendor Context Middleware │ │
|
|
│ │ │ │
|
|
│ │ Check Host header: │ │
|
|
│ │ • vendor1.platform.com → Query Vendor.subdomain │ │
|
|
│ │ • /vendor/vendor1/ → Query Vendor.subdomain │ │
|
|
│ └────────────────────────────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
│ │ Database: vendors table │ │
|
|
│ │ ┌──────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ id │ subdomain │ name │ is_active │ │ │
|
|
│ │ ├────┼───────────┼─────────────┼─────────────────────┤ │ │
|
|
│ │ │ 1 │ vendor1 │ Shop Alpha │ true │ │ │
|
|
│ │ │ 2 │ vendor2 │ Shop Beta │ true │ │ │
|
|
│ │ └──────────────────────────────────────────────────────┘ │ │
|
|
│ └────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
Customers access via:
|
|
→ vendor1.platform.com (production)
|
|
→ /vendor/vendor1/ (development)
|
|
```
|
|
|
|
### AFTER (With Custom Domains)
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Your FastAPI Application │
|
|
│ │
|
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
│ │ Enhanced Vendor Context Middleware │ │
|
|
│ │ │ │
|
|
│ │ Priority 1: Check if custom domain │ │
|
|
│ │ • customdomain1.com → Query VendorDomain.domain │ │
|
|
│ │ │ │
|
|
│ │ Priority 2: Check if subdomain │ │
|
|
│ │ • vendor1.platform.com → Query Vendor.subdomain │ │
|
|
│ │ │ │
|
|
│ │ Priority 3: Check if path-based │ │
|
|
│ │ • /vendor/vendor1/ → Query Vendor.subdomain │ │
|
|
│ └────────────────────────────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
│ │ Database: vendors table │ │
|
|
│ │ ┌──────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ id │ subdomain │ name │ is_active │ │ │
|
|
│ │ ├────┼───────────┼─────────────┼─────────────────────┤ │ │
|
|
│ │ │ 1 │ vendor1 │ Shop Alpha │ true │ │ │
|
|
│ │ │ 2 │ vendor2 │ Shop Beta │ true │ │ │
|
|
│ │ └──────────────────────────────────────────────────────┘ │ │
|
|
│ └────────────────────────────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
│ │ NEW TABLE: vendor_domains │ │
|
|
│ │ ┌──────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ id │ vendor_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 - Vendor 1)
|
|
→ shop.alpha.com (custom domain - Vendor 1)
|
|
→ customdomain2.com (custom domain - Vendor 2)
|
|
→ vendor1.platform.com (subdomain - still works!)
|
|
→ /vendor/vendor1/ (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 │
|
|
│ │
|
|
│ ┌───────────────────────────────────────────────────┐ │
|
|
│ │ Vendor Context Middleware │ │
|
|
│ │ │ │
|
|
│ │ host = "customdomain1.com" │ │
|
|
│ │ │ │
|
|
│ │ Step 1: Is it a custom domain? │ │
|
|
│ │ not host.endswith("platform.com") → YES │ │
|
|
│ │ │ │
|
|
│ │ Step 2: Query vendor_domains table │ │
|
|
│ │ SELECT * FROM vendor_domains │ │
|
|
│ │ WHERE domain = 'customdomain1.com' │ │
|
|
│ │ AND is_active = true │ │
|
|
│ │ AND is_verified = true │ │
|
|
│ │ │ │
|
|
│ │ Result: vendor_id = 1 │ │
|
|
│ │ │ │
|
|
│ │ Step 3: Load Vendor 1 │ │
|
|
│ │ SELECT * FROM vendors WHERE id = 1 │ │
|
|
│ │ │ │
|
|
│ │ Step 4: Set request state │ │
|
|
│ │ request.state.vendor = Vendor(id=1, ...) │ │
|
|
│ └───────────────────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌───────────────────────────────────────────────────┐ │
|
|
│ │ Route Handler │ │
|
|
│ │ │ │
|
|
│ │ @router.get("/") │ │
|
|
│ │ def shop_home(request): │ │
|
|
│ │ vendor = request.state.vendor # Vendor 1 │ │
|
|
│ │ │ │
|
|
│ │ # All queries auto-scoped to Vendor 1 │ │
|
|
│ │ products = get_products(vendor.id) │ │
|
|
│ │ │ │
|
|
│ │ return render("shop.html", { │ │
|
|
│ │ "vendor": vendor, │ │
|
|
│ │ "products": products │ │
|
|
│ │ }) │ │
|
|
│ └───────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│
|
|
│ HTML Response
|
|
↓
|
|
┌──────────────────────┐
|
|
│ Customer Browser │
|
|
│ │
|
|
│ Sees: │
|
|
│ Vendor 1's shop │
|
|
│ at customdomain1.com│
|
|
└──────────────────────┘
|
|
```
|
|
|
|
### Scenario 2: Customer visits vendor1.platform.com (subdomain)
|
|
```
|
|
Customer → DNS → Server → Nginx → FastAPI
|
|
|
|
FastAPI Middleware:
|
|
host = "vendor1.platform.com"
|
|
|
|
Step 1: Custom domain? NO (ends with .platform.com)
|
|
Step 2: Subdomain? YES
|
|
Extract "vendor1"
|
|
Query: SELECT * FROM vendors
|
|
WHERE subdomain = 'vendor1'
|
|
Result: Vendor 1
|
|
|
|
request.state.vendor = Vendor 1
|
|
|
|
Route → Render Vendor 1's shop
|
|
```
|
|
|
|
### Scenario 3: Development - localhost:8000/vendor/vendor1/
|
|
```
|
|
Customer → localhost:8000/vendor/vendor1/
|
|
|
|
FastAPI Middleware:
|
|
host = "localhost:8000"
|
|
path = "/vendor/vendor1/"
|
|
|
|
Step 1: Custom domain? NO (localhost)
|
|
Step 2: Subdomain? NO (localhost has no subdomain)
|
|
Step 3: Path-based? YES
|
|
Extract "vendor1" from path
|
|
Query: SELECT * FROM vendors
|
|
WHERE subdomain = 'vendor1'
|
|
Result: Vendor 1
|
|
|
|
request.state.vendor = Vendor 1
|
|
request.state.clean_path = "/" (strip /vendor/vendor1)
|
|
|
|
Route → Render Vendor 1's shop
|
|
```
|
|
|
|
## Database Relationships
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ vendors │
|
|
├─────────────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ subdomain (UNIQUE) │
|
|
│ name │
|
|
│ is_active │
|
|
│ ... │
|
|
└─────────────────┬───────────────────────┘
|
|
│
|
|
│ One-to-Many
|
|
│
|
|
┌─────────┴──────────┐
|
|
│ │
|
|
↓ ↓
|
|
┌───────────────────┐ ┌─────────────────────┐
|
|
│ vendor_domains │ │ products │
|
|
├───────────────────┤ ├─────────────────────┤
|
|
│ id (PK) │ │ id (PK) │
|
|
│ vendor_id (FK) │ │ vendor_id (FK) │
|
|
│ domain (UNIQUE) │ │ name │
|
|
│ is_primary │ │ price │
|
|
│ is_active │ │ ... │
|
|
│ is_verified │ └─────────────────────┘
|
|
│ verification_token│
|
|
│ ... │
|
|
└───────────────────┘
|
|
|
|
Example Data:
|
|
|
|
vendors:
|
|
id=1, subdomain='vendor1', name='Shop Alpha'
|
|
id=2, subdomain='vendor2', name='Shop Beta'
|
|
|
|
vendor_domains:
|
|
id=1, vendor_id=1, domain='customdomain1.com', is_verified=true
|
|
id=2, vendor_id=1, domain='shop.alpha.com', is_verified=true
|
|
id=3, vendor_id=2, domain='customdomain2.com', is_verified=true
|
|
|
|
products:
|
|
id=1, vendor_id=1, name='Product A' ← Belongs to Vendor 1
|
|
id=2, vendor_id=1, name='Product B' ← Belongs to Vendor 1
|
|
id=3, vendor_id=2, name='Product C' ← Belongs to Vendor 2
|
|
```
|
|
|
|
## Middleware Decision Tree
|
|
|
|
```
|
|
[HTTP Request Received]
|
|
│
|
|
↓
|
|
┌───────────────┐
|
|
│ Extract Host │
|
|
│ from headers │
|
|
└───────┬───────┘
|
|
│
|
|
↓
|
|
┌─────────────────────────┐
|
|
│ Is admin request? │
|
|
│ (admin.* or /admin) │
|
|
└────┬────────────────┬───┘
|
|
│ YES │ NO
|
|
↓ │
|
|
[Skip vendor detection] │
|
|
Admin routing │
|
|
↓
|
|
┌────────────────────────────┐
|
|
│ Does host end with │
|
|
│ .platform.com or localhost?│
|
|
└────┬───────────────────┬───┘
|
|
│ NO │ YES
|
|
│ │
|
|
↓ ↓
|
|
┌──────────────────────┐ ┌──────────────────────┐
|
|
│ CUSTOM DOMAIN │ │ Check for subdomain │
|
|
│ │ │ or path prefix │
|
|
│ Query: │ │ │
|
|
│ vendor_domains table │ │ Query: │
|
|
│ WHERE domain = host │ │ vendors table │
|
|
│ │ │ WHERE subdomain = X │
|
|
└──────────┬───────────┘ └──────────┬───────────┘
|
|
│ │
|
|
│ │
|
|
└─────────┬───────────────┘
|
|
│
|
|
↓
|
|
┌─────────────────┐
|
|
│ Vendor found? │
|
|
└────┬────────┬───┘
|
|
│ YES │ NO
|
|
↓ ↓
|
|
[Set request.state.vendor] [404 or homepage]
|
|
│
|
|
↓
|
|
[Continue to route handler]
|
|
```
|
|
|
|
## Full System Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ Internet │
|
|
└────────────────────────────┬────────────────────────────────────────┘
|
|
│
|
|
┌───────────────────┼───────────────────┐
|
|
│ │ │
|
|
↓ ↓ ↓
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ customdomain1. │ │ vendor1. │ │ 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. Vendor Context ← Detects vendor from domain │ │
|
|
│ │ 3. Auth │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Route Handlers │ │
|
|
│ │ - Shop pages (vendor-scoped) │ │
|
|
│ │ - Admin pages │ │
|
|
│ │ - API endpoints │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ ↓ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Database Queries │ │
|
|
│ │ All queries filtered by: │ │
|
|
│ │ WHERE vendor_id = request.state.vendor.id │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
└────────────────────────────────────────────────────────────────┘
|
|
│
|
|
↓
|
|
┌──────────────────────────────┐
|
|
│ PostgreSQL Database │
|
|
│ │
|
|
│ Tables: │
|
|
│ - vendors │
|
|
│ - vendor_domains ← NEW! │
|
|
│ - products │
|
|
│ - customers │
|
|
│ - orders │
|
|
└──────────────────────────────┘
|
|
```
|
|
|
|
## DNS Configuration Examples
|
|
|
|
### Vendor 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: _wizamart-verify
|
|
Value: abc123xyz (verification token from your platform)
|
|
TTL: 3600
|
|
```
|
|
|
|
**After DNS propagates (5-15 mins):**
|
|
1. Customer visits customdomain1.com
|
|
2. DNS resolves to your server
|
|
3. Nginx accepts request
|
|
4. FastAPI middleware queries vendor_domains table
|
|
5. Finds vendor_id = 1
|
|
6. Shows Vendor 1's shop
|