refactor: rename platform_domain → main_domain to avoid confusion with platform.domain
Some checks failed
Some checks failed
The setting `settings.platform_domain` (the global/main domain like "wizard.lu") was easily confused with `platform.domain` (per-platform domain like "rewardflow.lu"). Renamed to `settings.main_domain` / `MAIN_DOMAIN` env var across the entire codebase. Also updated docs to reflect the refactored store detection logic with `is_platform_domain` / `is_subdomain_of_platform` guards. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,12 +92,15 @@ INFO Response: 200 for GET /admin/dashboard (0.143s)
|
||||
**Purpose**: Detect which store's storefront the request is for (multi-tenant core)
|
||||
|
||||
**What it does**:
|
||||
- Detects store from:
|
||||
- Custom domain (e.g., `customdomain.com`) — via `StoreDomain` lookup (optionally scoped to a platform via `StoreDomain.platform_id`)
|
||||
- Subdomain with two-step lookup:
|
||||
1. `StorePlatform.custom_subdomain` — per-platform subdomain overrides (e.g., `wizatech-rewards.rewardflow.lu`)
|
||||
2. `Store.subdomain` — standard subdomain fallback (e.g., `wizatech.omsflow.lu`)
|
||||
- Path prefix (e.g., `/store/store1/` or `/stores/store1/`)
|
||||
- Guards based on platform domain awareness:
|
||||
- When `host == platform.domain` (e.g., `rewardflow.lu`): Methods 1 & 2 are skipped (the platform's own domain is not a store)
|
||||
- When host is a subdomain of `platform.domain` (e.g., `acme.rewardflow.lu`): Method 1 (custom domain) is skipped
|
||||
- Detects store from (in priority order):
|
||||
1. Custom domain (e.g., `customdomain.com`) — via `StoreDomain` lookup (skipped on platform domains)
|
||||
2. Subdomain with two-step lookup:
|
||||
- `StorePlatform.custom_subdomain` — per-platform subdomain overrides (e.g., `wizatech-rewards.rewardflow.lu`)
|
||||
- `Store.subdomain` — standard subdomain fallback (e.g., `wizatech.omsflow.lu`)
|
||||
3. Path prefix (e.g., `/store/store1/` or `/stores/store1/`) — always runs as fallback
|
||||
- Queries database to find store by domain or code
|
||||
- Injects store object and platform into `request.state.store`
|
||||
- Extracts "clean path" (path without store prefix)
|
||||
|
||||
@@ -563,7 +563,7 @@ Every context includes these base variables regardless of modules:
|
||||
| `request` | FastAPI Request object |
|
||||
| `platform` | Platform model (may be None) |
|
||||
| `platform_name` | From settings.project_name |
|
||||
| `platform_domain` | From settings.platform_domain |
|
||||
| `main_domain` | From settings.main_domain |
|
||||
| `_` | Translation function (gettext style) |
|
||||
| `t` | Translation function (key-value style) |
|
||||
| `current_language` | Current language code |
|
||||
|
||||
@@ -113,26 +113,37 @@ platform.com/storefront/store3 → Store 3 Storefront
|
||||
|
||||
### Store Detection Logic
|
||||
|
||||
The `StoreContextMiddleware` detects stores using this priority:
|
||||
The `StoreContextMiddleware` detects stores via `_detect_store_from_host_and_path()`.
|
||||
Before trying the three detection methods, two guards determine which methods apply:
|
||||
|
||||
- **`is_platform_domain`**: `host == platform.domain` (e.g., `rewardflow.lu` itself) — skips Methods 1 & 2
|
||||
- **`is_subdomain_of_platform`**: host is a subdomain of `platform.domain` (e.g., `acme.rewardflow.lu`) — skips Method 1
|
||||
|
||||
```python
|
||||
def detect_store(request):
|
||||
host = request.headers.get("host")
|
||||
def detect_store(host, path, platform):
|
||||
platform_own_domain = platform.domain # e.g. "rewardflow.lu"
|
||||
is_platform_domain = (host == platform_own_domain)
|
||||
is_subdomain_of_platform = (
|
||||
host != platform_own_domain
|
||||
and host.endswith(f".{platform_own_domain}")
|
||||
)
|
||||
|
||||
# 1. Try custom domain first
|
||||
store = find_by_custom_domain(host)
|
||||
if store:
|
||||
return store, "custom_domain"
|
||||
# 1. Custom domain — skipped when host is platform domain or subdomain of it
|
||||
if not is_platform_domain and not is_subdomain_of_platform:
|
||||
main_domain = settings.main_domain # e.g. "wizard.lu"
|
||||
if host != main_domain and not host.endswith(f".{main_domain}"):
|
||||
store = find_by_custom_domain(host)
|
||||
if store:
|
||||
return store, "custom_domain"
|
||||
|
||||
# 2. Try subdomain
|
||||
if host != settings.platform_domain:
|
||||
store_code = host.split('.')[0]
|
||||
store = find_by_code(store_code)
|
||||
# 2. Subdomain — skipped when host IS the platform domain
|
||||
if not is_platform_domain and "." in host:
|
||||
subdomain = host.split('.')[0]
|
||||
store = find_by_code(subdomain)
|
||||
if store:
|
||||
return store, "subdomain"
|
||||
|
||||
# 3. Try path-based
|
||||
path = request.url.path
|
||||
# 3. Path-based — always runs as fallback
|
||||
if path.startswith("/store/") or path.startswith("/storefront/"):
|
||||
store_code = extract_code_from_path(path)
|
||||
store = find_by_code(store_code)
|
||||
|
||||
@@ -78,6 +78,7 @@ logger.info(f"Request: GET /storefront/products from 192.168.1.100")
|
||||
|
||||
**What happens**:
|
||||
- Analyzes host header and path
|
||||
- Checks platform domain guards: if host matches `platform.domain`, Methods 1 & 2 are skipped; if host is a subdomain of `platform.domain`, Method 1 is skipped
|
||||
- Determines routing mode (custom domain / subdomain / path-based)
|
||||
- Queries database for store
|
||||
- Extracts clean path
|
||||
|
||||
@@ -43,9 +43,10 @@ The core authentication manager handling JWT tokens, password hashing, and role-
|
||||
Detects and manages store context from custom domains, subdomains, or path-based routing. This is the foundation of the multi-tenant system.
|
||||
|
||||
**Key Features:**
|
||||
- Platform domain awareness: skips custom domain / subdomain detection when host is the platform's own domain
|
||||
- Custom domain routing (customdomain.com → Store)
|
||||
- Subdomain routing (store1.platform.com → Store)
|
||||
- Path-based routing (/store/store1/ → Store)
|
||||
- Path-based routing (/store/store1/ → Store) — always runs as fallback
|
||||
- Clean path extraction for nested routing
|
||||
|
||||
::: middleware.store_context.StoreContextManager
|
||||
|
||||
@@ -123,7 +123,7 @@ Controls the base domain for store subdomains and custom-domain features.
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|---|---|---|---|
|
||||
| `PLATFORM_DOMAIN` | Root domain under which store subdomains are created | `wizard.lu` | No |
|
||||
| `MAIN_DOMAIN` | Root domain under which store subdomains are created | `wizard.lu` | No |
|
||||
| `ALLOW_CUSTOM_DOMAINS` | Allow stores to use their own domain names | `True` | No |
|
||||
| `REQUIRE_DOMAIN_VERIFICATION` | Require DNS verification before activating a custom domain | `True` | No |
|
||||
| `SSL_PROVIDER` | SSL certificate provider (`letsencrypt`, `cloudflare`, `manual`) | `letsencrypt` | No |
|
||||
|
||||
@@ -911,7 +911,7 @@ settings.invitation_expiry_days # int
|
||||
settings.database_url # str
|
||||
|
||||
# Platform
|
||||
settings.platform_domain # str
|
||||
settings.main_domain # str
|
||||
settings.project_name # str
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user