from functools import lru_cache from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore", case_sensitive=False) database_url: str = Field( default="postgresql+psycopg://goon:goon@localhost:5432/goon", validation_alias="DATABASE_URL", ) tpdb_api_token: str | None = Field(default=None, validation_alias="TPDB_API_TOKEN") tpdb_base_url: str = Field( default="https://api.theporndb.net", validation_alias="TPDB_BASE_URL" ) stashdb_api_key: str | None = Field(default=None, validation_alias="STASHDB_API_KEY") stashdb_graphql_url: str = Field( default="https://stashdb.org/graphql", validation_alias="STASHDB_GRAPHQL_URL" ) log_level: str = Field(default="INFO", validation_alias="LOG_LEVEL") # Sentry observability — pusty DSN = init no-op (devel/local). Cloud free tier # 5k errors/mies wystarczy dla 1-user app. sentry_dsn: str | None = Field(default=None, validation_alias="SENTRY_DSN") sentry_environment: str = Field(default="dev", validation_alias="SENTRY_ENVIRONMENT") sentry_traces_sample_rate: float = Field( default=0.1, validation_alias="SENTRY_TRACES_SAMPLE_RATE" ) api_keys_raw: str = Field(default="", validation_alias="API_KEYS") """Lista API keys oddzielona przecinkami. Pusta = auth wyłączony (tylko dev/local).""" allowed_app_sig_hashes_raw: str = Field(default="", validation_alias="ALLOWED_APP_SIG_HASH") """Whitelist SHA256 (hex) podpisów APK akceptowane przez backend. Każdy request mobile wysyła `X-App-Signature` z hashem signing certu (PackageManager.GET_SIGNING_CERTIFICATES). Pusta = check wyłączony (dev/wstępny rollout). Lista = comma-separated lowercase hex. Re-packaging APK innym keystorem zmienia hash → 403.""" auto_merge_threshold: float = 0.92 review_threshold: float = 0.75 fingerprint_hamming_max: int = 5 title_token_set_min: int = 88 date_window_days: int = 7 # Skip ingestu clip-store (ManyVids/IWantClips/Clips4Sale/...) z canonical source — # to permanentne orphany (free tubes nie hostują), ~56% ingestu TPDB/StashDB. # False = wciągaj jak dawniej. Tube'y z clip-store studiem NIE są skipowane (mają playback). skip_clip_store: bool = Field(default=True, validation_alias="GOON_SKIP_CLIP_STORE") # Minimalny duration sceny z tube/scraper przy ingescie — str | None: """`host:port:user:pass` → `http://user:pass@host:port` dla curl_cffi/httpx. None gdy nieustawiony lub w złym formacie.""" parts = self.brightdata_proxy_raw.split(":") if len(parts) != 4 or not all(parts): return None host, port, user, pwd = parts return f"http://{user}:{pwd}@{host}:{port}" @property def api_keys(self) -> set[str]: return {k.strip() for k in self.api_keys_raw.split(",") if k.strip()} @property def auth_enabled(self) -> bool: return bool(self.api_keys) @property def allowed_app_sig_hashes(self) -> set[str]: return { h.strip().lower().replace(":", "") for h in self.allowed_app_sig_hashes_raw.split(",") if h.strip() } @property def app_sig_check_enabled(self) -> bool: return bool(self.allowed_app_sig_hashes) @lru_cache def get_settings() -> Settings: return Settings()