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:
@@ -67,10 +67,10 @@ LOG_LEVEL=INFO
|
||||
LOG_FILE=logs/app.log
|
||||
|
||||
# =============================================================================
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
# MAIN DOMAIN CONFIGURATION
|
||||
# =============================================================================
|
||||
# Your main platform domain
|
||||
PLATFORM_DOMAIN=wizard.lu
|
||||
MAIN_DOMAIN=wizard.lu
|
||||
|
||||
# Custom domain features
|
||||
# Enable/disable custom domains
|
||||
|
||||
@@ -6,7 +6,7 @@ This module provides classes and functions for:
|
||||
- Configuration management via environment variables
|
||||
- Database settings
|
||||
- JWT and authentication configuration
|
||||
- Platform domain and multi-tenancy settings
|
||||
- Main domain and multi-tenancy settings
|
||||
- Admin initialization settings
|
||||
|
||||
Note: Environment detection is handled by app.core.environment module.
|
||||
@@ -94,9 +94,9 @@ class Settings(BaseSettings):
|
||||
log_file: str | None = None
|
||||
|
||||
# =============================================================================
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
# MAIN DOMAIN CONFIGURATION
|
||||
# =============================================================================
|
||||
platform_domain: str = "wizard.lu"
|
||||
main_domain: str = "wizard.lu"
|
||||
|
||||
# Custom domain features
|
||||
allow_custom_domains: bool = True
|
||||
@@ -353,7 +353,7 @@ def print_environment_info():
|
||||
print(f" Database: {settings.database_url}")
|
||||
print(f" Debug mode: {settings.debug}")
|
||||
print(f" API port: {settings.api_port}")
|
||||
print(f" Platform: {settings.platform_domain}")
|
||||
print(f" Platform: {settings.main_domain}")
|
||||
print(f" Secure cookies: {should_use_secure_cookies()}")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ def purchase_addon(
|
||||
store = billing_service.get_store(db, store_id)
|
||||
|
||||
# Build URLs
|
||||
base_url = f"https://{settings.platform_domain}"
|
||||
base_url = f"https://{settings.main_domain}"
|
||||
success_url = f"{base_url}/store/{store.store_code}/billing?addon_success=true"
|
||||
cancel_url = f"{base_url}/store/{store.store_code}/billing?addon_cancelled=true"
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ def create_checkout_session(
|
||||
|
||||
store_code = subscription_service.get_store_code(db, store_id)
|
||||
|
||||
base_url = f"https://{settings.platform_domain}"
|
||||
base_url = f"https://{settings.main_domain}"
|
||||
success_url = f"{base_url}/store/{store_code}/billing?success=true"
|
||||
cancel_url = f"{base_url}/store/{store_code}/billing?cancelled=true"
|
||||
|
||||
@@ -87,7 +87,7 @@ def create_portal_session(
|
||||
merchant_id, platform_id = subscription_service.resolve_store_to_merchant(db, store_id, current_user.token_platform_id)
|
||||
|
||||
store_code = subscription_service.get_store_code(db, store_id)
|
||||
return_url = f"https://{settings.platform_domain}/store/{store_code}/billing"
|
||||
return_url = f"https://{settings.main_domain}/store/{store_code}/billing"
|
||||
|
||||
result = billing_service.create_portal_session(db, merchant_id, platform_id, return_url)
|
||||
|
||||
|
||||
@@ -617,7 +617,7 @@ class SignupService:
|
||||
|
||||
# Build login URL
|
||||
login_url = (
|
||||
f"https://{settings.platform_domain}"
|
||||
f"https://{settings.main_domain}"
|
||||
f"/store/{store.store_code}/dashboard"
|
||||
)
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ def _build_base_context(
|
||||
"request": request,
|
||||
"platform": platform,
|
||||
"platform_name": settings.project_name,
|
||||
"platform_domain": settings.platform_domain,
|
||||
"main_domain": settings.main_domain,
|
||||
}
|
||||
|
||||
# Add i18n globals
|
||||
|
||||
@@ -335,19 +335,19 @@ class Store(Base, TimestampMixin):
|
||||
Domain Resolution Priority:
|
||||
1. Store-specific custom domain (StoreDomain) -> highest priority
|
||||
2. Merchant domain (MerchantDomain) -> inherited default
|
||||
3. Store subdomain ({store.subdomain}.{platform_domain}) -> fallback
|
||||
3. Store subdomain ({store.subdomain}.{main_domain}) -> fallback
|
||||
"""
|
||||
if self.primary_domain:
|
||||
return self.primary_domain
|
||||
if self.merchant and self.merchant.primary_domain:
|
||||
return self.merchant.primary_domain
|
||||
return f"{self.subdomain}.{settings.platform_domain}"
|
||||
return f"{self.subdomain}.{settings.main_domain}"
|
||||
|
||||
@property
|
||||
def all_domains(self):
|
||||
"""Get all active domains (subdomain + custom domains)."""
|
||||
domains = [
|
||||
f"{self.subdomain}.{settings.platform_domain}"
|
||||
f"{self.subdomain}.{settings.main_domain}"
|
||||
] # Start with the main subdomain
|
||||
for domain in self.domains:
|
||||
if domain.is_active:
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -37,57 +37,80 @@ class StoreContextManager:
|
||||
"""
|
||||
Detect store context from request.
|
||||
|
||||
Priority order:
|
||||
1. Custom domain (customdomain1.com)
|
||||
2. Subdomain (store1.platform.com)
|
||||
3. Path-based (/store/store1/ or /stores/store1/)
|
||||
|
||||
Uses platform_clean_path from PlatformContextMiddleware when available.
|
||||
This path has the platform prefix stripped (e.g., /oms/stores/foo → /stores/foo).
|
||||
Thin wrapper around _detect_store_from_host_and_path() that extracts
|
||||
host, path, and platform from the request object.
|
||||
|
||||
Returns dict with store info or None if not found.
|
||||
"""
|
||||
host = request.headers.get("host", "")
|
||||
# Use platform_clean_path if available (set by PlatformContextMiddleware)
|
||||
path = getattr(request.state, "platform_clean_path", None) or request.url.path
|
||||
platform = getattr(request.state, "platform", None)
|
||||
|
||||
return StoreContextManager._detect_store_from_host_and_path(host, path, platform)
|
||||
|
||||
@staticmethod
|
||||
def _detect_store_from_host_and_path(host: str, path: str, platform=None) -> dict | None:
|
||||
"""
|
||||
Core store detection logic from host and path.
|
||||
|
||||
Priority order:
|
||||
1. Custom domain (customdomain1.com) — skipped on platform domains
|
||||
2. Subdomain (store1.platform.com) — skipped on platform domains
|
||||
3. Path-based (/store/store1/ or /stores/store1/) — always runs as fallback
|
||||
|
||||
Args:
|
||||
host: The request host header (may include port)
|
||||
path: The clean path (platform prefix already stripped)
|
||||
platform: Optional platform object from middleware state
|
||||
|
||||
Returns dict with store info or None if not found.
|
||||
"""
|
||||
original_host = host
|
||||
|
||||
# Remove port from host if present (e.g., localhost:8000 -> localhost)
|
||||
if ":" in host:
|
||||
host = host.split(":")[0]
|
||||
|
||||
# Method 1: Custom domain detection (HIGHEST PRIORITY)
|
||||
# Check if this is a custom domain (not platform.com and not localhost)
|
||||
platform_domain = getattr(settings, "platform_domain", "platform.com")
|
||||
|
||||
# Skip custom domain detection if host is already a platform domain
|
||||
# Determine if host is the platform's own domain or a subdomain of it
|
||||
# (e.g. rewardflow.lu is the loyalty platform, not a store)
|
||||
platform = getattr(request.state, "platform", None)
|
||||
# (e.g. acme.rewardflow.lu is a subdomain OF the platform, not a custom domain)
|
||||
platform_own_domain = getattr(platform, "domain", None) if platform else None
|
||||
is_platform_domain = (
|
||||
platform and getattr(platform, "domain", None)
|
||||
and host == platform.domain
|
||||
platform_own_domain and host == platform_own_domain
|
||||
)
|
||||
is_subdomain_of_platform = (
|
||||
platform_own_domain
|
||||
and host != platform_own_domain
|
||||
and host.endswith(f".{platform_own_domain}")
|
||||
)
|
||||
|
||||
is_custom_domain = (
|
||||
host
|
||||
and not is_platform_domain
|
||||
and not host.endswith(f".{platform_domain}")
|
||||
and host != platform_domain
|
||||
and host
|
||||
not in ["localhost", "127.0.0.1", "admin.localhost", "admin.127.0.0.1"]
|
||||
and not host.startswith("admin.")
|
||||
)
|
||||
# Method 1: Custom domain detection (HIGHEST PRIORITY)
|
||||
# Skip if host is a platform domain or a subdomain of one
|
||||
if not is_platform_domain and not is_subdomain_of_platform:
|
||||
main_domain = getattr(settings, "main_domain", "platform.com")
|
||||
|
||||
if is_custom_domain:
|
||||
normalized_domain = StoreDomain.normalize_domain(host)
|
||||
return {
|
||||
"domain": normalized_domain,
|
||||
"detection_method": "custom_domain",
|
||||
"host": host,
|
||||
"original_host": request.headers.get("host", ""),
|
||||
}
|
||||
is_custom_domain = (
|
||||
host
|
||||
and not host.endswith(f".{main_domain}")
|
||||
and host != main_domain
|
||||
and host
|
||||
not in ["localhost", "127.0.0.1", "admin.localhost", "admin.127.0.0.1"]
|
||||
and not host.startswith("admin.")
|
||||
)
|
||||
|
||||
# Method 2: Subdomain detection (store1.platform.com)
|
||||
if "." in host:
|
||||
if is_custom_domain:
|
||||
normalized_domain = StoreDomain.normalize_domain(host)
|
||||
return {
|
||||
"domain": normalized_domain,
|
||||
"detection_method": "custom_domain",
|
||||
"host": host,
|
||||
"original_host": original_host,
|
||||
}
|
||||
|
||||
# Method 2: Subdomain detection (acme.rewardflow.lu → "acme")
|
||||
# Runs for subdomains of the platform domain, skipped for exact platform domain
|
||||
if not is_platform_domain and "." in host:
|
||||
parts = host.split(".")
|
||||
# Check if it's a valid subdomain (not www, admin, api)
|
||||
if len(parts) >= 2 and parts[0] not in ["www", "admin", "api"]:
|
||||
@@ -349,12 +372,12 @@ class StoreContextManager:
|
||||
|
||||
# Method 2: Subdomain detection from referer host
|
||||
# orion.platform.com → orion
|
||||
platform_domain = getattr(settings, "platform_domain", "platform.com")
|
||||
main_domain = getattr(settings, "main_domain", "platform.com")
|
||||
if "." in referer_host:
|
||||
parts = referer_host.split(".")
|
||||
if len(parts) >= 2 and parts[0] not in ["www", "admin", "api"]:
|
||||
# Check if it's a subdomain of platform domain
|
||||
if referer_host.endswith(f".{platform_domain}"):
|
||||
# Check if it's a subdomain of main domain
|
||||
if referer_host.endswith(f".{main_domain}"):
|
||||
subdomain = parts[0]
|
||||
logger.debug(
|
||||
f"[STORE] Extracted store from Referer subdomain: {subdomain}",
|
||||
@@ -374,8 +397,8 @@ class StoreContextManager:
|
||||
# custom-shop.com → custom-shop.com
|
||||
is_custom_domain = (
|
||||
referer_host
|
||||
and not referer_host.endswith(f".{platform_domain}")
|
||||
and referer_host != platform_domain
|
||||
and not referer_host.endswith(f".{main_domain}")
|
||||
and referer_host != main_domain
|
||||
and referer_host not in ["localhost", "127.0.0.1"]
|
||||
and not referer_host.startswith("admin.")
|
||||
)
|
||||
|
||||
@@ -390,14 +390,14 @@ def create_admin_settings(db: Session) -> int:
|
||||
},
|
||||
{
|
||||
"key": "platform_url",
|
||||
"value": f"https://{settings.platform_domain}",
|
||||
"value": f"https://{settings.main_domain}",
|
||||
"value_type": "string",
|
||||
"description": "Main platform URL",
|
||||
"is_public": True,
|
||||
},
|
||||
{
|
||||
"key": "support_email",
|
||||
"value": f"support@{settings.platform_domain}",
|
||||
"value": f"support@{settings.main_domain}",
|
||||
"value_type": "string",
|
||||
"description": "Platform support email",
|
||||
"is_public": True,
|
||||
|
||||
@@ -339,7 +339,7 @@ def validate_configuration(env_vars: dict) -> dict:
|
||||
# -------------------------------------------------------------------------
|
||||
# Platform Domain
|
||||
# -------------------------------------------------------------------------
|
||||
domain = env_vars.get("PLATFORM_DOMAIN", "orion.lu")
|
||||
domain = env_vars.get("MAIN_DOMAIN", "orion.lu")
|
||||
if domain != "orion.lu":
|
||||
results["domain"] = {
|
||||
"status": "ok",
|
||||
@@ -350,7 +350,7 @@ def validate_configuration(env_vars: dict) -> dict:
|
||||
results["domain"] = {
|
||||
"status": "warning",
|
||||
"message": "Using default domain",
|
||||
"items": ["Set PLATFORM_DOMAIN for your deployment"]
|
||||
"items": ["Set MAIN_DOMAIN for your deployment"]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -1445,7 +1445,7 @@ def print_summary(db: Session):
|
||||
print("\n🏪 Demo Stores:")
|
||||
for store in stores:
|
||||
print(f"\n {store.name} ({store.store_code})")
|
||||
print(f" Subdomain: {store.subdomain}.{settings.platform_domain}")
|
||||
print(f" Subdomain: {store.subdomain}.{settings.main_domain}")
|
||||
|
||||
# Show per-platform custom subdomains
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
@@ -235,19 +235,19 @@ def print_dev_urls(platforms, stores, store_domains, store_platform_map):
|
||||
|
||||
def print_prod_urls(platforms, stores, store_domains):
|
||||
"""Print all production URLs."""
|
||||
platform_domain = settings.platform_domain
|
||||
main_domain = settings.main_domain
|
||||
|
||||
print()
|
||||
print("PRODUCTION URLS")
|
||||
print(f"Platform domain: {platform_domain}")
|
||||
print(f"Platform domain: {main_domain}")
|
||||
print(SEPARATOR)
|
||||
|
||||
# Admin
|
||||
print()
|
||||
print(" ADMIN PANEL")
|
||||
print(f" Login: https://admin.{platform_domain}/admin/login")
|
||||
print(f" Dashboard: https://admin.{platform_domain}/admin/")
|
||||
print(f" API: https://admin.{platform_domain}/api/v1/admin/")
|
||||
print(f" Login: https://admin.{main_domain}/admin/login")
|
||||
print(f" Dashboard: https://admin.{main_domain}/admin/")
|
||||
print(f" API: https://admin.{main_domain}/api/v1/admin/")
|
||||
|
||||
# Platforms
|
||||
print()
|
||||
@@ -259,10 +259,10 @@ def print_prod_urls(platforms, stores, store_domains):
|
||||
print(f" Home: https://{p.domain}/")
|
||||
elif p.code == "main":
|
||||
print(f" {p.name}{tag}")
|
||||
print(f" Home: https://{platform_domain}/")
|
||||
print(f" Home: https://{main_domain}/")
|
||||
else:
|
||||
print(f" {p.name} ({p.code}){tag}")
|
||||
print(f" Home: https://{p.code}.{platform_domain}/")
|
||||
print(f" Home: https://{p.code}.{main_domain}/")
|
||||
|
||||
# Group domains by store
|
||||
domains_by_store = {}
|
||||
@@ -280,7 +280,7 @@ def print_prod_urls(platforms, stores, store_domains):
|
||||
|
||||
tag = f" [{status_badge(v.is_active)}]" if not v.is_active else ""
|
||||
print(f" {v.name} ({v.store_code}){tag}")
|
||||
print(f" Dashboard: https://{v.subdomain}.{platform_domain}/store/{v.store_code}/")
|
||||
print(f" Dashboard: https://{v.subdomain}.{main_domain}/store/{v.store_code}/")
|
||||
|
||||
# Storefronts
|
||||
print()
|
||||
@@ -295,7 +295,7 @@ def print_prod_urls(platforms, stores, store_domains):
|
||||
print(f" {v.name} ({v.store_code}){tag}")
|
||||
|
||||
# Subdomain URL
|
||||
print(f" Subdomain: https://{v.subdomain}.{platform_domain}/")
|
||||
print(f" Subdomain: https://{v.subdomain}.{main_domain}/")
|
||||
|
||||
# Custom domains
|
||||
vd_list = domains_by_store.get(v.id, [])
|
||||
|
||||
@@ -63,7 +63,7 @@ if [ "$MODE" = "dev" ]; then
|
||||
fail ".env file not found — copy from .env.example"
|
||||
fi
|
||||
|
||||
REQUIRED_KEYS="DATABASE_URL REDIS_URL JWT_SECRET_KEY ADMIN_EMAIL PLATFORM_DOMAIN"
|
||||
REQUIRED_KEYS="DATABASE_URL REDIS_URL JWT_SECRET_KEY ADMIN_EMAIL MAIN_DOMAIN"
|
||||
for key in $REQUIRED_KEYS; do
|
||||
val=$(env_val "$key")
|
||||
if [ -n "$val" ]; then
|
||||
|
||||
@@ -37,7 +37,7 @@ def client(db):
|
||||
with patch("middleware.store_context.get_db", override_get_db):
|
||||
with patch("middleware.theme_context.get_db", override_get_db):
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
client = TestClient(app)
|
||||
yield client
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ def client(db):
|
||||
|
||||
This patches:
|
||||
1. get_db in both middleware modules to use the test database
|
||||
2. settings.platform_domain in store_context to use 'platform.com' for testing
|
||||
2. settings.main_domain in store_context to use 'platform.com' for testing
|
||||
|
||||
This ensures middleware can see test fixtures and detect subdomains correctly.
|
||||
"""
|
||||
@@ -64,7 +64,7 @@ def client(db):
|
||||
|
||||
# Patch get_db in middleware modules - they have their own imports
|
||||
# The middleware calls: db_gen = get_db(); db = next(db_gen)
|
||||
# Also patch settings.platform_domain so subdomain detection works with test hosts
|
||||
# Also patch settings.main_domain so subdomain detection works with test hosts
|
||||
# Also bypass StorefrontAccessMiddleware subscription check for test routes —
|
||||
# these tests verify store context detection, not subscription access.
|
||||
from middleware.storefront_access import SKIP_PATH_PREFIXES
|
||||
@@ -76,7 +76,7 @@ def client(db):
|
||||
with patch("middleware.theme_context.get_db", override_get_db):
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
with patch("middleware.storefront_access.SKIP_PATH_PREFIXES", test_skip_prefixes):
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
client = TestClient(app)
|
||||
yield client
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ async def test_inactive_store_detection(request: Request):
|
||||
|
||||
|
||||
@router.get("/platform-domain")
|
||||
async def test_platform_domain(request: Request):
|
||||
async def test_main_domain(request: Request):
|
||||
"""Test platform domain without subdomain."""
|
||||
store = getattr(request.state, "store", None)
|
||||
return {"store_detected": store is not None}
|
||||
|
||||
@@ -7,7 +7,7 @@ for all routing modes: subdomain, custom domain, and path-based.
|
||||
|
||||
Note: These tests require the middleware conftest.py which patches:
|
||||
1. get_db in middleware modules to use the test database session
|
||||
2. settings.platform_domain to use 'platform.com' for testing subdomain detection
|
||||
2. settings.main_domain to use 'platform.com' for testing subdomain detection
|
||||
"""
|
||||
|
||||
import pytest
|
||||
@@ -137,7 +137,7 @@ class TestStoreContextFlow:
|
||||
data = response.json()
|
||||
assert data["store_detected"] is False
|
||||
|
||||
def test_platform_domain_without_subdomain_no_store(self, client):
|
||||
def test_main_domain_without_subdomain_no_store(self, client):
|
||||
"""Test that platform domain without subdomain doesn't detect store."""
|
||||
response = client.get(
|
||||
"/middleware-test/platform-domain", headers={"host": "platform.com"}
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestStoreContextManager:
|
||||
request.url = Mock(path="/")
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestStoreContextManager:
|
||||
request.url = Mock(path="/")
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -75,7 +75,7 @@ class TestStoreContextManager:
|
||||
request.url = Mock(path="/")
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -91,7 +91,7 @@ class TestStoreContextManager:
|
||||
request.url = Mock(path="/")
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -155,7 +155,7 @@ class TestStoreContextManager:
|
||||
request.state.platform_clean_path = None
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -170,7 +170,7 @@ class TestStoreContextManager:
|
||||
request.state.platform_clean_path = None
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -185,7 +185,7 @@ class TestStoreContextManager:
|
||||
request.state.platform_clean_path = None
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -200,7 +200,7 @@ class TestStoreContextManager:
|
||||
request.state.platform_clean_path = None
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
@@ -441,7 +441,7 @@ class TestStoreContextManager:
|
||||
request.headers = {"referer": "http://orion.platform.com/storefront/products"} # noqa: SEC034
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.extract_store_from_referer(request)
|
||||
|
||||
@@ -456,7 +456,7 @@ class TestStoreContextManager:
|
||||
request.headers = {"referer": "http://my-custom-shop.com/storefront/products"} # noqa: SEC034
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.extract_store_from_referer(request)
|
||||
|
||||
@@ -490,7 +490,7 @@ class TestStoreContextManager:
|
||||
request.headers = {"referer": "http://admin.platform.com/dashboard"} # noqa: SEC034
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.extract_store_from_referer(request)
|
||||
|
||||
@@ -503,7 +503,7 @@ class TestStoreContextManager:
|
||||
request.headers = {"referer": "http://www.platform.com/storefront"} # noqa: SEC034
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.extract_store_from_referer(request)
|
||||
|
||||
@@ -515,7 +515,7 @@ class TestStoreContextManager:
|
||||
request.headers = {"referer": "http://localhost:8000/storefront"}
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.extract_store_from_referer(request)
|
||||
|
||||
@@ -996,7 +996,7 @@ class TestEdgeCases:
|
||||
request.url = Mock(path="/")
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "platform.com"
|
||||
mock_settings.main_domain = "platform.com"
|
||||
|
||||
context = StoreContextManager.detect_store_context(request)
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ class TestPlatformInjectionIntoStoreContext:
|
||||
mock_request.url = Mock(path="/store/login")
|
||||
|
||||
with patch("middleware.store_context.settings") as mock_settings:
|
||||
mock_settings.platform_domain = "omsflow.lu"
|
||||
mock_settings.main_domain = "omsflow.lu"
|
||||
|
||||
context = StoreContextManager.detect_store_context(mock_request)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user