"""FlowDeck — Configuration via pydantic-settings.""" from __future__ import annotations import os from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): @property def data_dir(self) -> str: """Racine des fichiers (avatars, uploads…). Pas un champ : la lecture est faite à chaque accès parce que les tests monkeypatchent `FLOWDECK_DATA_DIR` en cours de vie (A42 — les 9 copies de `Path(os.environ.get("FLOWDECK_DATA_DIR", "/data"))` vont ici). """ return os.environ.get("FLOWDECK_DATA_DIR", "/data") model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore" ) # Gitea gitea_url: str = "https://git.dracodev.net" gitea_token: str = "change-me" gitea_oauth_client_id: str = "test-id" gitea_oauth_client_secret: str = "test-secret" gitea_webhook_secret: str = "" # GitHub github_oauth_client_id: str = "" github_oauth_client_secret: str = "" # OAuth2 — empty = dynamic per-request redirect URI (Host + X-Forwarded-*), # set this ONLY to pin an exact URI (must be registered in Gitea/GitHub) oauth_redirect_uri: str = "" # Webhook webhook_base_url: str = "http://localhost:8080" # App app_secret_key: str = "change-me-to-random" app_host: str = "0.0.0.0" app_port: int = 8080 log_level: str = "INFO" default_lang: str = "fr" # Rate limiting rate_limit_enabled: bool = True rate_limit_requests: int = 60 # per minute # Public API v2 (v6.3.0) public_api_insecure_ok: bool = False # if True, fd-public-key is accepted (dev only) api_v2_rate_limit_per_token: int = 300 # req/min per token for /api/v2 # Database database_url: str = "sqlite:////data/flowdeck.db" # Sync sync_interval: int = 60 gitea_cache_ttl: int = 30 # Backup (v5.2.0) — scheduled daily snapshot of the SQLite file backup_enabled: bool = True backup_dir: str = "/data/backups" backup_interval_hours: int = 24 backup_keep: int = 30 # Forge projects sync (v5.2.0) — periodic refresh of `projects` table project_sync_enabled: bool = True project_sync_interval_hours: int = 1 # Reminders (v5.8.0) — background scan for due date reminders reminders_enabled: bool = True reminder_scan_interval_seconds: int = 60 # Webhooks outbound (v6.4.0) — retry of failed deliveries webhook_retry_enabled: bool = True webhook_retry_interval_seconds: int = 60 # Email / SMTP notifications (v4.9.0) — optional. If smtp_host is empty, # email notifications are skipped (only in-app notifications are delivered). smtp_host: str = "" smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" smtp_from: str = "FlowDeck " smtp_use_tls: bool = True app_base_url: str = "http://localhost:8080" # SSO / SAML + OIDC (v6.7.0) — bootstrap fallback ONLY: as soon as an admin # saves a configuration in Settings → Admin → SSO / Enterprise, the # `sso_config` table wins (see app/services/sso_provisioning.py). sso_provider: str = "" # 'saml' | 'oidc' | '' (disabled) sso_name: str = "Company SSO" # button label on the login page sso_entity_id: str = "" # SAML: IdP entity id sso_sso_url: str = "" # SAML: IdP SSO URL (HTTP-Redirect) sso_slo_url: str = "" # SAML: IdP Single Logout URL sso_x509_certificate: str = "" # SAML: IdP signing certificate (PEM) sso_issuer_url: str = "" # OIDC: issuer identifier sso_client_id: str = "" # OIDC: client id sso_client_secret: str = "" # OIDC: client secret (env only) sso_scope: str = "openid profile email" sso_attribute_mapping: str = "" # JSON, defaults per provider sso_groups_mapping: str = "[]" # JSON [{sso_group, workspace_role, workspace_id}] sso_auto_provision: bool = True sso_only: bool = False # refuse local login when true sso_sign_requests: bool = False # sign AuthnRequest / LogoutRequest sso_default_workspace_id: int = 0 # FlowDeck Agent (v4.10.0) — multi-LLM. Empty keys → offline/mock mode # (deterministic rule-based planner so the agent works without any API key). agent_enabled: bool = True llm_provider: str = "offline" # any id from llm_client.PROVIDERS # (openai, anthropic, mistral, cohere, # google, groq, deepseek, openrouter, # nvidia, together, perplexity, xai, # qwencloud, minimax, morph, fireworks, # cerebras, sambanova, chutes, xiaomi, # sealion, sensenova, ollama, offline) llm_model: str = "gpt-4o" llm_api_key: str = "" llm_api_base: str = "" # custom base URL (Ollama, OpenRouter, ...) agent_max_iterations: int = 12 agent_max_tokens_budget: int = 500000 agent_run_timeout_seconds: int = 300 @property def db_path(self) -> Path: if self.database_url == "sqlite:///:memory:": return Path(":memory:") # Special SQLite in-memory if self.database_url.startswith("sqlite:///"): p = self.database_url.replace("sqlite:///", "", 1) # On Windows, don't prepend / if path starts with a drive letter import re if re.match(r'^[a-zA-Z]:', p): return Path(p) # A26 : `sqlite:////data/flowdeck.db` donne p='/data/…' — un simple # concat '/'+'/' → '//data/…' = chemin UNC sous Windows. On normalise. return Path("/" + p.lstrip("/")) return Path("/data/flowdeck.db") settings = Settings()