Files
orion/app/core/config.py
Samir Boulahtit 0d1007282a
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
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) <noreply@anthropic.com>
2026-03-29 21:43:36 +02:00

389 lines
14 KiB
Python

# app/core/config.py
"""
Application configuration using Pydantic Settings.
This module provides classes and functions for:
- Configuration management via environment variables
- Database settings
- JWT and authentication configuration
- Main domain and multi-tenancy settings
- Admin initialization settings
Note: Environment detection is handled by app.core.environment module.
This module focuses purely on configuration storage and validation.
"""
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""
Settings class for application configuration.
Environment detection is delegated to app.core.environment.
This class focuses on configuration values only.
"""
# =============================================================================
# PROJECT INFORMATION
# =============================================================================
project_name: str = "Orion - Multi-Store Marketplace Platform"
version: str = "2.2.0"
# Clean description without HTML
description: str = """
Marketplace product import and management system with multi-store support.
**Features:**
- JWT Authentication with role-based access
- Multi-marketplace product import (CSV processing)
- Inventory management across multiple locations
- Store management with individual configurations
**Documentation:** Visit /documentation for complete guides
**API Testing:** Use /docs for interactive API exploration
"""
# =============================================================================
# DATABASE (PostgreSQL only)
# =============================================================================
database_url: str = "postgresql://orion_user:secure_password@localhost:5432/orion_db"
# =============================================================================
# ADMIN INITIALIZATION (for init_production.py)
# =============================================================================
admin_email: str = "admin@orion.lu"
admin_username: str = "admin"
admin_password: str = "admin123" # CHANGE IN PRODUCTION!
admin_first_name: str = "Platform"
admin_last_name: str = "Administrator"
# =============================================================================
# JWT AUTHENTICATION
# =============================================================================
jwt_secret_key: str = "change-this-in-production"
jwt_expire_hours: int = 24
jwt_expire_minutes: int = 30
# =============================================================================
# API SERVER
# =============================================================================
api_host: str = "0.0.0.0"
api_port: int = 8000
debug: bool = True
# =============================================================================
# DOCUMENTATION
# =============================================================================
documentation_url: str = "http://localhost:8001"
# =============================================================================
# MIDDLEWARE & SECURITY
# =============================================================================
allowed_hosts: list[str] = ["*"] # Configure for production
# Rate Limiting
rate_limit_enabled: bool = True
rate_limit_requests: int = 100
rate_limit_window: int = 3600
# =============================================================================
# LOGGING
# =============================================================================
log_level: str = "INFO"
log_file: str | None = None
# =============================================================================
# MAIN DOMAIN CONFIGURATION
# =============================================================================
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
# SSL/TLS configuration for custom domains
ssl_provider: str = "letsencrypt" # or "cloudflare", "manual"
auto_provision_ssl: bool = False
# DNS verification
dns_verification_prefix: str = "_orion-verify"
dns_verification_ttl: int = 3600
# =============================================================================
# PLATFORM LIMITS
# =============================================================================
max_stores_per_user: int = 5
max_team_members_per_store: int = 50
invitation_expiry_days: int = 7
# =============================================================================
# STRIPE BILLING
# =============================================================================
stripe_secret_key: str = ""
stripe_publishable_key: str = ""
stripe_webhook_secret: str = ""
stripe_trial_days: int = 30 # 1-month free trial (card collected upfront but not charged)
# =============================================================================
# EMAIL CONFIGURATION
# =============================================================================
# Provider: smtp, sendgrid, mailgun, ses
email_provider: str = "smtp"
email_from_address: str = "noreply@orion.lu"
email_from_name: str = "Orion"
email_reply_to: str = "" # Optional reply-to address
# SMTP Settings (used when email_provider=smtp)
smtp_host: str = "localhost"
smtp_port: int = 587
smtp_user: str = ""
smtp_password: str = ""
smtp_use_tls: bool = True
smtp_use_ssl: bool = False # For port 465
# SendGrid (used when email_provider=sendgrid)
sendgrid_api_key: str = ""
# Mailgun (used when email_provider=mailgun)
mailgun_api_key: str = ""
mailgun_domain: str = ""
# Amazon SES (used when email_provider=ses)
aws_access_key_id: str = ""
aws_secret_access_key: str = ""
aws_region: str = "eu-west-1"
# Email behavior
email_enabled: bool = True # Set to False to disable all emails
email_debug: bool = False # Log emails instead of sending (for development)
# =============================================================================
# STOREFRONT DEFAULTS
# =============================================================================
# These can be overridden by AdminSetting in the database
default_storefront_locale: str = "fr-LU" # Currency/number formatting locale
default_currency: str = "EUR" # Default currency code
# =============================================================================
# DEMO/SEED DATA CONFIGURATION
# =============================================================================
# Controls for demo data seeding
seed_demo_stores: int = 3 # Number of demo stores to create
seed_customers_per_store: int = 15 # Customers per store
seed_products_per_store: int = 20 # Products per store
seed_orders_per_store: int = 10 # Orders per store
# =============================================================================
# CELERY / REDIS TASK QUEUE
# =============================================================================
# Redis URL for Celery broker and result backend
redis_url: str = "redis://localhost:6379/0"
# Feature flag: enable Celery for background tasks (False = use FastAPI BackgroundTasks)
use_celery: bool = False
# Flower monitoring dashboard
flower_url: str = "http://localhost:5555"
flower_password: str = "changeme" # CHANGE IN PRODUCTION!
# =============================================================================
# SENTRY ERROR TRACKING
# =============================================================================
sentry_dsn: str | None = None # Set to enable Sentry
sentry_environment: str = "development" # development, staging, production
sentry_traces_sample_rate: float = 0.1 # 10% of transactions for performance monitoring
# =============================================================================
# MONITORING
# =============================================================================
enable_metrics: bool = False
grafana_url: str = "https://grafana.wizard.lu"
grafana_admin_user: str = "admin"
grafana_admin_password: str = ""
# =============================================================================
# CLOUDFLARE R2 STORAGE
# =============================================================================
storage_backend: str = "local" # "local" or "r2"
r2_account_id: str | None = None
r2_access_key_id: str | None = None
r2_secret_access_key: str | None = None
r2_bucket_name: str = "orion-media"
r2_public_url: str | None = None # Custom domain for public access (e.g., https://media.yoursite.com)
# =============================================================================
# CLOUDFLARE CDN / PROXY
# =============================================================================
cloudflare_enabled: bool = False # Set to True when using CloudFlare proxy
# =============================================================================
# APPLE WALLET (LOYALTY MODULE)
# =============================================================================
loyalty_apple_pass_type_id: str | None = None
loyalty_apple_team_id: str | None = None
loyalty_apple_wwdr_cert_path: str | None = None
loyalty_apple_signer_cert_path: str | None = None
loyalty_apple_signer_key_path: str | None = None
model_config = {"env_file": ".env", "extra": "ignore"}
# Singleton settings instance
settings = Settings()
# =============================================================================
# ENVIRONMENT UTILITIES - Module-level functions
# =============================================================================
# Import environment detection utilities
from app.core.environment import (
get_environment,
is_development,
is_production,
is_staging,
should_use_secure_cookies,
)
def get_current_environment() -> str:
"""
Get current environment.
Convenience function that delegates to app.core.environment.
Use this when you need just the environment string.
"""
return get_environment()
def is_production_environment() -> bool:
"""
Check if running in production.
Convenience function that delegates to app.core.environment.
Use this for production-specific logic.
"""
return is_production()
def is_development_environment() -> bool:
"""
Check if running in development.
Convenience function that delegates to app.core.environment.
Use this for development-specific logic.
"""
return is_development()
def is_staging_environment() -> bool:
"""
Check if running in staging.
Convenience function that delegates to app.core.environment.
Use this for staging-specific logic.
"""
return is_staging()
# =============================================================================
# VALIDATION FUNCTIONS
# =============================================================================
def validate_database_url() -> None:
"""
Validate that database URL is PostgreSQL.
Raises:
ValueError: If database URL is not PostgreSQL
"""
if settings.database_url.startswith("sqlite"):
raise ValueError(
"SQLite is not supported. Please use PostgreSQL.\n"
"Set DATABASE_URL environment variable to a PostgreSQL connection string.\n"
"Example: postgresql://user:password@localhost:5432/dbname\n"
"For local development, run: docker-compose up -d db"
)
if not settings.database_url.startswith("postgresql"):
raise ValueError(
f"Unsupported database: {settings.database_url.split(':')[0]}\n"
"Only PostgreSQL is supported."
)
def validate_production_settings() -> list[str]:
"""
Validate settings for production environment.
Returns:
List of warning messages if configuration is insecure
"""
warnings = []
if is_production():
# Check for default/insecure values
if settings.admin_password == "admin123":
warnings.append("⚠️ Using default admin password in production!")
if settings.jwt_secret_key == "change-this-in-production":
warnings.append("⚠️ Using default JWT secret key in production!")
if settings.debug:
warnings.append("⚠️ Debug mode enabled in production!")
if "*" in settings.allowed_hosts:
warnings.append("⚠️ ALLOWED_HOSTS is set to wildcard (*) in production!")
return warnings
def print_environment_info():
"""Print current environment configuration."""
print("\n" + "=" * 70)
print(f" ENVIRONMENT: {get_environment().upper()}")
print("=" * 70)
print(f" Database: {settings.database_url}")
print(f" Debug mode: {settings.debug}")
print(f" API port: {settings.api_port}")
print(f" Platform: {settings.main_domain}")
print(f" Secure cookies: {should_use_secure_cookies()}")
print("=" * 70 + "\n")
# Show warnings if in production
if is_production():
warnings = validate_production_settings()
if warnings:
print("\n⚠️ PRODUCTION WARNINGS:")
for warning in warnings:
print(f" {warning}")
print()
# =============================================================================
# PUBLIC API
# =============================================================================
__all__ = [
# Settings singleton
"settings",
# Environment detection (re-exported from app.core.environment)
"get_environment",
"is_development",
"is_production",
"is_staging",
"should_use_secure_cookies",
# Convenience functions
"get_current_environment",
"is_production_environment",
"is_development_environment",
"is_staging_environment",
# Validation
"validate_database_url",
"validate_production_settings",
"print_environment_info",
]