Repo for the whole DH/AS webstack
Find a file
Dada1981 67570d9b1f
Merge pull request #16 from Dada1981/codex/isolate-and-refactor-portfolios
refactor portfolios and isolate public profiles
2026-08-15 09:07:09 +02:00
assets/.aistudio feat: initialize dual identity author portfolio 2026-06-10 22:33:37 +02:00
data Changed folder structure 2026-07-20 21:46:53 +02:00
src refactor portfolios and isolate public profiles 2026-08-15 09:05:20 +02:00
tests refactor portfolios and isolate public profiles 2026-08-15 09:05:20 +02:00
.dockerignore Switched to modular data-layout. Added fourth portfolio. 2026-07-22 16:46:50 +02:00
.env.example feat: secure admin authentication with sessions 2026-08-15 00:36:01 +02:00
.gitignore Switched to modular data-layout. Added fourth portfolio. 2026-07-22 16:46:50 +02:00
bun.lock Selektiv die AI-Änderungen übernommen 2026-07-20 13:53:24 +02:00
docker-compose.yml feat: secure admin authentication with sessions 2026-08-15 00:36:01 +02:00
Dockerfile feat: secure admin authentication with sessions 2026-08-15 00:36:01 +02:00
index.html feat: harden persistence and enrich portfolio projects 2026-08-14 22:43:40 +02:00
metadata.json feat: initialize dual identity author portfolio 2026-06-10 22:33:37 +02:00
package.json test: harden uploads and cover critical flows 2026-08-15 08:45:07 +02:00
README.md refactor portfolios and isolate public profiles 2026-08-15 09:05:20 +02:00
server.ts refactor portfolios and isolate public profiles 2026-08-15 09:05:20 +02:00
tsconfig.json feat: initialize dual identity author portfolio 2026-06-10 22:33:37 +02:00
vite.config.ts feat: initialize dual identity author portfolio 2026-06-10 22:33:37 +02:00

Autoren-Portfolio-Suite

Eine selbst gehostete Portfolio-Anwendung für mehrere voneinander getrennte Autorenprofile. Inhalte, Bücher, laufende Projekte, Designs, SEO-Daten und Rechtstexte werden über eine gemeinsame, passwortgeschützte Administrationsoberfläche gepflegt.

Technisch besteht die Anwendung aus einem React-/Tailwind-Frontend und einem Express-Backend. Sie kann als einzelner Docker-Container betrieben werden und benötigt keine externe Datenbank.

Funktionsumfang

  • Mehrere unabhängig konfigurierte Autorenprofile mit eigenen Domains und Themes
  • Biografie, Schlagworte, individuelle Texte und optionale Zusatzsektion
  • Bücherregal mit Detailansicht, Cover, Kauflink und optionaler Spotify-Playlist
  • Aktuelle Projekte mit Fortschritt, Markdown-Detailtext, Bild und optionaler Spotify-Playlist
  • Lokaler Bild-Upload über den Adminbereich
  • Verwaltung von Impressum, Datenschutzerklärung und weiteren Rechtstexten
  • Optionale serverseitige Gemini-Unterstützung für Klappentexte
  • Profilabhängige SEO-Metadaten, Open Graph, strukturierte Daten, Sitemap und robots.txt
  • Health-Endpunkte, persistente JSON-Datenhaltung und automatische Migrationsbackups

Trennung der Profile

Die öffentliche Seite erhält ausschließlich die Daten des Profils, das zur aufgerufenen Domain gehört. Andere Profile, deren Domains und interne Pfadkonfigurationen werden von der öffentlichen API nicht ausgeliefert.

Im Produktionsmodus gilt der Hostname als Trennlinie:

  • Konfigurierte Domains reagieren nur auf die exakte Domain und deren www-Variante.
  • Ein URL-Pfad oder Query-Parameter kann nicht zu einem anderen Profil wechseln.
  • Pfadbasierte Profilvorschauen stehen nur in der lokalen Entwicklungsumgebung zur Verfügung.
  • Vollständige Profildaten sind ausschließlich über einen authentifizierten Admin-Endpunkt verfügbar.
  • Gemeinsam gepflegte Rechtstexte werden bei der öffentlichen Ausgabe von strukturiert bekannten Kennungen anderer Profile bereinigt. Die gespeicherten Originaltexte bleiben unverändert.

Der Reverse Proxy sollte zusätzlich nur die tatsächlich verwendeten Domains an den Container weiterleiten.

Architektur

