Files
orion/docs/architecture/diagrams/multitenant-diagrams.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
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>
2026-02-14 16:46:56 +01:00

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):

  1. Customer visits customdomain1.com
  2. DNS resolves to your server
  3. Nginx accepts request
  4. FastAPI middleware queries store_domains table
  5. Finds store_id = 1
  6. Shows Store 1's shop