revamping documentation
This commit is contained in:
416
docs/architecture/diagrams/multitenant-diagrams.md
Normal file
416
docs/architecture/diagrams/multitenant-diagrams.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# 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
|
||||
478
docs/architecture/diagrams/vendor-domain-diagrams.md
Normal file
478
docs/architecture/diagrams/vendor-domain-diagrams.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# Vendor Domains - Architecture Diagram
|
||||
|
||||
## System Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CLIENT REQUEST │
|
||||
│ POST /vendors/1/domains │
|
||||
│ {"domain": "myshop.com"} │
|
||||
└────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ENDPOINT LAYER │
|
||||
│ app/api/v1/admin/vendor_domains.py │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ @router.post("/{vendor_id}/domains") │
|
||||
│ def add_vendor_domain( │
|
||||
│ vendor_id: int, │
|
||||
│ domain_data: VendorDomainCreate, ◄───┐ │
|
||||
│ db: Session, │ │
|
||||
│ current_admin: User │ │
|
||||
│ ): │ │
|
||||
│ domain = vendor_domain_service │ │
|
||||
│ .add_domain(...) │ │
|
||||
│ return VendorDomainResponse(...) │ │
|
||||
│ │ │
|
||||
└─────────────────────┬───────────────────────┼───────────────────┘
|
||||
│ │
|
||||
│ │
|
||||
┌────────────▼──────────┐ ┌────────▼─────────┐
|
||||
│ Pydantic Validation │ │ Authentication │
|
||||
│ (Auto by FastAPI) │ │ Dependency │
|
||||
└────────────┬──────────┘ └──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SERVICE LAYER │
|
||||
│ app/services/vendor_domain_service.py │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ class VendorDomainService: │
|
||||
│ │
|
||||
│ def add_domain(db, vendor_id, domain_data): │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ 1. Verify vendor exists │ │
|
||||
│ │ 2. Check domain limit │ │
|
||||
│ │ 3. Validate domain format │ │
|
||||
│ │ 4. Check uniqueness │ │
|
||||
│ │ 5. Handle primary domain logic │ │
|
||||
│ │ 6. Create database record │ │
|
||||
│ │ 7. Generate verification token │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ Raises Custom Exceptions │ │
|
||||
│ │ - VendorNotFoundException │ │
|
||||
│ │ - DomainAlreadyExistsException │ │
|
||||
│ │ - MaxDomainsReachedException │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DATABASE LAYER │
|
||||
│ models/database/vendor_domain.py │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ class VendorDomain(Base): │
|
||||
│ id: int │
|
||||
│ vendor_id: int (FK) │
|
||||
│ domain: str (unique) │
|
||||
│ is_primary: bool │
|
||||
│ is_active: bool │
|
||||
│ is_verified: bool │
|
||||
│ verification_token: str │
|
||||
│ ssl_status: str │
|
||||
│ ... │
|
||||
│ │
|
||||
└─────────────────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DATABASE │
|
||||
│ PostgreSQL / MySQL │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Request Flow Diagram
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Client │
|
||||
└────┬─────┘
|
||||
│ POST /vendors/1/domains
|
||||
│ {"domain": "myshop.com", "is_primary": true}
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ FastAPI Router │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ 1. URL Routing │ │
|
||||
│ │ 2. Pydantic Validation │ │
|
||||
│ │ 3. Dependency Injection │ │
|
||||
│ │ - get_db() │ │
|
||||
│ │ - get_current_admin_user() │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Endpoint Function │
|
||||
│ add_vendor_domain() │
|
||||
│ │
|
||||
│ ✓ Receives validated data │
|
||||
│ ✓ Has DB session │
|
||||
│ ✓ Has authenticated admin user │
|
||||
│ ✓ Calls service layer │
|
||||
│ ✓ Returns response model │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Service Layer │
|
||||
│ vendor_domain_service.add_domain() │
|
||||
│ │
|
||||
│ Business Logic: │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ Vendor Validation │ │
|
||||
│ │ ├─ Check vendor exists │ │
|
||||
│ │ └─ Get vendor object │ │
|
||||
│ │ │ │
|
||||
│ │ Limit Checking │ │
|
||||
│ │ ├─ Count existing domains │ │
|
||||
│ │ └─ Enforce max limit │ │
|
||||
│ │ │ │
|
||||
│ │ Domain Validation │ │
|
||||
│ │ ├─ Normalize format │ │
|
||||
│ │ ├─ Check reserved subdomains │ │
|
||||
│ │ └─ Validate regex pattern │ │
|
||||
│ │ │ │
|
||||
│ │ Uniqueness Check │ │
|
||||
│ │ └─ Query existing domains │ │
|
||||
│ │ │ │
|
||||
│ │ Primary Domain Logic │ │
|
||||
│ │ └─ Unset other primary domains │ │
|
||||
│ │ │ │
|
||||
│ │ Create Record │ │
|
||||
│ │ ├─ Generate verification token │ │
|
||||
│ │ ├─ Set initial status │ │
|
||||
│ │ └─ Create VendorDomain object │ │
|
||||
│ │ │ │
|
||||
│ │ Database Transaction │ │
|
||||
│ │ ├─ db.add() │ │
|
||||
│ │ ├─ db.commit() │ │
|
||||
│ │ └─ db.refresh() │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Database │
|
||||
│ INSERT INTO vendor_domains ... │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Return to Endpoint │
|
||||
│ ← VendorDomain object │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Endpoint Response │
|
||||
│ VendorDomainResponse( │
|
||||
│ id=1, │
|
||||
│ domain="myshop.com", │
|
||||
│ is_verified=False, │
|
||||
│ verification_token="abc123...", │
|
||||
│ ... │
|
||||
│ ) │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ FastAPI Serialization │
|
||||
│ Convert to JSON │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ HTTP Response (201 Created) │
|
||||
│ { │
|
||||
│ "id": 1, │
|
||||
│ "domain": "myshop.com", │
|
||||
│ "is_verified": false, │
|
||||
│ "verification_token": "abc123...", │
|
||||
│ ... │
|
||||
│ } │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Client │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
## Error Handling Flow
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Client │
|
||||
└────┬─────┘
|
||||
│ POST /vendors/1/domains
|
||||
│ {"domain": "existing.com"}
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Service Layer │
|
||||
│ │
|
||||
│ def add_domain(...): │
|
||||
│ if self._domain_exists(db, domain): │
|
||||
│ raise VendorDomainAlready │
|
||||
│ ExistsException( │
|
||||
│ domain="existing.com", │
|
||||
│ existing_vendor_id=2 │
|
||||
│ ) │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
│ Exception raised
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Exception Handler │
|
||||
│ app/exceptions/handler.py │
|
||||
│ │
|
||||
│ @app.exception_handler(WizamartException) │
|
||||
│ async def custom_exception_handler(...): │
|
||||
│ return JSONResponse( │
|
||||
│ status_code=exc.status_code, │
|
||||
│ content=exc.to_dict() │
|
||||
│ ) │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ HTTP Response (409 Conflict) │
|
||||
│ { │
|
||||
│ "error_code": "VENDOR_DOMAIN_ │
|
||||
│ ALREADY_EXISTS", │
|
||||
│ "message": "Domain 'existing.com' │
|
||||
│ is already registered", │
|
||||
│ "status_code": 409, │
|
||||
│ "details": { │
|
||||
│ "domain": "existing.com", │
|
||||
│ "existing_vendor_id": 2 │
|
||||
│ } │
|
||||
│ } │
|
||||
└────────────────┬───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Client │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
## Component Interaction Diagram
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Endpoints │ ◄─── HTTP Requests from client
|
||||
│ (HTTP Layer) │ ───► HTTP Responses to client
|
||||
└────────┬────────┘
|
||||
│ Calls
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Service │ ◄─── Business logic
|
||||
│ Layer │ ───► Returns domain objects
|
||||
└────────┬────────┘ or raises exceptions
|
||||
│ Uses
|
||||
│
|
||||
├──────────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Database │ │ Exceptions │
|
||||
│ Models │ │ (Custom) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ SQLAlchemy │ │ Exception │
|
||||
│ ORM │ │ Handler │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Database │ │ JSON Error │
|
||||
│ (PostgreSQL) │ │ Response │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow for Domain Verification
|
||||
|
||||
```
|
||||
Step 1: Add Domain
|
||||
┌──────────┐
|
||||
│ Admin │ POST /vendors/1/domains
|
||||
└────┬─────┘ {"domain": "myshop.com"}
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ System creates domain record │
|
||||
│ - domain: "myshop.com" │
|
||||
│ - is_verified: false │
|
||||
│ - verification_token: "abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
Step 2: Get Instructions
|
||||
┌──────────┐
|
||||
│ Admin │ GET /domains/1/verification-instructions
|
||||
└────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ System returns instructions: │
|
||||
│ "Add TXT record: │
|
||||
│ _wizamart-verify.myshop.com │
|
||||
│ Value: abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
Step 3: Vendor Adds DNS Record
|
||||
┌──────────┐
|
||||
│ Vendor │ Adds TXT record at DNS provider
|
||||
└────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ DNS Provider (GoDaddy/etc) │
|
||||
│ _wizamart-verify.myshop.com TXT │
|
||||
│ "abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
Step 4: Verify Domain
|
||||
┌──────────┐
|
||||
│ Admin │ POST /domains/1/verify
|
||||
└────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ System: │
|
||||
│ 1. Queries DNS for TXT record │
|
||||
│ 2. Checks token matches │
|
||||
│ 3. Updates domain: │
|
||||
│ - is_verified: true │
|
||||
│ - verified_at: now() │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
Step 5: Activate Domain
|
||||
┌──────────┐
|
||||
│ Admin │ PUT /domains/1 {"is_active": true}
|
||||
└────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ System activates domain: │
|
||||
│ - is_active: true │
|
||||
│ - Domain now routes to vendor │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
Result: Domain Active!
|
||||
┌──────────────┐
|
||||
│ Customer │ Visits https://myshop.com
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ Middleware detects custom domain │
|
||||
│ Routes to Vendor 1 │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Structure Visual
|
||||
|
||||
```
|
||||
project/
|
||||
│
|
||||
├── app/
|
||||
│ ├── api/
|
||||
│ │ └── v1/
|
||||
│ │ └── admin/
|
||||
│ │ ├── vendors.py ✓ Existing (reference)
|
||||
│ │ └── vendor_domains.py ★ NEW (endpoints)
|
||||
│ │
|
||||
│ ├── services/
|
||||
│ │ ├── vendor_service.py ✓ Existing (reference)
|
||||
│ │ └── vendor_domain_service.py ★ NEW (business logic)
|
||||
│ │
|
||||
│ └── exceptions/
|
||||
│ ├── __init__.py ✓ UPDATE (add exports)
|
||||
│ ├── base.py ✓ Existing
|
||||
│ ├── auth.py ✓ Existing
|
||||
│ ├── admin.py ✓ Existing
|
||||
│ └── vendor_domain.py ★ NEW (custom exceptions)
|
||||
│
|
||||
└── models/
|
||||
├── schema/
|
||||
│ ├── vendor.py ✓ Existing
|
||||
│ └── vendor_domain.py ★ NEW (pydantic schemas)
|
||||
│
|
||||
└── database/
|
||||
├── vendor.py ✓ UPDATE (add domains relationship)
|
||||
└── vendor_domain.py ✓ Existing (database model)
|
||||
|
||||
Legend:
|
||||
★ NEW - Files to create
|
||||
✓ Existing - Files already exist
|
||||
✓ UPDATE - Files to modify
|
||||
```
|
||||
|
||||
## Separation of Concerns Visual
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ENDPOINT LAYER │
|
||||
│ - HTTP request/response │
|
||||
│ - FastAPI decorators │
|
||||
│ - Dependency injection │
|
||||
│ - Response models │
|
||||
│ - Documentation │
|
||||
│ │
|
||||
│ ✓ No business logic │
|
||||
│ ✓ No database operations │
|
||||
│ ✓ No validation (handled by Pydantic) │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ Calls
|
||||
│
|
||||
┌──────────────────────▼──────────────────────────────────────┐
|
||||
│ SERVICE LAYER │
|
||||
│ - Business logic │
|
||||
│ - Database operations │
|
||||
│ - Transaction management │
|
||||
│ - Error handling │
|
||||
│ - Validation logic │
|
||||
│ - Logging │
|
||||
│ │
|
||||
│ ✓ Reusable methods │
|
||||
│ ✓ Unit testable │
|
||||
│ ✓ No HTTP concerns │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
│ Uses
|
||||
│
|
||||
┌──────────────────────▼──────────────────────────────────────┐
|
||||
│ DATABASE LAYER │
|
||||
│ - SQLAlchemy models │
|
||||
│ - Table definitions │
|
||||
│ - Relationships │
|
||||
│ - Database constraints │
|
||||
│ │
|
||||
│ ✓ Pure data models │
|
||||
│ ✓ No business logic │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
This architecture ensures:
|
||||
- ✅ Clean separation of concerns
|
||||
- ✅ Easy to test each layer
|
||||
- ✅ Reusable business logic
|
||||
- ✅ Maintainable codebase
|
||||
- ✅ Follows SOLID principles
|
||||
Reference in New Issue
Block a user