feat(portfolio): add product updates feed
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
# Batchmaker Newsfeed Backend Implementation Plan
|
||||
|
||||
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Einen kleinen, dauerhaft betriebenen Update-Feed für `Batchmaker` und `Batchmaker Standalone` bauen, der von beiden Produkten und dem Portfolio gelesen werden kann.
|
||||
|
||||
**Architecture:** Separater, schlanker `updates-api`-Service statt Newsdaten im Next.js-Build oder in der Portfolio-App. Der Service bietet eine öffentliche, read-only JSON-/RSS-Schnittstelle und einen geschützten Admin-Schreibpfad. SQLite ist für die geringe erwartete Änderungsfrequenz ausreichend, wird aber auf einem persistenten Docker-Volume betrieben und regelmäßig gesichert. Das Portfolio und beide Batchmaker-Oberflächen bleiben Clients; kein Produkt greift direkt auf die Datenbank zu.
|
||||
|
||||
**Tech Stack:** Node.js 22, TypeScript, native `node:sqlite`, kleiner HTTP-Server ohne neues Framework sofern die vorhandene Runtime das zulässt, Docker Compose, SQLite WAL, Bearer-Token aus Secret/Environment, Next.js Server Component oder Route-Proxy für das Portfolio.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ist-Zustand und Integrationsgrenzen verifizieren
|
||||
|
||||
**Objective:** Vor der Implementierung die realen Repositories, Deployment-Ziele und vorhandenen Batchmaker-Backend-Konventionen festhalten.
|
||||
|
||||
**Read-only checks:**
|
||||
|
||||
```bash
|
||||
find /home/eldov-ryzen5/workspace/Coding -maxdepth 3 -type f \( -name package.json -o -name docker-compose.yml -o -name Dockerfile \)
|
||||
rg -n "express|node:sqlite|sqlite|health|PORT|docker" /home/eldov-ryzen5/workspace/Coding/Batchmaker
|
||||
```
|
||||
|
||||
**Festhalten:**
|
||||
- Welches Repository ist die kanonische Quelle für `Batchmaker`?
|
||||
- Wo läuft `Batchmaker Standalone` tatsächlich?
|
||||
- Soll der Feed auf `free-warez.win` oder am selben Host wie das Portfolio betrieben werden?
|
||||
- Existiert bereits ein Secret-/Backup-Verfahren, das wiederverwendet werden muss?
|
||||
- Welche Produkt-URLs sollen aus einem Update verlinkt werden?
|
||||
|
||||
**Gate:** Keine Codeänderung, bevor Host, Repo-Pfade und Deployment-SSOT eindeutig sind.
|
||||
|
||||
## 2. Datenmodell und API-Vertrag als kleine, stabile Oberfläche definieren
|
||||
|
||||
**Objective:** Ein minimales Schema schaffen, das sowohl öffentliche Updates als auch spätere redaktionelle Pflege trägt.
|
||||
|
||||
**Proposed entity `updates`:**
|
||||
|
||||
```sql
|
||||
id INTEGER PRIMARY KEY
|
||||
slug TEXT NOT NULL UNIQUE
|
||||
product TEXT NOT NULL CHECK (product IN ('batchmaker', 'standalone'))
|
||||
title TEXT NOT NULL
|
||||
summary TEXT NOT NULL
|
||||
body_markdown TEXT NOT NULL DEFAULT ''
|
||||
status TEXT NOT NULL CHECK (status IN ('draft', 'published', 'archived'))
|
||||
published_at TEXT NULL
|
||||
created_at TEXT NOT NULL
|
||||
updated_at TEXT NOT NULL
|
||||
link_url TEXT NULL
|
||||
```
|
||||
|
||||
**Indexes:** `(product, status, published_at DESC)` und `slug`.
|
||||
|
||||
**Public API:**
|
||||
- `GET /healthz` → `{ "status": "ok" }`, ohne Datenbankdetails.
|
||||
- `GET /v1/updates?product=batchmaker|standalone&limit=1..50&cursor=...` → nur `published` und `published_at <= now`, newest first.
|
||||
- `GET /v1/updates/:slug` → ein veröffentlichtes Update.
|
||||
- `GET /feed.xml?product=...` → RSS 2.0 oder Atom; für Browser/Feed-Reader und SEO.
|
||||
|
||||
**Admin API:**
|
||||
- `POST /v1/admin/updates`
|
||||
- `PATCH /v1/admin/updates/:id`
|
||||
- `POST /v1/admin/updates/:id/publish`
|
||||
- `POST /v1/admin/updates/:id/archive`
|
||||
|
||||
Admin-Endpunkte verlangen `Authorization: Bearer ...`; kein Token in URLs, Logs oder Frontend-Code. Schreibdaten werden serverseitig validiert; `slug`, `product`, Titel, Status, Limits und Datumswerte sind keine vertrauenswürdigen Eingaben.
|
||||
|
||||
**Entscheidungen:** Cursor-Pagination nur, wenn sie mit vertretbarem Aufwand sauber umgesetzt wird; sonst zunächst `limit` mit hartem Maximum. Kein Login-System, kein CMS und keine Datei-Uploads in v1.
|
||||
|
||||
## 3. Backend als eigenständigen Service anlegen
|
||||
|
||||
**Likely files:**
|
||||
- Create: `services/updates-api/package.json`
|
||||
- Create: `services/updates-api/tsconfig.json`
|
||||
- Create: `services/updates-api/src/server.ts`
|
||||
- Create: `services/updates-api/src/db.ts`
|
||||
- Create: `services/updates-api/src/validation.ts`
|
||||
- Create: `services/updates-api/src/feed.ts`
|
||||
- Create: `services/updates-api/migrations/001_initial.sql`
|
||||
- Create: `services/updates-api/Dockerfile`
|
||||
- Create: `services/updates-api/.env.example`
|
||||
|
||||
**Implementation constraints:**
|
||||
- Native Node APIs und `node:sqlite` zuerst; keine neue ORM-Abhängigkeit.
|
||||
- DB-Verzeichnis per `UPDATES_DB_PATH`, Standard lokal unter `data/updates.sqlite`.
|
||||
- Beim Start Migrationen idempotent ausführen, WAL und foreign keys aktivieren.
|
||||
- JSON-Fehlerformat stabil halten: `{ "error": { "code": "...", "message": "..." } }`.
|
||||
- Request body und Content-Length begrenzen, damit der Admin-Endpunkt kein unkontrollierter Speicher-/Disk-Sink wird.
|
||||
- `Cache-Control` für öffentliche Antworten setzen; Admin-Antworten nicht cachen.
|
||||
- Logs ohne Authorization-Header und ohne vollständigen Markdown-Body.
|
||||
- HTML-Escaping/Content-Type beim RSS-Generator korrekt behandeln; Markdown nicht ungefiltert als HTML ausgeben.
|
||||
|
||||
## 4. Tests zuerst für die Invarianten schreiben
|
||||
|
||||
**Test target:** `services/updates-api/test/updates.test.ts`
|
||||
|
||||
Abdecken:
|
||||
1. Drafts erscheinen niemals in öffentlichen Listen.
|
||||
2. Zukünftige `published_at`-Einträge erscheinen erst nach ihrem Veröffentlichungszeitpunkt.
|
||||
3. `product` akzeptiert nur `batchmaker` und `standalone`.
|
||||
4. `limit` wird auf den erlaubten Bereich begrenzt oder mit 400 abgewiesen.
|
||||
5. Fehlendes/falsches Bearer-Token blockiert alle Admin-Schreibpfade.
|
||||
6. Publish setzt `status` und `published_at` atomar.
|
||||
7. Archivierte Einträge verschwinden aus Public API und RSS.
|
||||
8. RSS enthält keine ungefilterten/kaputten XML-Zeichen.
|
||||
9. `/healthz` prüft die DB-Verbindung, gibt aber keine internen Pfade preis.
|
||||
|
||||
**Run:**
|
||||
|
||||
```bash
|
||||
node --test --import tsx services/updates-api/test/updates.test.ts
|
||||
```
|
||||
|
||||
Falls `tsx` nicht bereits verfügbar ist: nicht blind eine Dependency hinzufügen; zuerst prüfen, ob TypeScript-Tests über den vorhandenen Build/Node-Mechanismus laufen. Nur dann die kleinste notwendige devDependency ergänzen.
|
||||
|
||||
## 5. Öffentliche Clients anbinden
|
||||
|
||||
### Portfolio
|
||||
|
||||
**Likely files:**
|
||||
- Modify: `src/app/page.tsx` oder neue `src/components/updates-feed.tsx`
|
||||
- Modify: `src/app/layout.tsx` nur falls Metadata/Feed-Link benötigt wird
|
||||
- Modify: `docker-compose.yml` nur für URL-/Netzwerk-Konfiguration
|
||||
- Modify: `.env.example` oder Runtime-Dokumentation
|
||||
|
||||
Serverseitig vom Next.js-Server abrufen, nicht per Browser direkt gegen eine private interne URL. Bei Feed-Ausfall darf die Portfolio-Seite nicht failen: leerer Zustand bzw. „Updates momentan nicht verfügbar“, mit serverseitigem Timeout. Produktfilter sichtbar machen: „Batchmaker“ / „Standalone“ / „Alle“. Keine Admin-Funktion im öffentlichen Portfolio.
|
||||
|
||||
### Batchmaker und Standalone
|
||||
|
||||
Je Produkt eine kleine, vorhandene UI-Integration verwenden; keine gemeinsame UI-Bibliothek nur für drei Karten einführen. Die Clients konsumieren denselben Public API-Vertrag und zeigen maximal die letzten drei Updates plus Link „Alle Updates“. Die Feed-API-URL wird konfiguriert, nicht hart codiert.
|
||||
|
||||
## 6. Deployment- und Persistenzpfad definieren
|
||||
|
||||
**Likely files:**
|
||||
- Modify/create: Compose-Datei des Zielhosts bzw. eines dedizierten `updates-api`-Stacks
|
||||
- Create: `services/updates-api/backup.sh` oder vorhandenes Backup-Verfahren erweitern
|
||||
- Modify: Traefik dynamic config/labels, falls öffentliches Routing dort erfolgt
|
||||
|
||||
**Deployment design:**
|
||||
- Service intern auf Port 8080.
|
||||
- Traefik-Router z. B. `updates.w-make.com` oder ein eindeutig festgelegter Pfad; Entscheidung erst nach Hostprobe.
|
||||
- SQLite unter `/data/updates.sqlite` auf named volume, nicht im Container-Layer.
|
||||
- Admin-Token ausschließlich über Secret/Environment auf dem Host.
|
||||
- `/healthz` als Container-Healthcheck.
|
||||
- DB-Backup vor jedem Deployment und per täglichem, atomarem `sqlite3 .backup`/Copy-Verfahren; Restore-Test in temporärem Verzeichnis.
|
||||
- Keine Docker-Socket-Berechtigung für den Newsfeed-Service.
|
||||
|
||||
**Gate:** Erst deployen, wenn Traefik-Docker-Socket-Zugriff und Router-Konfiguration live geprüft sind; kein blindes 404-Debugging.
|
||||
|
||||
## 7. Redaktions- und Betriebsworkflow festlegen
|
||||
|
||||
**V1:** Updates werden per dokumentiertem CLI-Skript oder `curl` aus einer sicheren Admin-Umgebung angelegt/publiziert. Kein browserbasiertes Admin-Panel, solange nicht mehrere Autoren oder regelmäßige Redaktionsarbeit nachgewiesen sind.
|
||||
|
||||
**Create:** `services/updates-api/README.md` mit:
|
||||
- lokalem Start
|
||||
- Beispiel für Draft → Publish
|
||||
- Token-Handling ohne Credential-Beispiele
|
||||
- Backup/Restore
|
||||
- Rollback
|
||||
- Public API examples
|
||||
|
||||
Optional später: kleines Admin-Formular hinter Auth, wenn der CLI-Workflow nachweislich hinderlich ist.
|
||||
|
||||
## 8. Ende-zu-Ende-Verifikation
|
||||
|
||||
**Local gates:**
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
node --test --import tsx services/updates-api/test/updates.test.ts
|
||||
curl -fsS http://127.0.0.1:8080/healthz
|
||||
curl -fsS 'http://127.0.0.1:8080/v1/updates?product=standalone&limit=3'
|
||||
```
|
||||
|
||||
**Integration checks:**
|
||||
- Ein Test-Draft je Produkt anlegen.
|
||||
- Sicherstellen, dass Public API beide Drafts nicht zeigt.
|
||||
- Beide veröffentlichen und Responses/RSS prüfen.
|
||||
- Portfolio und beide Produkte laden; Feed-Karten und Deep-Link prüfen.
|
||||
- API absichtlich stoppen: Clients bleiben renderbar und zeigen Fallback.
|
||||
- Falsches Admin-Token, übergroßer Body und ungültiges Produkt testen.
|
||||
|
||||
**Production gates:**
|
||||
- `curl` über den finalen HTTPS-Host → 200 für `/healthz`, `/v1/updates`, `/feed.xml`.
|
||||
- Traefik-Router trifft den richtigen Service; keine 404/502.
|
||||
- Container-Restart: Test-Update bleibt vorhanden.
|
||||
- Backup erstellen und in temporäre SQLite-Datei zurücklesen.
|
||||
- Logs enthalten keine Secrets.
|
||||
- Erst danach echte News veröffentlichen.
|
||||
|
||||
## Risiken und Trade-offs
|
||||
|
||||
- **SQLite vs. PostgreSQL:** SQLite ist für seltene redaktionelle Writes und wenige Leser ausreichend und reduziert Ops deutlich. Bei mehreren Autoren, hoher Schreiblast oder bestehender PostgreSQL-SSOT migrieren; API-Vertrag bleibt gleich.
|
||||
- **Separate API vs. Next Route Handler:** Separate API verhindert Kopplung von Persistenz und Portfolio-Build und kann von beiden Batchmaker-Produkten genutzt werden. Mehr Deployment-Artefakt, aber sauberere Verantwortungsgrenze.
|
||||
- **Token vs. Auth-System:** Ein einzelnes Admin-Token ist für v1 klein und ausreichend, solange es nur server-/CLI-seitig genutzt wird. Bei mehreren Autoren oder Browser-Admin auf OIDC wechseln.
|
||||
- **Markdown vs. rich HTML:** Markdown als gespeicherter Inhalt bleibt diff-/backup-freundlich; Ausgabe zunächst plain text bzw. strikt sanitised rendern. Kein raw HTML in v1.
|
||||
- **Public cache:** CDN-/Traefik-Caching kann Updates verzögern. Bei Veröffentlichung `Cache-Control` kurz halten oder gezielt invalidieren; keine clientseitige Dauer-Cacheschicht einführen.
|
||||
|
||||
## Offene Entscheidungen für den Session-Start
|
||||
|
||||
1. Finaler API-Host bzw. Pfad und Zielhost.
|
||||
2. Sind `Batchmaker` und `Standalone` beide öffentlich erreichbar und in welchen Repositories liegen sie?
|
||||
3. Soll der öffentliche Feed vollständig anonym sein oder nur über die Produktseiten erreichbar sein?
|
||||
4. Reicht CLI/`curl` als Redaktionsworkflow für v1? Default: ja.
|
||||
5. Welche Pflichtfelder braucht ein Update redaktionell: nur Titel/Summary/Body oder auch Release-Version und CTA-Link?
|
||||
6. Gibt es bereits einen PostgreSQL-Service, der als SSOT genutzt werden muss? Default: nein; nicht aus Gründen der vermeintlichen Zukunftssicherheit einführen.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- Ein eigenständiger, health-checkbarer Updates-Service läuft reproduzierbar lokal und im Ziel-Compose.
|
||||
- Beide Produktwerte werden korrekt getrennt, Draft/Published/Archived sind invariant.
|
||||
- Public JSON und RSS funktionieren; Admin-Schreibpfad ist authentifiziert und validiert.
|
||||
- Portfolio, Batchmaker und Standalone konsumieren denselben Vertrag mit Graceful Fallback.
|
||||
- SQLite liegt persistent, Backup und Restore sind real getestet.
|
||||
- Lint, Build, Unit-/Integrationstests und HTTPS-Smoke-Tests sind grün.
|
||||
- Keine Credentials, Produktionsdaten oder Formeln im Repository.
|
||||
|
||||
## Nicht in v1
|
||||
|
||||
- Kein vollständiges CMS.
|
||||
- Kein User-/Role-System.
|
||||
- Keine Kommentare, Likes, Suche oder Analytics.
|
||||
- Keine Webhooks/Event-Sourcing-Struktur.
|
||||
- Kein PostgreSQL/Redis/Kafka ohne gemessenen Bedarf.
|
||||
- Keine direkte Kopplung an Batchmaker-interne Datenbanktabellen.
|
||||
|
||||
ponytail: SQLite + separater API-Service ist die kleinste belastbare Grenze. Auf PostgreSQL/OIDC migrieren, sobald Multi-Author-Redaktion, hohe Write-Last oder vorhandene DB-Governance das rechtfertigt.
|
||||
|
||||
---
|
||||
|
||||
**Session handoff:** Start mit Abschnitt 1, dann Architektur-Gate vor dem ersten Code. Nach jeder implementierten Invariante Tests ausführen; Deployment erst nach Persistenz- und Traefik-Probe.
|
||||
|
||||
**Constitution note:** Die geladene `batchmaker`-Constitution beschränkt Repo-Operationen auf `/home/eldov-ryzen5/workspace/Coding/Batchmaker/`; dieses Portfolio-Repository liegt außerhalb. Für die nächste Session muss die zuständige Portfolio-/CEO-Freigabe bzw. der korrekte Projektkontext geklärt werden, bevor Batchmaker-Repositories verändert werden.
|
||||
|
||||
**Created:** 2026-08-16
|
||||
**Status:** Proposed
|
||||
**No implementation performed.**
|
||||
Reference in New Issue
Block a user