134 lines
7.9 KiB
Markdown
134 lines
7.9 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 selbst gehostetem [LiveKit](https://livekit.io) SFU-Video-Call, 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, LiveKit-SFU-Token und 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`), passend zur bestehenden Payload-Konfiguration
|
|
|
|
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
|
|
|
|
## Kontaktanfragen und CMS-Nachrichten
|
|
|
|
Kontakt- und Terminanfragen werden unter `/admin/anfragen` als Konversationen
|
|
gespeichert. Antworten aus dem CMS werden per SMTP verschickt. Setze dafür
|
|
`INBOUND_REPLY_TO` auf die Mailbox, auf die Besucher antworten sollen. Ein
|
|
Inbound-Mail-Provider muss eingehende Antworten anschließend als JSON an
|
|
`POST /api/email/inbound` weiterleiten und dabei `x-inbound-secret` mit dem
|
|
Wert aus `INBOUND_EMAIL_SECRET` senden. Erwartet werden mindestens `from`,
|
|
`subject`, `text` und eine eindeutige `messageId`; der Thread-Schlüssel im
|
|
Betreff (`[ANOUMA-…]`) ordnet die Antwort der richtigen Anfrage zu.
|
|
|
|
- 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`). Ein zweiter Endpoint tauscht dieses App-Token gegen ein kurzlebiges LiveKit-Token aus.
|
|
- LiveKit läuft als SFU auf dem eigenen Server: Audio, Video und Bildschirm werden nicht zwischen allen Browsern vermasht, sondern einmal zum SFU gesendet und gezielt an die anderen Teilnehmer verteilt. Der Server zeichnet nichts auf oder speichert Medien.
|
|
- 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)
|
|
|
|
Produktion läuft komplett über GitHub Actions CI/CD - der Server baut nichts selbst, er zieht ein fertiges, getestetes Image aus GitHub Container Registry. Einmalig auf dem Server:
|
|
|
|
```bash
|
|
git clone https://github.com/whitehai11/anouma.git
|
|
cd anouma
|
|
chmod +x setup.sh
|
|
./setup.sh
|
|
```
|
|
|
|
`setup.sh` installiert bei Bedarf Docker, legt `/opt/anouma` an, generiert alle Secrets automatisch (nie erneut, wenn schon vorhanden), richtet einen eingeschränkten Deployment-User samt SSH-Key für GitHub Actions ein und zeigt am Ende genau die Werte an, die als GitHub Repository Secrets hinterlegt werden müssen. Das Docker-Image landet unter `ghcr.io/whitehai11/anouma`. `./setup.sh --non-interactive` läuft ohne Rückfragen.
|
|
|
|
Jedes weitere Deployment läuft danach ausschließlich über einen Git-Tag:
|
|
|
|
```bash
|
|
git tag v1.2.0 && git push origin v1.2.0
|
|
```
|
|
|
|
GitHub Actions testet, baut das Image, pusht es nach GHCR und deployt es per SSH auf den Server - inklusive Backup, Healthcheck und automatischem Rollback bei Fehlschlag. Es gibt kein separates Update-Script.
|
|
|
|
Für alle Details (Architektur, GitHub Secrets, CI/Release-Workflows, Reverse-Proxy- und LiveKit-Einrichtung, Environment-Variablen, Backups, Rollback, Security) 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 |
|
|
| `npm test` | Test-Suite (aktuell ein Platzhalter - noch keine Tests vorhanden) |
|
|
| `npm run seo:check` | Optionale SEO-Prüfung (doppelte Slugs, fehlende Alt-Texte) - nicht produktionskritisch |
|
|
|
|
## 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.
|