Add Logbuch: project update blog with admin, media and API
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.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,322 @@
|
||||
# Logbuch - Design
|
||||
|
||||
Stand: 30.07.2026
|
||||
|
||||
## Zweck
|
||||
|
||||
Logbuch ist die zentrale Stelle, an der pro Projekt festgehalten wird, was passiert ist. Alle zwei Wochen picken sich Moderatoren die stärksten Neuerungen eines Projekts heraus, schreiben sie mit Screenshots auf und veröffentlichen sie. Die jeweilige Anwendung holt die Beiträge über eine API und zeigt sie ihren Nutzern als "Was ist neu" an. Zusätzlich gibt es ein Web-Archiv in Blog-Form: alle Projekte auf einer Übersicht, oder ein einzelnes Projekt mit Jahres- und Monatsarchiv.
|
||||
|
||||
Der Aufbau ist durchgängig dynamisch. Marken, Projekte, Beitragstypen, Inhaltsblöcke und Kanäle sind Daten, nicht Code. Ein neues Projekt anzulegen ist eine Eingabe im Admin, kein Deployment.
|
||||
|
||||
## Rahmenbedingungen und Entscheidungen
|
||||
|
||||
| Punkt | Entscheidung |
|
||||
|---|---|
|
||||
| Reichweite | Zuerst intern, Datenmodell aber mit Marke, Projekt und Zielgruppe, damit Kundennutzung ohne Umbau möglich ist |
|
||||
| Inhalte | Von Hand im Admin aus Blöcken gebaut, dazu Import-Vorschläge aus Trakk und GitHub als Rohmaterial |
|
||||
| Kanäle Runde eins | Web-Archiv und In-App über API |
|
||||
| Kanäle später | Mail über SES, Push über ntfy. Im Datenmodell vorgesehen, nicht gebaut |
|
||||
| Deployment | Eigene App, eigenes Repo, eigene Subdomain, Coolify. Nicht Teil von Portal-v2 |
|
||||
| Auth | better-auth für Moderatoren und Admins, Portal-SSO später. Basic-Auth-Gate vor der Instanz wie bei allen NYO-Apps |
|
||||
| Medien | Coolify-Volume hinter einer Storage-Abstraktion, S3 später als Adapter |
|
||||
| Web-Archiv Runde eins | Interne Ansicht hinter Basic-Auth. Kundensicht läuft ausschließlich über die App-API |
|
||||
| Lesen | Bewusst offen. Wer durch das Basic-Auth-Gate kommt, darf alle Projekte und alle internen Beiträge lesen. Kein Leser-Konto, keine Leserechte-Pflege |
|
||||
| Erfassen | Muss in unter zwei Minuten gehen, ohne Einarbeitung. Wenn Kollegen es als Aufwand empfinden, wird es nicht benutzt. Siehe Abschnitt "Erfassen ohne Reibung" |
|
||||
| Erscheinungsbild | Wie ein guter Blog, nicht wie ein Verwaltungswerkzeug. Hell ist Standard, Dunkel vollständig unterstützt |
|
||||
|
||||
### Ausdrücklich nicht im Umfang
|
||||
|
||||
Mailversand, eigene Absenderdomains pro Kunde, Kampagnen-Automationen, A/B-Tests, Billing, Kommentarfunktion, öffentliche Anmeldeformulare, Double-Opt-in.
|
||||
|
||||
## Firmen- und Projektstruktur
|
||||
|
||||
Spaceport Adventures ist das Dach, darunter die Schwestern NYO, Pocket Rocket, Tajo und Aliens Exist. Marke ist deshalb eine eigene Ebene über dem Projekt, nicht ein Feld am Projekt. Die Startseite kann nach Marke gruppieren, ein Projekt gehört immer zu genau einer Marke.
|
||||
|
||||
## Architektur
|
||||
|
||||
### Stack
|
||||
|
||||
Übernommen aus Trakk und Portal-v2, damit der Kosmos einheitlich bleibt:
|
||||
|
||||
- Next.js 16.2 (App Router), React 19
|
||||
- PostgreSQL mit Drizzle ORM
|
||||
- Tailwind 4
|
||||
- better-auth für Authentifizierung
|
||||
- next-intl für Zweisprachigkeit (de, en)
|
||||
- zod für Validierung an allen API-Grenzen
|
||||
- react-icons mit Tabler-Icons
|
||||
- overlayscrollbars statt native Scrollbars
|
||||
- pnpm
|
||||
|
||||
### Schichten
|
||||
|
||||
Vier klar getrennte Einheiten, jede mit eigener Aufgabe und eigener Schnittstelle:
|
||||
|
||||
1. **Domain** (`src/domain/`): Kernlogik ohne Framework-Bezug. Sichtbarkeitsregeln, Statusübergänge, Rechteprüfung, Slug-Erzeugung. Reine Funktionen, vollständig testbar ohne Datenbank.
|
||||
2. **Data** (`src/data/`): Drizzle-Schema und Repositories. Jede Tabelle bekommt ein Repository mit benannten Abfragen. Kein SQL außerhalb dieser Schicht.
|
||||
3. **API** (`src/app/api/v1/`): HTTP-Grenze. Nimmt Anfragen an, validiert per zod, prüft Token oder Session, ruft Domain und Data, gibt JSON zurück. Keine Logik.
|
||||
4. **UI** (`src/app/(admin)/`, `src/app/(public)/`): Admin und Web-Archiv. Holt Daten über Server Components, schreibt über Server Actions oder die API.
|
||||
|
||||
Die Sichtbarkeitsregel lebt ausschließlich in der Domain-Schicht und wird an genau einer Stelle angewendet, bevor Daten die API verlassen. Kein Frontend filtert Zielgruppen.
|
||||
|
||||
## Datenmodell
|
||||
|
||||
### brand
|
||||
|
||||
Marke oder Firma. `id`, `slug`, `name`, `color`, `logo_media_id`, `sort`, `created_at`.
|
||||
|
||||
### project
|
||||
|
||||
`id`, `brand_id`, `slug`, `name`, `description`, `color`, `logo_media_id`, `is_active`, `sort`, `created_at`.
|
||||
|
||||
Slug ist projektweit eindeutig und Teil der Web-Routen.
|
||||
|
||||
### post
|
||||
|
||||
Die zentrale Einheit.
|
||||
|
||||
| Feld | Bedeutung |
|
||||
|---|---|
|
||||
| `id` | |
|
||||
| `project_id` | Zugehöriges Projekt |
|
||||
| `issue_id` | Optionale Ausgabe |
|
||||
| `slug` | Eindeutig pro Projekt |
|
||||
| `title`, `teaser` | |
|
||||
| `type` | `feature`, `improvement`, `fix`, `breaking`, `info` |
|
||||
| `audience` | `internal`, `customer`, `public` |
|
||||
| `status` | `draft`, `review`, `scheduled`, `published`, `archived` |
|
||||
| `locale` | `de` oder `en` |
|
||||
| `translation_group` | Verbindet Übersetzungen desselben Beitrags. Beiträge können auch nur einsprachig existieren |
|
||||
| `publish_at` | Termin bei `scheduled`, Veröffentlichungszeitpunkt bei `published` |
|
||||
| `author_id` | Autor, auch ein API-Client möglich |
|
||||
| `created_by_client_id` | Gesetzt, wenn der Beitrag über die API entstand |
|
||||
| `cover_media_id` | Aufmacherbild |
|
||||
| `search_vector` | tsvector für Volltextsuche |
|
||||
| `created_at`, `updated_at` | |
|
||||
|
||||
Die Zielgruppen sind aufsteigend offen: `public` sehen alle, `customer` sehen Kunden und intern, `internal` nur intern. Ein Kunde bekommt nie einen internen Beitrag ausgeliefert, auch nicht als Titel in einer Liste.
|
||||
|
||||
### block
|
||||
|
||||
Inhaltsblöcke eines Beitrags. `id`, `post_id`, `sort`, `type`, `data` (JSONB).
|
||||
|
||||
Blocktypen in Runde eins: `text` (Rich Text), `image`, `gallery`, `before_after`, `video`, `quote`, `code`, `link`, `callout`.
|
||||
|
||||
Jeder Typ hat ein zod-Schema in einer Registry und eine Render-Komponente. Ein neuer Typ heißt: Schema plus Komponente plus Editor-Eingabe. Kein Schema-Change an der Datenbank.
|
||||
|
||||
### issue
|
||||
|
||||
Optionale Klammer über mehrere Beiträge. `id`, `project_id`, `title`, `period_from`, `period_to`, `status`, `publish_at`, `created_at`.
|
||||
|
||||
Eine Ausgabe erzwingt keinen Rhythmus. Beiträge können jederzeit einzeln erscheinen, oder gebündelt als Ausgabe alle zwei Wochen.
|
||||
|
||||
### tag, post_tag
|
||||
|
||||
Freie Schlagworte pro Projekt, Filter im Archiv.
|
||||
|
||||
### media
|
||||
|
||||
`id`, `project_id`, `kind` (`image`, `video`), `original_filename`, `mime`, `width`, `height`, `byte_size`, `variants` (JSONB mit Breite, Format und Pfad je Variante), `alt`, `caption`, `uploaded_by`, `created_at`.
|
||||
|
||||
Bilder werden beim Upload nach WebP konvertiert und in mehreren Breiten abgelegt. Alt-Text wird angemahnt, ist aber nur bei öffentlichen Beiträgen Pflicht.
|
||||
|
||||
### user, user_project_role
|
||||
|
||||
`user` kommt von better-auth, ergänzt um `is_admin`.
|
||||
|
||||
`user_project_role`: `user_id`, `project_id`, `role` (`moderator`).
|
||||
|
||||
Ein Konto braucht nur, wer schreibt. Lesen ist offen und an keine Rolle gebunden. Schreibrechte gelten pro Projekt, ein Admin hat implizit alle Rechte in allen Projekten.
|
||||
|
||||
Die Tabelle trägt eine Rollenspalte statt eines Kennzeichens, damit weitere Rollen später ohne Migration dazukommen können.
|
||||
|
||||
### api_client
|
||||
|
||||
`id`, `name`, `token_hash`, `project_id` (null bedeutet alle Projekte), `scopes`, `can_publish` (Standard false), `last_used_at`, `revoked_at`.
|
||||
|
||||
Zwei Arten von Clients: **Write-Clients** legen Drafts an und laden Medien hoch. **Read-Clients** sind die eingebundenen Anwendungen und holen veröffentlichte Beiträge ihrer Zielgruppe.
|
||||
|
||||
### post_read
|
||||
|
||||
`project_id`, `external_user_id`, `post_id`, `read_at`.
|
||||
|
||||
Der gelesen-Status hängt an der Nutzerkennung der Zielanwendung, nicht an einem Logbuch-Konto. Trakk schickt seine eigene User-ID mit, MTA360 seine. Die Kennung wird pro Projekt gespeichert, damit sich IDs verschiedener Anwendungen nicht überschneiden.
|
||||
|
||||
### reaction
|
||||
|
||||
`post_id`, `project_id`, `external_user_id`, `kind` (`useful`, `question`), `created_at`. Eindeutig pro Nutzer, Beitrag und Art.
|
||||
|
||||
In Runde eins nur aus der App, weil das Web-Archiv keine Leser-Identität hat.
|
||||
|
||||
### delivery
|
||||
|
||||
`id`, `post_id`, `channel` (`web`, `inapp`, `mail`, `push`), `status`, `dispatched_at`, `error`.
|
||||
|
||||
Web und In-App werden beim Veröffentlichen als erledigt vermerkt. Die Tabelle existiert, damit Mail und Push später nur einen Adapter brauchen.
|
||||
|
||||
### audit_log
|
||||
|
||||
`id`, `actor_type` (`user`, `client`), `actor_id`, `action`, `entity`, `entity_id`, `data`, `created_at`.
|
||||
|
||||
Protokolliert mindestens: Veröffentlichen, Zurückziehen, Zielgruppenwechsel, Rechteänderung, Token-Erzeugung.
|
||||
|
||||
## Rollen und Rechte
|
||||
|
||||
| Rolle | Darf |
|
||||
|---|---|
|
||||
| Admin | Alles. Marken, Projekte, Nutzer, Tokens verwalten |
|
||||
| Moderator (pro Projekt) | Beiträge und Ausgaben anlegen, bearbeiten, veröffentlichen, zurückziehen, Medien hochladen |
|
||||
| Jeder im internen Archiv | Alle Projekte und alle Beiträge lesen, ohne Konto und ohne Rollenzuweisung |
|
||||
| Write-Client | Drafts anlegen und bearbeiten, Medien hochladen. Nicht veröffentlichen |
|
||||
| Read-Client | Veröffentlichte Beiträge der erlaubten Zielgruppe abrufen, gelesen melden |
|
||||
|
||||
Lesen ist bewusst nicht eingeschränkt. Der Schutz liegt in Runde eins allein am Basic-Auth-Gate vor der Instanz. Das ist eine bewusste Entscheidung, damit niemand Leserechte pflegen muss, und gilt nur solange das Web-Archiv intern ist. Sobald Kunden eine Web-Ansicht bekommen sollen, muss diese Entscheidung neu getroffen werden.
|
||||
|
||||
`can_publish` bleibt für Write-Clients auf false. Damit landet nichts unkontrolliert bei Kunden, auch nicht aus einem automatisierten Lauf.
|
||||
|
||||
## API
|
||||
|
||||
Version im Pfad, `/api/v1`. Alle Eingaben zod-validiert. Fehler einheitlich als Problem-JSON mit `type`, `title`, `status`, `detail`.
|
||||
|
||||
### Lesend, Bearer eines Read-Clients
|
||||
|
||||
| Route | Zweck |
|
||||
|---|---|
|
||||
| `GET /api/v1/brands` | Marken |
|
||||
| `GET /api/v1/projects` | Projekte, gefiltert auf die Berechtigung des Clients |
|
||||
| `GET /api/v1/posts` | Beiträge. Parameter: `project`, `since`, `type`, `tag`, `q`, `page`, `per_page` |
|
||||
| `GET /api/v1/posts/:slug` | Einzelner Beitrag mit Blöcken und Medien |
|
||||
| `GET /api/v1/issues` | Ausgaben eines Projekts |
|
||||
| `GET /api/v1/unread` | Ungelesene Beiträge. Parameter: `external_user_id` |
|
||||
| `POST /api/v1/read` | Meldet einen oder mehrere Beiträge als gelesen |
|
||||
| `POST /api/v1/reactions` | Reaktion setzen oder entfernen |
|
||||
|
||||
Die Zielgruppe wird nicht vom Aufrufer bestimmt, sondern aus dem Client abgeleitet. Ein Read-Client der MTA360-Kundenoberfläche bekommt niemals interne Beiträge, egal welche Parameter er schickt.
|
||||
|
||||
### Schreibend, Bearer eines Write-Clients
|
||||
|
||||
| Route | Zweck |
|
||||
|---|---|
|
||||
| `POST /api/v1/posts` | Beitrag anlegen, Status immer `draft` |
|
||||
| `PATCH /api/v1/posts/:id` | Beitrag ändern, solange er nicht veröffentlicht ist |
|
||||
| `PUT /api/v1/posts/:id/blocks` | Blöcke setzen, vollständige Liste in Reihenfolge |
|
||||
| `POST /api/v1/media` | Datei hochladen, multipart. Liefert Media-ID und Varianten |
|
||||
|
||||
`POST /api/v1/posts` akzeptiert einen `idempotency_key`, damit ein wiederholter Aufruf keinen Doppelbeitrag erzeugt.
|
||||
|
||||
### Feeds
|
||||
|
||||
`GET /feed/:project.xml` als Atom, nur Beiträge der Zielgruppe `public`.
|
||||
|
||||
## Einbindung in die Anwendungen
|
||||
|
||||
Keine iframes. Die Anwendungen holen JSON und rendern selbst, damit das Panel im jeweiligen Design sitzt.
|
||||
|
||||
Dazu zwei dünne Client-Pakete im Repo:
|
||||
|
||||
- `packages/client-react` für Trakk, Orbit, Arc und weitere Next-Apps
|
||||
- `packages/client-vue` für MTA360 und nyo-frontend
|
||||
|
||||
Beide bieten dasselbe: Beiträge laden, ungelesene Anzahl als Badge, Panel mit Beitragsliste und Detailansicht, gelesen melden, reagieren. Die Anwendung übergibt Read-Key, Projekt-Slug und ihre eigene Nutzerkennung.
|
||||
|
||||
Der Read-Key gehört serverseitig in die einbindende App. Die Pakete rufen einen kleinen Proxy-Endpunkt der jeweiligen App auf, damit der Key nie im Browser liegt.
|
||||
|
||||
## Admin
|
||||
|
||||
- Projekt-Switcher in der Kopfzeile, Rechte bestimmen die Auswahl
|
||||
- Beitragsliste mit Status, Typ, Zielgruppe, Termin, Filter und Suche
|
||||
- Block-Editor: Blöcke hinzufügen, sortieren per Drag & Drop, Bilder direkt in den Editor ziehen
|
||||
- Vorschau umschaltbar zwischen den Zielgruppen, damit vor dem Veröffentlichen sichtbar ist, was ein Kunde sieht
|
||||
- Workflow `draft` nach `review` nach `published`, mit Zurückziehen nach `archived`
|
||||
- Terminierung: Termin setzen, ein Cron veröffentlicht
|
||||
- Ausgaben: Zeitraum wählen, Beiträge zuordnen, gemeinsam veröffentlichen
|
||||
- Medienbibliothek pro Projekt mit Alt-Text-Pflege
|
||||
- Verwaltung von Marken, Projekten, Nutzern, Rollen und Tokens für Admins
|
||||
|
||||
## Erfassen ohne Reibung
|
||||
|
||||
Das Projekt scheitert oder gelingt an dieser Stelle. Wenn ein Kollege für einen Beitrag über eine neue Funktion mehr als zwei Minuten braucht oder erst etwas lernen muss, schreibt er nichts. Deshalb sind das harte Anforderungen, keine Verbesserungen für später:
|
||||
|
||||
- **Ein Feld zum Anfangen.** Neuer Beitrag heißt: Titel eingeben, losschreiben. Projekt ist aus dem Kontext vorbelegt, Typ ist `feature`, Zielgruppe ist `internal`, Sprache ist `de`. Alles änderbar, nichts abzufragen
|
||||
- **Screenshot aus der Zwischenablage.** Bild mit Strg+V direkt in den Editor einfügen, ohne Datei-Dialog. Genauso Drag & Drop von mehreren Bildern auf einmal, die dann als Galerie landen
|
||||
- **Schreiben wie in einem Textfeld.** Der Text-Block versteht Markdown-Eingaben beim Tippen (Überschrift, Liste, Fettung, Link). Wer Markdown nicht kennt, benutzt die Werkzeugleiste. Eingefügter Text aus anderen Quellen behält Struktur, verliert Fremdformatierung
|
||||
- **Automatisches Speichern.** Entwürfe speichern sich selbst. Kein verlorener Text, keine Speicher-Frage beim Verlassen
|
||||
- **Blöcke sind unsichtbar, bis man sie braucht.** Wer nur Text und Bilder schreibt, sieht keine Block-Verwaltung. Weitere Blocktypen kommen über einen Einfüge-Knopf oder `/` im Text
|
||||
- **Veröffentlichen ist ein Knopf.** Kein Pflicht-Review, kein Pflicht-Termin, keine Pflicht-Schlagworte. Der Review-Status existiert für die, die ihn wollen
|
||||
- **Nichts blockiert ohne Grund.** Prüfungen vor dem Veröffentlichen sind Hinweise, keine Sperren. Gesperrt wird nur, was echten Schaden anrichtet: fehlende Zielgruppe und öffentliche Beiträge ohne Alt-Text
|
||||
|
||||
## Erscheinungsbild
|
||||
|
||||
Es soll aussehen wie ein guter Blog, den man freiwillig liest, nicht wie eine Verwaltungsmaske. Große Aufmacherbilder, ruhige Typografie mit ordentlicher Zeilenlänge im Lesetext, klare Abstände, Beitragstypen als dezente Badges, Projektfarbe als Akzent statt als Anstrich.
|
||||
|
||||
Harte Regeln:
|
||||
|
||||
- **Keine nativen Scrollbars.** Überall overlayscrollbars, auch in Panels, Modalen, Listen, Medienbibliothek und im In-App-Panel. Keine Ausnahme
|
||||
- **Kein großes Stylesheet.** Gestaltet wird mit Tailwind-Utilities und Design-Tokens. Das globale CSS enthält nur Tokens, Basis-Typografie und das overlayscrollbars-Thema und bleibt unter 150 Zeilen. Keine CSS-Datei pro Komponente, kein wachsendes Sammel-Stylesheet
|
||||
- **Hell ist Standard, Dunkel ist vollwertig.** Umsetzung über CSS-Variablen als Tokens plus Tailwind-Dark-Variante. Beide Modi über dieselben Tokens, kein zweites Stylesheet, keine Sonderfälle je Komponente. Umschalter im Kopfbereich, Auswahl wird gespeichert, Systemeinstellung als dritte Option
|
||||
- **Keine Emojis** in Oberfläche und Inhalten
|
||||
- **Keine Pixelangaben**, Abstände und Größen über die Tailwind-Skala
|
||||
- Icons ausschließlich Tabler über react-icons
|
||||
|
||||
### Import-Vorschläge
|
||||
|
||||
Ein Panel im Beitrags-Editor zieht Rohmaterial für einen Zeitraum:
|
||||
|
||||
- Erledigte Tickets aus der Trakk-API des zugeordneten Projekts
|
||||
- Releases und zusammengeführte Pull Requests aus GitHub
|
||||
|
||||
Ergebnis ist eine Auswahlliste. Angehakte Einträge werden als Blöcke mit Titel und Link eingefügt. Kein generierter Text. Die Zuordnung Projekt zu Trakk-Projekt und GitHub-Repository steht am Projekt.
|
||||
|
||||
## Web-Archiv
|
||||
|
||||
| Route | Inhalt |
|
||||
|---|---|
|
||||
| `/` | Übersicht aller Projekte, gruppiert nach Marke, neueste Beiträge |
|
||||
| `/[project]` | Projekt-Blog mit Seitenweiterschaltung |
|
||||
| `/[project]/[year]` und `/[project]/[year]/[month]` | Zeitarchiv |
|
||||
| `/[project]/[slug]` | Einzelner Beitrag |
|
||||
| `/[project]/tag/[tag]` | Schlagwort-Filter |
|
||||
| `/issues/[id]` | Ausgabe als Ganzes |
|
||||
| `/search` | Volltextsuche über Postgres tsvector |
|
||||
|
||||
Zweisprachig über next-intl, keine festverdrahteten Texte. Beitragstypen als Badges. OG-Bilder werden aus Projektfarbe, Logo und Titel erzeugt. Bilder als WebP mit `srcset`, Galerie mit Lightbox.
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
- Eingaben scheitern laut und früh: zod an jeder API-Grenze, Antwort mit Feldfehlern
|
||||
- Veröffentlichen prüft vorab und unterscheidet Hinweis von Sperre. Gesperrt wird nur bei fehlender Zielgruppe, fehlendem Titel, leerem Beitrag oder öffentlichem Beitrag ohne Alt-Text. Fehlender Alt-Text bei internen Beiträgen, fehlende Schlagworte oder fehlendes Aufmacherbild sind Hinweise und halten niemanden auf
|
||||
- Medien-Upload ist zweistufig: Datei speichern, dann Varianten erzeugen. Scheitert die Konvertierung, bleibt das Original nutzbar und der Fehler steht am Medium
|
||||
- Der Veröffentlichungs-Cron ist wiederholbar: er nimmt Beiträge mit `status = scheduled` und `publish_at <= jetzt`, ein Doppellauf erzeugt keine Doppelauslieferung, weil `delivery` den Zustand hält
|
||||
- Ausfälle von Trakk oder GitHub beim Import blockieren den Editor nicht, das Panel zeigt den Fehler und bleibt schließbar
|
||||
- Unbekannte Blocktypen im Renderer werden übersprungen und protokolliert, statt die Seite zu zerlegen
|
||||
|
||||
## Tests
|
||||
|
||||
Der Schwerpunkt liegt dort, wo ein Fehler weh tut.
|
||||
|
||||
| Bereich | Art |
|
||||
|---|---|
|
||||
| Sichtbarkeitsregel (Zielgruppe je Read-Client) | Vitest, vollständige Matrix |
|
||||
| Statusübergänge und Veröffentlichungs-Prüfungen | Vitest |
|
||||
| Schreibrechte pro Projekt, Write-Client darf nicht veröffentlichen | Vitest |
|
||||
| Block-Schemas | Vitest gegen die Registry |
|
||||
| API-Routen | Vitest mit Testdatenbank, Schwerpunkt Auth und Filter |
|
||||
| Draft bis Veröffentlicht bis In-App-Abruf | Ein Playwright-Smoketest |
|
||||
|
||||
Die Sichtbarkeitsmatrix bekommt bewusst mehr Tests als der Rest, weil ein Fehler dort interne Inhalte an Kunden ausliefert.
|
||||
|
||||
## Betrieb
|
||||
|
||||
- Eigenes Repository, Verzeichnis `projects/logbuch`
|
||||
- Coolify-Deployment, eigene Subdomain, Basic-Auth-Gate davor
|
||||
- Eigene PostgreSQL-Instanz
|
||||
- Migrationen über Drizzle, beim Deployment ausgeführt
|
||||
- Medien auf einem Coolify-Volume, Zugriff über eine Storage-Schnittstelle mit einem lokalen Adapter. Ein S3-Adapter kann später ohne Änderung am Rest ergänzt werden
|
||||
- Cron für terminierte Veröffentlichung
|
||||
- Rybbit für Zugriffszahlen des Web-Archivs
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
- Subdomain noch nicht festgelegt
|
||||
- Welche Nutzerkennung MTA360 und Trakk für den gelesen-Status liefern, muss beim Einbinden je App geklärt werden
|
||||
- Ob Tajo und Aliens Exist eigene Projekte in Logbuch bekommen, ist offen. Das Datenmodell trägt sie
|
||||
Reference in New Issue
Block a user