From 0d1007282afe760a6348eef8f799ce843a2b271f Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 29 Mar 2026 21:43:36 +0200 Subject: [PATCH] feat(config): add APP_BASE_URL setting for outbound link construction Adds app_base_url config (default http://localhost:8000) used for all outbound URLs: invitation emails, billing checkout redirects, signup login links, portal return URLs. Replaces hardcoded https://{main_domain} and localhost:8000 patterns. Configurable per environment via APP_BASE_URL env var: - Dev: http://localhost:8000 (or http://acme.localhost:9999) - Prod: https://wizard.lu main_domain is preserved for subdomain resolution and cookie config. Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.example | 5 +++++ app/core/config.py | 5 +++++ app/modules/billing/routes/api/store_addons.py | 2 +- app/modules/billing/routes/api/store_checkout.py | 4 ++-- app/modules/billing/services/signup_service.py | 2 +- app/modules/tenancy/services/store_team_service.py | 13 ++----------- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 63bcb6a0..5ea14c4c 100644 --- a/.env.example +++ b/.env.example @@ -72,6 +72,11 @@ LOG_FILE=logs/app.log # Your main platform domain MAIN_DOMAIN=wizard.lu +# Full base URL for outbound links (emails, billing redirects, etc.) +# Must include protocol and port if non-standard +# Examples: http://localhost:8000, http://acme.localhost:9999, https://wizard.lu +APP_BASE_URL=http://localhost:8000 + # Custom domain features # Enable/disable custom domains ALLOW_CUSTOM_DOMAINS=True diff --git a/app/core/config.py b/app/core/config.py index 56d9dae4..4128a1da 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -98,6 +98,11 @@ class Settings(BaseSettings): # ============================================================================= main_domain: str = "wizard.lu" + # Full base URL for outbound links (emails, redirects, etc.) + # Must include protocol and port if non-standard. + # Examples: http://localhost:8000, http://acme.localhost:9999, https://wizard.lu + app_base_url: str = "http://localhost:8000" + # Custom domain features allow_custom_domains: bool = True require_domain_verification: bool = True diff --git a/app/modules/billing/routes/api/store_addons.py b/app/modules/billing/routes/api/store_addons.py index 1ea08bcc..4713f01b 100644 --- a/app/modules/billing/routes/api/store_addons.py +++ b/app/modules/billing/routes/api/store_addons.py @@ -144,7 +144,7 @@ def purchase_addon( store = billing_service.get_store(db, store_id) # Build URLs - base_url = f"https://{settings.main_domain}" + base_url = settings.app_base_url.rstrip("/") 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" diff --git a/app/modules/billing/routes/api/store_checkout.py b/app/modules/billing/routes/api/store_checkout.py index 5e86e104..f8b5c791 100644 --- a/app/modules/billing/routes/api/store_checkout.py +++ b/app/modules/billing/routes/api/store_checkout.py @@ -59,7 +59,7 @@ def create_checkout_session( store_code = subscription_service.get_store_code(db, store_id) - base_url = f"https://{settings.main_domain}" + base_url = settings.app_base_url.rstrip("/") 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.main_domain}/store/{store_code}/billing" + return_url = f"{settings.app_base_url.rstrip('/')}/store/{store_code}/billing" result = billing_service.create_portal_session(db, merchant_id, platform_id, return_url) diff --git a/app/modules/billing/services/signup_service.py b/app/modules/billing/services/signup_service.py index dfe92e60..a1d86d86 100644 --- a/app/modules/billing/services/signup_service.py +++ b/app/modules/billing/services/signup_service.py @@ -617,7 +617,7 @@ class SignupService: # Build login URL login_url = ( - f"https://{settings.main_domain}" + f"{settings.app_base_url.rstrip('/')}" f"/store/{store.store_code}/dashboard" ) diff --git a/app/modules/tenancy/services/store_team_service.py b/app/modules/tenancy/services/store_team_service.py index 991965f6..7f4c9ca2 100644 --- a/app/modules/tenancy/services/store_team_service.py +++ b/app/modules/tenancy/services/store_team_service.py @@ -981,19 +981,10 @@ class StoreTeamService: ): """Send team invitation email.""" from app.core.config import settings as app_settings - from app.core.environment import is_production from app.modules.messaging.services.email_service import EmailService - # Build acceptance URL - # Prod: https://{subdomain}.{main_domain}/invitation/accept?token=... - # Dev: http://localhost:8000/store/{store_code}/invitation/accept?token=... - main_domain = app_settings.main_domain.rstrip("/") - if is_production(): - base_url = f"https://{store.subdomain}.{main_domain}" - else: - base_url = f"http://localhost:8000/store/{store.store_code}" - - acceptance_link = f"{base_url}/invitation/accept?token={token}" + base_url = app_settings.app_base_url.rstrip("/") + acceptance_link = f"{base_url}/store/{store.store_code}/invitation/accept?token={token}" email_service = EmailService(db) email_service.send_template(