from pathlib import Path from pydantic import computed_field from pydantic_settings import BaseSettings, SettingsConfigDict # Repo-root .env (shared with the compose stack). Real environment variables # always take precedence over .env values. REPO_ROOT = Path(__file__).resolve().parent.parent.parent class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=REPO_ROOT / ".env", extra="ignore") version: str = "0.1.0" debug: bool = False # App database (SQLite file). Override via DATABASE_URL for other engines. # Must be a URL (not a bare path): db.py and Alembic hand it to # create_engine, which rejects bare paths. database_url: str = f"sqlite:///{REPO_ROOT / 'backend' / 'data' / 'kirche.db'}" # Canonical flow definitions (source of truth): one Langflow export per file. flows_dir: str = str(REPO_ROOT / "backend" / "flows") # Auto-reconcile backend/flows into the DB + LFX while the API is running. flows_watch_enabled: bool = True langflow_url: str = "http://localhost:7860" langflow_api_key: str = "" lfx_url: str = "http://localhost:6004" lfx_api_key: str = "" llm_api_key: str = "" # Base URL flows use for callbacks into this API. Must be reachable # FROM the LFX process (compose: http://api:8000). callback_base_url: str = "http://localhost:8000" # Shared secret that authenticates flow callbacks (POST /api/approvals). # The runner injects it into every run envelope; LFX-served flows send # it back as X-Callback-Token. Empty disables callbacks (503) — set a # long random value in prod (.env CALLBACK_TOKEN). callback_token: str = "" # Edge hardening (see app/middleware.py). max_body_bytes: int = 2 * 1024 * 1024 run_throttle_per_minute: int = 60 # --- access management (magic-link auth, see app/auth) --- # Public frontend URL (magic links point at /login?token= there). frontend_url: str = "http://localhost:3000" # Comma-separated emails that become admins (role assignment + bootstrap). admin_emails: str = "" # Reply-To on outgoing mail; also receives access-request notifications. contact_to: str = "" mail_from: str = "" smtp_host: str = "" smtp_port: int = 587 smtp_username: str = "" smtp_password: str = "" @computed_field # type: ignore[misc] @property def admin_email_list(self) -> list[str]: return [e.strip().lower() for e in self.admin_emails.split(",") if e.strip()] settings = Settings()