feat(config): add APP_BASE_URL setting for outbound link construction
Some checks failed
CI / ruff (push) Successful in 15s
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 been cancelled

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) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 21:43:36 +02:00
parent 2a15c14ee8
commit 0d1007282a
6 changed files with 16 additions and 15 deletions

View File

@@ -72,6 +72,11 @@ LOG_FILE=logs/app.log
# Your main platform domain # Your main platform domain
MAIN_DOMAIN=wizard.lu 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 # Custom domain features
# Enable/disable custom domains # Enable/disable custom domains
ALLOW_CUSTOM_DOMAINS=True ALLOW_CUSTOM_DOMAINS=True

View File

@@ -98,6 +98,11 @@ class Settings(BaseSettings):
# ============================================================================= # =============================================================================
main_domain: str = "wizard.lu" 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 # Custom domain features
allow_custom_domains: bool = True allow_custom_domains: bool = True
require_domain_verification: bool = True require_domain_verification: bool = True

View File

@@ -144,7 +144,7 @@ def purchase_addon(
store = billing_service.get_store(db, store_id) store = billing_service.get_store(db, store_id)
# Build URLs # 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" 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" cancel_url = f"{base_url}/store/{store.store_code}/billing?addon_cancelled=true"

View File

@@ -59,7 +59,7 @@ def create_checkout_session(
store_code = subscription_service.get_store_code(db, store_id) 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" success_url = f"{base_url}/store/{store_code}/billing?success=true"
cancel_url = f"{base_url}/store/{store_code}/billing?cancelled=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) 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) 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) result = billing_service.create_portal_session(db, merchant_id, platform_id, return_url)

View File

@@ -617,7 +617,7 @@ class SignupService:
# Build login URL # Build login URL
login_url = ( login_url = (
f"https://{settings.main_domain}" f"{settings.app_base_url.rstrip('/')}"
f"/store/{store.store_code}/dashboard" f"/store/{store.store_code}/dashboard"
) )

View File

@@ -981,19 +981,10 @@ class StoreTeamService:
): ):
"""Send team invitation email.""" """Send team invitation email."""
from app.core.config import settings as app_settings 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 from app.modules.messaging.services.email_service import EmailService
# Build acceptance URL base_url = app_settings.app_base_url.rstrip("/")
# Prod: https://{subdomain}.{main_domain}/invitation/accept?token=... acceptance_link = f"{base_url}/store/{store.store_code}/invitation/accept?token={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}"
email_service = EmailService(db) email_service = EmailService(db)
email_service.send_template( email_service.send_template(