Files
anouma/README.md
T
maroandClaude Sonnet 5 50c39a70e0 Add full Docker deployment: setup.sh, update.sh, healthcheck, TURN support
- setup.sh: interactive/non-interactive one-shot installer (build, DB
  healthcheck, migrate, seed, start), idempotent secret generation, NPM
  reverse-proxy network auto-detection and optional join, optional
  AUTO_UPDATE cron install.
- update.sh: release-tag-gated updates only (never bare main), DB backup
  with retention before every update, lock file against concurrent runs,
  automatic code rollback on failed post-update healthcheck.
- Dockerfile: multi-stage build, non-root user, built-in HEALTHCHECK against
  the new /api/health route, wholesale COPY so new source dirs (e.g.
  scripts/) never silently go missing at runtime.
- docker-compose.yml: internal anouma-network (configurable), named volume
  for Postgres, app depends_on postgres healthy, no unnecessary published
  ports; docker-compose.override.yml.example documents joining an existing
  NPM network without ever touching NPM itself.
- Fix host-detection: isHost was Boolean(user), wrongly granting host
  privileges to logged-in customers; now checks user.collection === "users".
- Wire configurable STUN/TURN servers through to the WebRTC client
  (lib/meeting/iceServers.ts) so a TURN server can be added later via env
  vars only, no code changes.
- DEPLOYMENT.md, updated README.md and .env.example documenting the whole
  flow: NPM integration, env vars, WebRTC, updates, backups, rollback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 21:50:28 +02:00

114 lines
6.7 KiB
Markdown