Die öffentlichen Portfolioseiten verwenden eine gemeinsame, daten- und themegesteuerte React-Komponente. Dadurch liegen Layout, Buch- und Projektmodale, Rechtstexte sowie die Medienintegration nur einmal im Code vor. Profilbezogene Inhalte und Farben kommen aus der Datenbank; die Auswahl des Profils findet serverseitig statt.

Wichtige Verzeichnisse und Dateien:

src/App.tsx                         Öffentliche/Admin-Routen und Datenabruf
src/components/PortfolioPage.tsx   Gemeinsame öffentliche Portfolioseite
src/components/AdminPanel.tsx      Administrationsarbeitsbereich
src/components/admin/              Ausgelagerte Admin-Komponenten
src/defaultData.ts                 Ausgangsdaten für eine neue Installation
server.ts                           API, Routing, Sessions, Uploads und SEO
data/database.json                 Persistente Inhaltsdaten
data/uploads/                       Lokal hochgeladene Bilder
data/backups/                       Automatische Migrationsbackups
tests/                              Integrations- und Sicherheitstests

Docker-Inbetriebnahme

1. Konfiguration anlegen

Kopiere .env.example nach .env und ersetze die Beispielwerte:

ADMIN_PASSWORD=EinLangesZufaelligesAdminPasswort
SESSION_SECRET=EineUnabhaengigeZufaelligeZeichenfolgeMitMindestens32Zeichen

# Optional: aktiviert den Klappentext-Assistenten
GEMINI_API_KEY=

# Nur setzen, wenn genau ein vertrauenswürdiger Reverse Proxy vorgeschaltet ist
# TRUST_PROXY=1

Für den Produktionsbetrieb gelten folgende Mindestanforderungen:

  • ADMIN_PASSWORD: gesetzt und mindestens 12 Zeichen lang
  • SESSION_SECRET: gesetzt, unabhängig vom Passwort und mindestens 32 Zeichen lang

Ohne gültige Werte startet der Server im Produktionsmodus bewusst nicht. Geheimnisse gehören ausschließlich in .env beziehungsweise in die Secret-Verwaltung der Betriebsumgebung und nicht in docker-compose.yml oder Git.

2. Container starten

docker compose up -d --build

Die Anwendung ist anschließend am veröffentlichten Container-Port 3000 erreichbar. In einer öffentlichen Installation sollte davor ein Reverse Proxy wie Caddy, Traefik oder Nginx Proxy Manager TLS übernehmen und ausschließlich die vorgesehenen Domains weiterleiten.

Wenn genau ein vertrauenswürdiger Proxy vorgeschaltet und Port 3000 nicht direkt öffentlich erreichbar ist, kann TRUST_PROXY=1 gesetzt werden. Das sorgt dafür, dass das Login-Limit die ursprüngliche Client-IP verwendet. Bei direkter Veröffentlichung des Ports darf diese Option nicht aktiviert werden.

Installation ohne Docker

Voraussetzung ist eine unterstützte Node.js-Version (empfohlen: Node.js 20 oder neuer).

npm install
npm run build
NODE_ENV=production \
ADMIN_PASSWORD="EinLangesZufaelligesAdminPasswort" \
SESSION_SECRET="EineUnabhaengigeZufaelligeZeichenfolgeMitMindestens32Zeichen" \
npm start

Optional können GEMINI_API_KEY, TRUST_PROXY, PORT und DATA_DIR ergänzt werden. Standardmäßig lauscht die Anwendung auf Port 3000 und verwendet ./data als Datenverzeichnis.

Für die lokale Entwicklung genügt:

npm install
npm run dev

Die im Entwicklungsmodus vorhandenen Fallback-Secrets sind ausschließlich für lokale Entwicklung gedacht und werden in Produktion nicht akzeptiert.

Admin-Authentifizierung und Schutzmaßnahmen

Das Admin-Passwort wird nur beim Login übertragen und weder als Bearer-Token zurückgegeben noch im Browser gespeichert. Nach erfolgreicher Anmeldung wird eine zufällige, serverseitig verwaltete Sitzung erzeugt.

  • Sitzungsdauer: 24 Stunden
  • Cookie: HttpOnly, SameSite=Strict, in Produktion zusätzlich Secure
  • Sitzungen liegen nur im Arbeitsspeicher und enden spätestens bei einem Server-/Container-Neustart
  • Schreibende Admin-Anfragen werden auf gleiche Herkunft geprüft
  • Fehlgeschlagene Logins werden pro Client-IP begrenzt
  • Uploads und Gemini-Aufrufe besitzen zusätzliche Sitzungslimits
  • Sicherheitsheader und eine Content Security Policy werden serverseitig gesetzt

Ein vergessenes Admin-Passwort wird über die Betriebsumgebung geändert; es gibt keine öffentliche Passwort-zurücksetzen-Funktion.

