from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") DATABASE_URL: str = "sqlite:///./quiz.db" SECRET_KEY: str = "change-me-to-a-random-secret-key-in-production" ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440 REDIS_URL: str = "redis://localhost:6379/0" LITELLM_MODEL: str = "gpt-4o-mini" LITELLM_API_KEY: str = "" LITELLM_API_BASE: str = "" LITELLM_EMBEDDING_MODEL: str = "" # Cross-encoder reranker, named as the proxy serves it. Blank turns # reranking off and leaves every result list in the order rank fusion # produced, which is what a deployment without this proxy gets. LITELLM_RERANK_MODEL: str = "cohere-rerank-v4.0-pro" OPENAI_API_KEY: str = "" ELEVENLABS_API_KEY: str = "" GOOGLE_TTS_API_KEY: str = "" LOCAL_SPEECH_GATEWAY_URL: str = "http://127.0.0.1:8110" AWS_ACCESS_KEY_ID: str = "" AWS_SECRET_ACCESS_KEY: str = "" AWS_REGION: str = "us-east-1" AWS_BEDROCK_REGION: str = "us-east-1" EMBEDDING_DIMENSIONS: int = 1024 APP_URL: str = "https://quiz.danvics.com" CHROMA_PERSIST_DIR: str = "./chroma_data" MAIL_USERNAME: str = "" MAIL_PASSWORD: str = "" MAIL_FROM: str = "" MAIL_PORT: int = 587 MAIL_SERVER: str = "smtp.gmail.com" MAIL_STARTTLS: bool = True MAIL_SSL_TLS: bool = False UPLOAD_DIR: str = "./uploads" # local | s3. Reads fall back to the volume either way, so existing uploads # keep working and files can migrate gradually. STORAGE_BACKEND: str = "local" S3_ENDPOINT_URL: str = "http://minio:9000" S3_ACCESS_KEY: str = "" S3_SECRET_KEY: str = "" S3_BUCKET: str = "pedshub-media" S3_REGION: str = "us-east-1" MAX_UPLOAD_SIZE: int = 524288000 # 500MB # hCaptcha. Leave the secret blank to disable the challenge entirely. # Cap, self-hosted beside us. The secret verifies a solve and never leaves # the server; the site key is public and names the widget's endpoint. CAP_API_URL: str = "http://cap:3000" CAP_SECRET_KEY: str = "" # The browser gets its own copy of the site key from the frontend # container, which is a separate image with a separate .env. This one is # here so an operator can keep both halves of the pair together and see # at a glance which widget the secret belongs to. CAP_SITE_KEY: str = "" ADMIN_EMAIL: str = "" # Where contact form submissions are emailed DEFAULT_ADMIN_EMAIL: str = "" # Optional explicit bootstrap admin email DEFAULT_ADMIN_PASSWORD: str = "" # Optional explicit bootstrap admin password BBB_SERVER_URL: str = "" # BigBlueButton server URL (e.g. https://bbb.example.com/bigbluebutton) BBB_SECRET: str = "" # BigBlueButton shared secret # OIDC / SSO — leave blank to disable OIDC_PROVIDER_URL: str = "" # e.g. https://accounts.google.com, https://login.microsoftonline.com/{tenant}/v2.0 OIDC_CLIENT_ID: str = "" OIDC_CLIENT_SECRET: str = "" OIDC_SCOPES: str = "openid email profile" # space-separated OIDC_PROVIDER_NAME: str = "SSO" # Display name on login button LOG_LEVEL: str = "INFO" # DEBUG, INFO, WARNING, ERROR # Clinical library index (Milvus on the ped-ai stack), read-only. Articles # are grounded in what it retrieves; nothing here writes to it. CLINICAL_MILVUS_URI: str = "" CLINICAL_MILVUS_TOKEN: str = "" CLINICAL_MILVUS_COLLECTION: str = "mcp_bge_m3_1024" settings = Settings()