# Anouma
Die Website von Anouma — Next.js (App Router) mit einem eingebauten [Payload CMS](https://payloadcms.com) (PostgreSQL) für Termine, Beiträge, Angebote und Seiteninhalte, plus Online-Termine mit P2P-Video-Call (WebRTC), Anmeldungen und E-Mail-Erinnerungen.
## Projektstruktur
- `app/(frontend)/` — die öffentliche Website (bestehendes ANOUMA-Design, eigener Root-Layout)
- `app/(call)/` — die Video-Call-Oberfläche (`/termine/[slug]/call`), eigenes minimalistisches dunkles Layout ohne Navbar/Footer
- `app/(payload)/` — der Admin-Bereich unter `/admin`, Payloads REST/GraphQL-API sowie die Meeting-/Cron-API-Routen (`api/meetings/...`, `api/cron/...`)
- `app/global-not-found.tsx` — statische 404-Seite (siehe unten, warum sie nötig ist)
- `collections/`, `globals/`, `access/`, `fields/`, `payload.config.ts` — die CMS-Konfiguration
- `lib/payload/` — Local-API-Zugriffe für die öffentliche Website (mit `React.cache` pro Request memoisiert)
- `lib/meeting/` — Meeting-Passwort, Zeitfenster/Status, signierte Beitritts-Tokens, WebSocket-Signaling, Reminder-Logik
- `lib/email/` — SMTP-Versand (nodemailer) und das E-Mail-Template für Reminder
- `lib/texte.ts` — die ursprünglichen Anouma-Texte, nur noch als Seed-Quelle verwendet
- `scripts/seed.ts` — überträgt die vorhandenen Inhalte ins CMS
- `server.ts` — eigener Node-Server (statt `next start`), weil daran der WebRTC-Signaling-WebSocket hängt
Zwei bzw. drei Root-Layouts (`(frontend)`, `(payload)`, `(call)`) bedeuten: Next.js kann daraus keine einzelne 404-Seite komponieren, deshalb gibt es `app/global-not-found.tsx` (siehe `experimental.globalNotFound` in `next.config.ts`).
## Online-Termine & Video-Call
- Ein Termin wird per Häkchen „Online-Termin“ zu einem Video-Call-Termin. Payload generiert dabei automatisch ein zufälliges Meeting-Passwort (nie aus der Meeting-ID abgeleitet); die Admin kann jederzeit ein neues erzeugen lassen.
- Öffentliche Beitrittsseite: `/termine/[slug]/beitreten` (Name + Passwort, beide Pflicht) → bei Erfolg `/termine/[slug]/call`.
- Ist die aufrufende Person im selben Browser als Admin eingeloggt, wird sie automatisch als **Host** erkannt (kein Passwort nötig, größeres Beitritts-Zeitfenster, exklusive Steuerung: Bildschirmfreigabe, Teilnehmer entfernen).
- Die Zeitfenster (wie früh Host/Teilnehmer beitreten dürfen, wie lange das Meeting nach Ende offen bleibt) sind zentral im CMS unter **Video-Termin-Einstellungen** konfigurierbar.
- Server-seitige Validierung: `/api/meetings/[slug]/join` prüft Termin, Zeitfenster und Passwort und stellt danach erst ein kurzlebiges, signiertes Sitzungs-Token aus (`MEETING_SESSION_SECRET`). Das WebSocket-Signaling (`/ws/signaling`, siehe `server.ts`) prüft dieses Token erneut, bevor jemand einem Raum beitreten darf.
- Reines P2P-WebRTC: Der Server relayt nur kleine Signaling-Nachrichten (Angebot/Antwort/ICE), niemals Audio/Video/Bildschirmfreigabe. Es wird nichts aufgezeichnet oder gespeichert.
- Teilnehmer-Anmeldungen laufen über die Collection **Anmeldungen** (`event-registrations`), öffentlich erreichbar über `/api/meetings/[slug]/register`.
### E-Mail-Erinnerungen
Erinnerungs-Mails (60/30 Minuten vorher, pro Termin im CMS einzeln an-/abschaltbar) werden **nicht** automatisch im Hintergrund verschickt, sondern müssen von einem externen Cron-Job ausgelöst werden, z. B. alle 5 Minuten:
```bash
curl -H "Authorization: Bearer $CRON_SECRET" https://anouma.org/api/cron/event-reminders
```
Der Versand ist idempotent (`reminder60Sent`/`reminder30Sent` je Anmeldung, `hostReminder60Sent`/`hostReminder30Sent` je Termin) — ein häufiger laufender Cron verschickt also nie doppelt.
## Setup (Produktion / Deployment)
Der empfohlene Weg ist vollständig dockerisiert und braucht auf dem Host weder Node noch npm:
```bash
git clone https://git.maro.run/maro/anouma.git
cd anouma
./setup.sh
```
`setup.sh` fragt interaktiv nach Domain, ob eine bestehende Nginx Proxy Manager-Instanz eingebunden werden soll usw., generiert fehlende Secrets automatisch und überschreibt nie bereits gesetzte. Baut Images, startet Postgres, wartet auf dessen Healthcheck, migriert, seedet optional die Inhalte und startet die App. `./setup.sh --non-interactive` läuft ohne Rückfragen mit sinnvollen Defaults.
Updates auf ein neues Release: `./update.sh` (nur echte Release-Tags, nie ungetaggte `main`-Commits; Backup vor jedem Update, automatischer Rollback bei fehlgeschlagenem Healthcheck).
Für alle Details (NPM-Reverse-Proxy-Einrichtung, Environment-Variablen, WebRTC/STUN/TURN, Backups, Rollback, Auto-Updates, Troubleshooting) siehe **[DEPLOYMENT.md](./DEPLOYMENT.md)**.
## Lokale Entwicklung (ohne Docker für die App)
```bash
npm install
cp .env.example .env
# .env ausfüllen: DATABASE_URI auf 127.0.0.1 statt "postgres" setzen, PAYLOAD_SECRET (z. B. mit `openssl rand -base64 48`)
```
### Datenbank
Nur Postgres per Docker, die App läuft direkt auf dem Host:
```bash
docker compose up -d postgres
```
(oder eine gehostete Postgres-Instanz, z. B. Neon/Supabase — einfach `DATABASE_URI` in `.env` entsprechend setzen).
### Entwicklung
```bash
npm run dev
```
- Website: http://localhost:3000
- Admin: http://localhost:3000/admin — beim ersten Aufruf legst du dort direkt deinen eigenen Admin-Zugang (E-Mail + Passwort) an. Es gibt kein Standardpasswort im Code.
### Vorhandene Inhalte ins CMS übertragen
```bash
npm run seed
```
Überträgt die 7 Angebote sowie die Texte für Startseite, Über mich, Angebote-Einleitung, Aktuelles-Einleitung, Kontakt und Termin buchen aus `lib/texte.ts` ins CMS. Kann gefahrlos mehrfach ausgeführt werden. Kontaktdaten (E-Mail/Telefon/Region) werden zunächst als Platzhalter gesetzt — bitte im Admin unter „Kontakt“ durch die echten Angaben ersetzen.
### Produktions-Build ohne Docker
```bash
npm run build
npm run migrate # wendet Datenbank-Migrationen an
npm run start
```
Für einen vollständig dockerisierten Produktionsbetrieb (App + Datenbank, Healthchecks, Reverse-Proxy-Integration) siehe oben bzw. **[DEPLOYMENT.md](./DEPLOYMENT.md)**.
## Weitere Skripte
| Skript | Zweck |
| --------------------------- | --------------------------------------------------- |
| `npm run generate:types` | `payload-types.ts` aus der Config neu erzeugen |
| `npm run generate:importmap`| Admin-Importmap neu erzeugen (nach neuen Feldtypen) |
| `npm run migrate:create` | Neue Datenbank-Migration aus Config-Änderungen bauen |
| `npm run lint` | ESLint |
## Design
Siehe `app/(frontend)/globals.css` für das Farbsystem und `AGENTS.md` für die Next.js-Version-16-Hinweise, die für jede Code-Änderung in `app/` gelten.