from functools import lru_cache from pathlib import Path from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict ROOT_DIR = Path(__file__).resolve().parents[1] VERSION_FILE = ROOT_DIR / "VERSION" def load_default_app_version() -> str: try: value = VERSION_FILE.read_text(encoding="utf-8").strip() except FileNotFoundError: return "1.0.0" return value or "1.0.0" class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore") app_env: str = Field(default="development", alias="APP_ENV") port: int = Field(default=8000, alias="PORT") db_url: str = Field(default="sqlite:///./data/stundentracker.db", alias="DB_URL") session_secret: str = Field(default="change-this-in-production", alias="SESSION_SECRET") cookie_secure: bool = Field(default=False, alias="COOKIE_SECURE") cookie_samesite: str = Field(default="lax", alias="COOKIE_SAMESITE") login_rate_limit_attempts: int = Field(default=5, alias="LOGIN_RATE_LIMIT_ATTEMPTS") login_rate_limit_window_minutes: int = Field(default=15, alias="LOGIN_RATE_LIMIT_WINDOW_MINUTES") data_encryption_key: str | None = Field(default=None, alias="DATA_ENCRYPTION_KEY") password_reset_token_ttl_minutes: int = Field(default=60, alias="PASSWORD_RESET_TOKEN_TTL_MINUTES") mfa_code_ttl_minutes: int = Field(default=10, alias="MFA_CODE_TTL_MINUTES") mfa_pending_ttl_minutes: int = Field(default=10, alias="MFA_PENDING_TTL_MINUTES") smtp_timeout_seconds: int = Field(default=15, alias="SMTP_TIMEOUT_SECONDS") registration_notify_email: str = Field(default="admin@example.com", alias="REGISTRATION_NOTIFY_EMAIL") app_name: str = Field(default="Stundenfuchs", alias="APP_NAME") app_title: str | None = Field(default=None, alias="APP_TITLE") app_version: str = Field(default=load_default_app_version(), alias="APP_VERSION") email_verification_required: bool = Field(default=True, alias="EMAIL_VERIFICATION_REQUIRED") email_verification_token_ttl_minutes: int = Field(default=60 * 24, alias="EMAIL_VERIFICATION_TOKEN_TTL_MINUTES") bootstrap_admin_email: str | None = Field(default=None, alias="BOOTSTRAP_ADMIN_EMAIL") forwarded_allow_ips: str = Field(default="127.0.0.1,::1", alias="FORWARDED_ALLOW_IPS") @property def is_production(self) -> bool: return self.app_env.lower() == "production" @property def resolved_app_title(self) -> str: value = (self.app_title or "").strip() if value: return value return self.app_name @lru_cache(maxsize=1) def get_settings() -> Settings: return Settings()