Merge feature/config-db-4c

Move youtube_api_key into the DB-backed system_config (encrypted). Google OAuth
creds + admin-role UI deferred to the wizard/auth epics (see commit body).
This commit is contained in:
npeter83 2026-06-19 13:19:06 +02:00
commit b8223c5741
6 changed files with 16 additions and 7 deletions

View file

@ -48,6 +48,8 @@ SPECS: tuple[ConfigSpec, ...] = (
# --- Batch sizes ---
ConfigSpec("enrich_batch_size", "int", "batch", "enrich_batch_size", min=1, max=50),
ConfigSpec("autotag_title_sample", "int", "batch", "autotag_title_sample", min=1, max=1_000),
# --- YouTube Data API key (secret; optional — public reads prefer it over OAuth) ---
ConfigSpec("youtube_api_key", "str", "youtube", "youtube_api_key", secret=True),
)
_BY_KEY: dict[str, ConfigSpec] = {s.key: s for s in SPECS}

View file

@ -11,7 +11,7 @@ from datetime import datetime, timedelta, timezone
import httpx
from app import quota
from app import quota, sysconfig
from app.config import settings
from app.models import User
from app.security import decrypt
@ -86,8 +86,9 @@ class YouTubeClient:
def _get(self, path: str, params: dict, cost: int = 1, allow_key: bool = True) -> dict:
p = dict(params)
headers = {}
if allow_key and settings.youtube_api_key:
p["key"] = settings.youtube_api_key
api_key = sysconfig.get_str(self.db, "youtube_api_key")
if allow_key and api_key:
p["key"] = api_key
else:
headers["Authorization"] = f"Bearer {self._access_token()}"
resp = self._http.get(f"{API_BASE}/{path}", params=p, headers=headers)

View file

@ -11,7 +11,7 @@ import Tooltip from "./Tooltip";
// applied together via one Save/Discard bar (consistent with the Settings page); nothing hits
// the server until Save. Group order is fixed where known so logically related settings (e.g.
// Email/SMTP) stay together.
const GROUP_ORDER = ["email", "quota", "backfill", "shorts", "batch"];
const GROUP_ORDER = ["email", "youtube", "quota", "backfill", "shorts", "batch"];
type SaveState = "idle" | "saving" | "saved" | "error";
export default function ConfigPanel() {

View file

@ -3,6 +3,7 @@
"loading": "Konfiguration wird geladen…",
"groups": {
"email": "E-Mail / SMTP",
"youtube": "YouTube-API",
"quota": "Kontingent",
"backfill": "Nachladen (Backfill)",
"shorts": "Shorts-Prüfung",
@ -21,7 +22,8 @@
"shorts_probe_max_seconds": { "label": "Shorts-Prüfung — max. Dauer (s)", "hint": "Nur Videos bis zu dieser Länge werden als mögliche Shorts geprüft." },
"shorts_probe_batch": { "label": "Shorts-Prüfung — Stapelgröße", "hint": "Wie viele Videos pro Lauf klassifiziert werden." },
"enrich_batch_size": { "label": "Anreicherungs-Stapelgröße", "hint": "videos.list-IDs pro Aufruf (YouTube begrenzt auf 50)." },
"autotag_title_sample": { "label": "Auto-Tag-Titelstichprobe", "hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung." }
"autotag_title_sample": { "label": "Auto-Tag-Titelstichprobe", "hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung." },
"youtube_api_key": { "label": "YouTube-API-Schlüssel", "hint": "Optional. Für öffentliche Lesezugriffe (Kanäle/Videos), damit gemeinsames Nachladen nicht vom OAuth eines Nutzers abhängt. Verschlüsselt gespeichert; nur schreibbar." }
},
"save": "Speichern",
"saving": "Speichern…",

View file

@ -3,6 +3,7 @@
"loading": "Loading configuration…",
"groups": {
"email": "Email / SMTP",
"youtube": "YouTube API",
"quota": "Quota",
"backfill": "Backfill",
"shorts": "Shorts probe",
@ -21,7 +22,8 @@
"shorts_probe_max_seconds": { "label": "Shorts probe — max duration (s)", "hint": "Only videos at or below this length are probed as possible Shorts." },
"shorts_probe_batch": { "label": "Shorts probe — batch size", "hint": "How many videos to classify per run." },
"enrich_batch_size": { "label": "Enrichment batch size", "hint": "videos.list ids per call (YouTube caps this at 50)." },
"autotag_title_sample": { "label": "Auto-tag title sample", "hint": "Recent video titles sampled per channel for language detection." }
"autotag_title_sample": { "label": "Auto-tag title sample", "hint": "Recent video titles sampled per channel for language detection." },
"youtube_api_key": { "label": "YouTube API key", "hint": "Optional. Used for public reads (channels/videos) so shared backfill doesn't depend on a user's OAuth. Stored encrypted; write-only." }
},
"save": "Save",
"saving": "Saving…",

View file

@ -3,6 +3,7 @@
"loading": "Konfiguráció betöltése…",
"groups": {
"email": "E-mail / SMTP",
"youtube": "YouTube API",
"quota": "Kvóta",
"backfill": "Letöltés (backfill)",
"shorts": "Shorts-vizsgálat",
@ -21,7 +22,8 @@
"shorts_probe_max_seconds": { "label": "Shorts-vizsgálat — max hossz (mp)", "hint": "Csak az ennél nem hosszabb videókat vizsgáljuk lehetséges Shortsként." },
"shorts_probe_batch": { "label": "Shorts-vizsgálat — kötegméret", "hint": "Futásonként ennyi videót osztályozunk." },
"enrich_batch_size": { "label": "Gazdagítási kötegméret", "hint": "videos.list azonosítók hívásonként (a YouTube 50-ben maximálja)." },
"autotag_title_sample": { "label": "Auto-címke címmintavétel", "hint": "Csatornánként ennyi friss videócímet mintázunk a nyelvfelismeréshez." }
"autotag_title_sample": { "label": "Auto-címke címmintavétel", "hint": "Csatornánként ennyi friss videócímet mintázunk a nyelvfelismeréshez." },
"youtube_api_key": { "label": "YouTube API-kulcs", "hint": "Opcionális. Publikus olvasásokhoz (csatornák/videók), hogy a megosztott letöltés ne függjön egy felhasználó OAuth-jától. Titkosítva tárolva; csak írható." }
},
"save": "Mentés",
"saving": "Mentés…",