Datenhaltung und Backups

Alle Inhaltsdaten liegen in data/database.json. Docker bindet mit ./data:/app/data das vollständige Datenverzeichnis auf dem Host ein. Damit bleiben Daten und Uploads beim Neubauen oder Ersetzen des Containers erhalten.

Für ein vollständiges Backup sollte der gesamte Ordner data/ gesichert werden:

data/database.json
data/uploads/
data/backups/

Schreibvorgänge auf die JSON-Datenbank erfolgen atomar und nacheinander. Vor schemaändernden Migrationen legt die Anwendung eine unveränderte Sicherung in data/backups/ an. Diese lokalen Sicherungen ersetzen kein externes, regelmäßig getestetes Backup.

Bild-Uploads

Der Adminbereich akzeptiert JPEG, PNG, WebP, GIF und AVIF mit maximal 8 MB pro Datei.

Der Server:

  • prüft Dateiendung, angegebenen MIME-Typ und den tatsächlichen Dateikopf,
  • lehnt SVG, HTML und andere aktive Formate ab,
  • erzeugt zufällige, nicht überschreibbare Dateinamen,
  • begrenzt Uploadversuche pro Admin-Sitzung und Stunde,
  • liefert Uploads mit nosniff und langfristigen Cache-Headern aus.

Die Bilder können für Avatare, Banner, Buchcover und aktuelle Projekte verwendet werden. Leere Bildfelder erzeugen keinen Request zu einem externen Standardbild.

Spotify und externe Dienste

Spotify-Playlists werden sowohl in Buch- als auch in Projektdetails nach dem Zwei-Klick-Prinzip eingebunden. Beim Öffnen eines Details erscheint zunächst nur ein lokaler Platzhalter. Erst nach einem bewussten Klick auf „Spotify-Player laden“ wird das Spotify-iframe erzeugt und eine Verbindung zu Spotify hergestellt.

Weitere mögliche externe Verbindungen:

  • GEMINI_API_KEY: Der optionale Admin-Assistent sendet die eingegebenen Buchinformationen serverseitig an die Google-Gemini-API.
  • Kauf- und sonstige Markdown-Links: Erst ein Klick führt zur jeweiligen externen Website.
  • Externe Bild-URLs: Das System unterstützt sie weiterhin, empfohlen werden jedoch lokal hochgeladene Bilder.

Die Anwendung enthält kein Analytics- oder Tracking-System und lädt keine externen Webfonts. Sie verwendet für Besucher weder localStorage noch sessionStorage. Der Browser speichert lediglich das notwendige Admin-Sitzungscookie nach einer Anmeldung. Diese Punkte sowie das übliche Logging des Reverse Proxys/Hosters sollten passend zur tatsächlichen Installation in der Datenschutzerklärung beschrieben werden.

SEO und Betriebsendpunkte

  • /health/live: Prozess läuft
  • /health/ready: Daten wurden geladen und der Server ist bereit
  • /robots.txt: profilabhängige Crawling-Regeln
  • /sitemap.xml: profilabhängige Sitemap
  • /admin: mit noindex gekennzeichneter Administrationsbereich

Seitentitel, Beschreibung, Canonical URL, Open-Graph-Daten und strukturierte Personendaten werden serverseitig für die aufgerufene Domain erzeugt. Optionale SEO-Felder können je Profil im Adminbereich gepflegt werden; fehlende Angaben werden aus bestehenden Profildaten abgeleitet.

Qualitätschecks

npm run typecheck  # TypeScript-Typprüfung
npm test           # Produktionsbuild und Integrationstests
npm run check      # Typprüfung, Build und Tests

Die Integrationstests verwenden ein temporäres Datenverzeichnis und einen kurzlebigen lokalen HTTP-Server. Geprüft werden unter anderem:

  • verpflichtende Produktions-Secrets,
  • Admin-Sitzung und Logout,
  • Origin-Schutz und Login-Limit,
  • exaktes Domain-Routing und öffentliche Profilisolation,
  • Schutz des vollständigen Admin-Datenendpunkts,
  • gültige und manipulierte Bild-Uploads,
  • wesentliche Sicherheitsheader.

Hinweise zur Aktualisierung

Vor einem Update sollte der komplette data/-Ordner gesichert werden. Danach kann das Image neu gebaut werden:

docker compose up -d --build

Vorhandene Inhaltsdaten werden nicht durch die im Quellcode enthaltenen Ausgangsdaten ersetzt. Neue optionale Felder werden migrationsfähig ergänzt; vor notwendigen Schemaänderungen wird automatisch ein Backup angelegt.