from pydantic_settings import BaseSettings, SettingsConfigDict from typing import Optional class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") # App APP_NAME: str = "企迹-政企周报管理系统" ENVIRONMENT: str = "production" DEBUG: bool = False SECRET_KEY: str = "" # Database # A syntactically valid non-working placeholder keeps tooling imports side-effect free. DATABASE_URL: str = "postgresql+asyncpg://qiji_app:invalid@localhost:5432/qiji" # JWT JWT_ALGORITHM: str = "HS256" JWT_EXPIRE_MINUTES: int = 480 # Casdoor CASDOOR_ENDPOINT: str = "http://localhost:8001" CASDOOR_CLIENT_ID: str = "" CASDOOR_CLIENT_SECRET: str = "" CASDOOR_CERTIFICATE: Optional[str] = None CASDOOR_ORG_NAME: str = "qiji" CASDOOR_APPLICATION: str = "qiji-weekly-report" # MinIO MINIO_ENDPOINT: str = "" MINIO_ACCESS_KEY: str = "" MINIO_SECRET_KEY: str = "" MINIO_BUCKET: str = "qiji-photos" MINIO_SECURE: bool = True # WeChat Work WECOM_CORP_ID: str = "" WECOM_AGENT_ID: str = "" WECOM_SECRET: str = "" WECOM_TOKEN: str = "" WECOM_ENCODING_AES_KEY: str = "" WECOM_API_BASE: str = "https://qyapi.weixin.qq.com" # AI / LLM (OpenAI-compatible) AI_API_URL: str = "" AI_API_KEY: str = "" AI_MODEL: str = "gpt-4o" AI_MAX_TOKENS: int = 2000 # CORS CORS_ORIGINS: list[str] = ["http://localhost:5173", "http://localhost:3000"] settings = Settings() def validate_security_settings() -> None: """Reject unsafe or incomplete configuration before the application starts.""" if settings.ENVIRONMENT.lower() == "development": return invalid = [] if len(settings.SECRET_KEY) < 32 or settings.SECRET_KEY in {"change-me-in-production", ""}: invalid.append("SECRET_KEY") if not settings.DATABASE_URL or "postgres:postgres@" in settings.DATABASE_URL or ":invalid@" in settings.DATABASE_URL: invalid.append("DATABASE_URL") if not settings.MINIO_ENDPOINT or not settings.MINIO_ACCESS_KEY or not settings.MINIO_SECRET_KEY: invalid.append("MINIO configuration") if settings.MINIO_ACCESS_KEY == "minioadmin" or settings.MINIO_SECRET_KEY == "minioadmin": invalid.append("MINIO default credentials") if not settings.MINIO_SECURE: invalid.append("MINIO_SECURE") if invalid: raise RuntimeError(f"Unsafe production configuration: {', '.join(invalid)}")