Grundhaltung beim Import: nichts überschreiben. Was schon in der Bibliothek
liegt, bleibt wie es ist; es kommt nur dazu, was fehlt. Ein Import soll
niemand versehentlich seinen Fortschritt kosten.
Trakt gibt je nach Ansicht unterschiedliche Formen aus. Gemeinsam ist ihnen
ein Array mit Einträgen, die entweder show oder movie tragen — genau darauf
stützt sich der Import, statt ein bestimmtes Exportformat zu verlangen.
Der CSV-Leser errät das Trennzeichen: deutsche Tabellenprogramme nehmen gern
das Semikolon.
Frischt laufende Serien auf, meldet neue Episoden und neu verfügbare
Watchlist-Titel, räumt Papierkorb und abgelaufene Sitzungen.
Grundsatz: Der Lauf darf nie an einem einzelnen Titel scheitern. Fehler werden
gezählt und protokolliert, der Lauf macht weiter.
Neue Episoden werden nur gemeldet, wenn sie zeitlich nicht in ferner
Vergangenheit liegen — sonst löste der erste Abgleich einer alten Serie hundert
Meldungen aus.
Der Zeitplan ist ein einfacher Timer statt einer Cron-Bibliothek: Flimmer
braucht genau einen täglichen Lauf. Wer mehr will, stösst /api/cron/sync von
aussen an.
Vier Wege, ein Nachrichtenformat. Der Webhook trägt sowohl content als auch
text im Rumpf — damit bedient derselbe Kanal Discord, Slack und Mattermost,
ohne dass jemand ein Format auswählen muss.
Auch ein Fehlversuch wird protokolliert: sonst versucht es jeder nächste Lauf
erneut und der Kanal wird zugespammt, sobald er wieder erreichbar ist.
Die E-Mail-Vorlage ist schlichtes HTML mit Inline-Stilen — Mailprogramme
können weder externe Stylesheets noch CSS-Variablen. Das Logo steckt als
Inline-SVG drin.
Alles wird aus den Sichtungen berechnet — es gibt keine fortgeschriebenen
Zähler, die auseinanderlaufen könnten. Zeitreihen laufen als SQL, weil Prisma
keine Gruppierung nach Monat kennt.
Der Monatsschlüssel kommt bewusst als Text aus der Datenbank: date_trunc
liefert Mitternacht in der Sitzungszeitzone, JavaScript legt beim Vergleich
die lokale Zone an — zwei Stunden Versatz genügen, damit kein Schlüssel mehr
passt und das Diagramm leer bleibt.
Die Posterkachel zog über lib/images.ts die serverseitige Cache-Logik in den
Browser-Bundle, sobald sie aus einer Client-Komponente heraus gerendert wurde
— der Build brach ab, sobald eine solche Seite dazukam.
Die reine URL-Bildung liegt jetzt in lib/image-url.ts und darf überall
verwendet werden; Abruf und Dateicache bleiben hinter server-only.
Zwei Dinge unterscheiden ein Universum von einer Liste: es hat mehrere
Reihenfolgen, und der Fortschritt ist der des jeweiligen Betrachters.
Die Erscheinungsreihenfolge wird aus den Erscheinungsdaten berechnet und nie
gespeichert — sie stimmt damit immer, auch wenn später Titel dazukommen.
Alles andere ist frei sortierbar.
Ein geteiltes Universum zeigt jeder Person ihren eigenen Stand; bei Serien
schrumpft die Restlaufzeit anteilig mit den gesehenen Episoden.
Grundgedanke: Eine Sichtung ist die einzige Wahrheit darüber, was jemand
gesehen hat. Fortschritt, „nächste Episode" und später die Statistik werden
daraus berechnet, nichts davon wird doppelt gespeichert.
Rewatches zählen über einen Durchlaufzähler am Eintrag. Ein neuer Durchlauf
setzt den Fortschritt zurück, die alten Sichtungen bleiben stehen — sonst
verlöre die Schauzeit-Statistik jedes Mal ihre Geschichte.
Der Status ergibt sich aus den Sichtungen: die erste Episode startet, die
letzte schließt ab. Pausiert und Abgebrochen bleiben unangetastet — wer
bewusst pausiert hat, will das nicht durch ein Häkchen aufgehoben bekommen.
Der Katalog ist bewusst gemeinsam für alle Nutzer und über (Typ, TMDB-ID)
dedupliziert: zehn Leute mit derselben Serie brauchen die Episodenliste nur
einmal. Persönliches steckt ausschließlich in LibraryEntry und Watch.
Poster laufen über /api/image statt direkt zu TMDB — einmal holen, danach
vom eigenen Server ausliefern. Größe und Pfad werden streng geprüft, damit
daraus kein offener Proxy wird.
Staffel 0 (Specials) wird importiert, zählt aber nicht zum Fortschritt;
Episoden ohne eigene Laufzeit fallen auf die durchschnittliche Episodenlänge
der Serie zurück.
Nur die Endpunkte, die Flimmer wirklich braucht, samt der Felder, die uns
interessieren. Antworten werden zehn Minuten zwischengespeichert, damit
Tippen im Suchfeld nicht jedes Zeichen einzeln nach draußen trägt.
Zugang wahlweise über den klassischen API-Schlüssel oder das Bearer-Token —
TMDB gibt beides aus, und Selbsthoster haben mal das eine, mal das andere.
Authorization Code mit PKCE, ID-Token gegen die JWKS des Anbieters geprüft.
Ohne Client-Bibliothek: Flimmer braucht genau einen Ablauf, dafür lohnt
keine weitere Abhängigkeit mit eigenem Versionsrhythmus.
Bekannte SSO-Kennungen werden verknüpft, sonst über die E-Mail-Adresse
einem vorhandenen Konto zugeordnet; das Anlegen neuer Konten lässt sich
per OIDC_AUTO_REGISTER unterbinden.
Passwörter mit bcrypt, Sitzungen als opake Token in der Datenbank — im
Cookie steht das Klartext-Token, in der Tabelle nur dessen SHA-256. Damit
lassen sich einzelne Geräte gezielt abmelden, was reine JWT-Sitzungen nicht
hergeben.
Der erste registrierte Zugang wird automatisch Administrator, sonst käme
man nach dem Aufsetzen nicht in die Verwaltung. Die Registrierung ist
instanzweit abschaltbar; solange es keinen Nutzer gibt, bleibt sie offen.
Enthält außerdem API-Schlüssel je Nutzer als Grundlage für die spätere
REST-API und die Instanz-Einstellungen in der Tabelle AppSetting.
Prisma-Schema in vier Schichten: Konten und Zugang, gemeinsamer Katalog
(über die TMDB-ID dedupliziert), persönliches Tracking je Nutzer sowie
Universen mit mehreren Reihenfolgen. Dazu Benachrichtigungskanäle und
Protokoll der Hintergrundläufe.
Prisma 7 verlangt einen Treiber-Adapter statt der URL im Schema; die
Konfiguration liegt deshalb in prisma.config.mjs — bewusst als .mjs, damit
die CLI auch im schlanken Produktionsimage ohne TypeScript-Loader läuft.