from pydantic import Field, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): deployment_environment: str = "development" database_url: str = "postgresql+psycopg://rf4:rf4_local@localhost:5432/rf4_spotter" admin_token: str = "change-me-in-production" s3_endpoint_url: str = "http://localhost:9000" s3_public_endpoint_url: str = "http://localhost:9000" s3_access_key: str = "rf4-local" s3_secret_key: str = "rf4-local-secret" s3_bucket: str = "catch-screenshots" screenshot_max_bytes: int = 8 * 1024 * 1024 official_records_url: str = "https://rf4game.de/records/region/RU/" official_records_region: str = "RU" official_records_category: str = "records" official_import_required: bool = False seed_demo_data: bool = True import_interval_seconds: int = Field(default=3600, ge=3600) rate_limit_secret: str = "change-rate-limit-secret" log_level: str = "INFO" cors_origins: list[str] = Field(default_factory=lambda: ["http://localhost:4321", "http://127.0.0.1:4321"]) model_config = SettingsConfigDict(env_file=".env", extra="ignore") @model_validator(mode="after") def reject_insecure_production_defaults(self) -> "Settings": if self.deployment_environment != "production": return self insecure = { "ADMIN_TOKEN": self.admin_token == "change-me-in-production" or len(self.admin_token) < 32, "RATE_LIMIT_SECRET": self.rate_limit_secret == "change-rate-limit-secret" or len(self.rate_limit_secret) < 32, "S3_ACCESS_KEY": self.s3_access_key == "rf4-local" or len(self.s3_access_key) < 12, "S3_SECRET_KEY": self.s3_secret_key == "rf4-local-secret" or len(self.s3_secret_key) < 32, } invalid = [name for name, failed in insecure.items() if failed] if invalid: raise ValueError(f"insecure production settings: {', '.join(invalid)}") if not self.cors_origins or any(not origin.startswith("https://") for origin in self.cors_origins): raise ValueError("production CORS_ORIGINS must contain only HTTPS origins") if not self.s3_public_endpoint_url.startswith("https://"): raise ValueError("production S3_PUBLIC_ENDPOINT_URL must use HTTPS") if self.seed_demo_data: raise ValueError("SEED_DEMO_DATA must be false in production") return self settings = Settings()