refactor: rename platform_domain → main_domain to avoid confusion with platform.domain
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running

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:
2026-03-14 04:45:28 +01:00
parent 4a1f71a312
commit c2c0e3c740
26 changed files with 152 additions and 113 deletions

View File

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

View File

@@ -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 |

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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 |

View File

@@ -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
```