Public archive with sidebar navigation, project pages, month archive, search and entry pages with image blocks. Admin area for brands, projects, post types, users, api clients, media and the entry editor with drag and drop images, preview per audience, scheduling and publish checks. Read and write API with bearer tokens, audience scoping, idempotent creation, OpenAPI document and editorial guide. Magic link login with configurable allowed domains, whole app behind the session gate. 456 tests including design rule checks.
5.1 KiB
Logbuch Umsetzungsstand
Stand: 30.07.2026
Plan 1, Fundament: fertig, lokal, nicht in git
| Nachweis | Ergebnis |
|---|---|
pnpm test |
11 Dateien, 112 Tests grün |
pnpm typecheck |
ohne Fehler |
pnpm build |
erfolgreich, 7 Routen |
Live gegen localhost:4700 |
Lese-API, gelesen-Status und Sichtbarkeit von Hand geprüft |
| Mutationsprobe an 6 Wächtern | alle 6 werden von Tests gefangen |
Es gibt weiterhin kein Repository. Kein git init, kein Commit, bis der Auftraggeber die laufende Anwendung lokal abgenommen hat.
Abweichungen vom Plan, unterwegs entstanden
| Punkt | Was war | Was gilt |
|---|---|---|
| Postgres-Image | Volume an /var/lib/postgresql/data |
Postgres 18 verlangt den Mount an /var/lib/postgresql, sonst startet der Container nicht |
| TypeScript | 7.0.2 installiert | Next 16.2.12 kommt mit der Compiler-API von TS 7 nicht klar und braucht experimental.useTypeScriptCli. Rückweg wäre TypeScript 6 |
| sharp | im Plan nicht erwähnt | Lässt sich auf dem Rechner nicht bauen, aus onlyBuiltDependencies entfernt. Wird erst für Bildvarianten in Plan 5 gebraucht und muss dort geklärt werden |
tsconfig.json |
im Plan mit Tabs | Next schreibt die Datei beim Start selbst um, sie ist werkzeuggesteuert |
findPost |
nur Slug und Zielgruppe | zusätzlich optionale Projektbindung, weil Slugs nur pro Projekt eindeutig sind |
| Token-Abfrage | SQL lag in src/lib/api-auth.ts |
verschoben nach src/data/repositories/clients.ts, damit die eigene Regel gilt: kein SQL außerhalb von src/data |
Behobene Mängel aus dem Review
Fünf Prüfer mit getrennten Blickwinkeln haben 34 Funde gemeldet, 14 wurden gegnerisch geprüft, 9 bestätigt, 5 widerlegt. Vier echte Ursachen:
markReadnahm fremde Beitrags-Kennungen ungeprüft an. Ein an Trakk gebundener Client konnte einen MTA360-Beitrag als gelesen melden, die Zeile landete mit falschem Projekt in der Tabelle und senkte den Zähler der fremden App. Die Zeilen werden jetzt aus der Datenbank abgeleitet, eingeschränkt auf Projekt, Statuspublishedund die für den Client sichtbaren Zielgruppen.- Unbekannte Beitrags-Kennung führte zu 500. Der Fremdschlüssel schlug durch. Das war zusätzlich ein Auskunftskanal, weil existierende Kennungen 200 und unbekannte 500 ergaben. Jetzt antwortet der Endpunkt 200 mit
marked: 0und verrät nichts. - Seitenaufteilung ohne eindeutige Sortierung. Bei gleichem
publishAtwar die Reihenfolge zufällig, Beiträge konnten auf zwei Seiten doppelt oder gar nicht erscheinen. Sortiert wird jetzt nachpublish_at desc nulls last, dann nachid. - Stillgelegte Projekte lieferten weiter Beiträge aus, obwohl
GET /api/v1/projectssie ausblendet.listPosts,findPostundlistUnreadprüfen jetztproject.is_active.
Dazu Testlücken geschlossen: Projektbindung des Clients, vollständige Übergangsmatrix mit allen 25 Paaren, Zielgruppe bis zur HTTP-Antwort inklusive scope public, Form des Problem-JSON, Parametergrenzen, since als Gleichheitsgrenze, Schlagwort-Filter mit zwei Schlagworten ohne Doppelzählung, Seed-Idempotenz ohne Dubletten, 422 für Clients ohne Projektbindung.
Bewusst nicht behoben
| Fund | Warum nicht |
|---|---|
| Beitrags-Detail liefert keine Blöcke und Medien | Plan 1 liefert genau die Listenfelder. Blöcke kommen mit dem Web-Archiv in Plan 3 |
Schema erlaubt einen Write-Client mit can_publish |
Es gibt noch keine Schreib-API. Die Sperre gehört in Plan 5, dort mit Test |
| Lese-Endpunkte prüfen den Client-Modus nicht | Ein Write-Client darf lesen. Die Zielgruppe schützt weiterhin, der Modus ist keine zweite Schranke |
| Kein Index deckt die Hauptabfrage der Liste | Bei dieser Datenmenge sinnlos. Gehört gemessen, nicht geraten |
listUnread lädt alle ungelesenen Zeilen zum Zählen |
Zählt Beiträge eines Projekts, das bleibt klein. Bei Bedarf später eine Zählabfrage |
Seed-Token steht im Klartext in scripts/seed.ts |
Reines Entwicklungs-Token für die lokale Datenbank, lb_seed_trakk_read. Darf niemals in einer erreichbaren Umgebung gelten |
Zusammengesetzter Fremdschlüssel auf post_read |
Die Anwendung verhindert falsche Zuordnungen jetzt zuverlässig, geprüft per Mutationsprobe. Ein zusätzlicher Datenbank-Zwang wäre Tiefenverteidigung und kostet eine Migration plus zusätzlichen Index |
Lokale Abnahme
cd /Volumes/M2mini/WORK/NYO/projects/logbuch
docker compose up -d
pnpm dev
Dann:
curl -s -H "Authorization: Bearer lb_seed_trakk_read" "http://localhost:4700/api/v1/posts"
curl -s -H "Authorization: Bearer lb_seed_trakk_read" "http://localhost:4700/api/v1/unread?external_user_id=u-1"
curl -s -o /dev/null -w "%{http_code}\n" "http://localhost:4700/api/v1/posts"
Erwartet: zwei Trakk-Beiträge, kein interner Beitrag, ohne Token 401.
Das Design des Web-Archivs liegt als Entwurf unter docs/design/mockup-overview.html und ist im Browser direkt öffenbar.
Nächster Schritt
Plan 2: Auth, Admin, Block-Editor, Freigabe. Offen davor: Subdomain, und ob der Admin-Login mit better-auth per Mail und Passwort startet oder auf Portal-SSO wartet.