Files
planer/README.md
Florian WildeandClaude Opus 5 d01c49b5a0 docs: Handbuchseiten zu Aufgaben, Ideen und Projekten
Jede Seite erklärt nicht nur, wo man klickt, sondern warum es so gebaut
ist: warum die Schnellerfassung nur ein Feld hat, warum die Inbox kein
eigener Zustand ist, warum Ideen getrennt liegen und warum Verworfenes
nicht in den Fortschritt zählt.

README und Schnellstart auf den Stand von M2 gebracht; der Schnellstart
endet jetzt mit der ersten erfassten Aufgabe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:33:34 +02:00

278 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Planer
Aufgaben, Ideen, Projekte und Zeitblöcke an einem Ort.
Die Kernidee: Kalender aus Microsoft 365 (Firma, Gewerbe) und Google (privat)
lesen und Aufgaben per Ziehen und Ablegen als Zeitblöcke direkt in diese
Kalender schreiben — inklusive Rückweg, wenn ein Block im Kalender verschoben
wird.
> **Stand:** Meilenstein 2 von 8. Erfassen und Wiederfinden steht: Aufgaben,
> Inbox, Ideen und Projekte, dazu Anmeldung, Rollen, Protokoll und der Betrieb.
> Das Lesen der Kalender kommt mit M3, die Wochenplanung per Ziehen und Ablegen
> mit M4. Der [Fahrplan](ROADMAP.md) sagt, was wann folgt.
- **[Schnellstart](docs/SCHNELLSTART.md)** – in zehn Minuten von `git clone`
zur ersten Anmeldung
- **[Handbuch](docs/handbuch/)** – Anleitung für den Alltag
- **[Fahrplan](ROADMAP.md)** – was geplant ist und was bewusst nicht
- **[Änderungen](CHANGELOG.md)** – was sich je Version getan hat
## Funktionsumfang
### Heute
- **Schnellerfassung** mit einem einzigen Feld; alles ohne Bereich sammelt
sich in der **Inbox** und wird später einsortiert
- **Aufgaben** mit Typ, Status, Priorität, Aufwand und Fälligkeit, sortiert
nach Dringlichkeit und filterbar über die Adresszeile
- **Ideen** getrennt von Aufgaben — ohne Termin, per Klick übernehmbar
- **Projekte** mit Arbeitspaketen, Meilensteinen und Fortschritt
- Anmeldung über einen OIDC-Anbieter, über Passkeys oder mit E-Mail und
Passwort — je Instanz einzeln zuschaltbar
- Rollen `Benutzer` und `Verwaltung`; jede schreibende Aktion wird
serverseitig geprüft und hinterlässt einen Protokolleintrag
- Bereiche mit Farbe, Arbeitsfenster und Tageskapazität je Wochentag
- Hell und Dunkel, Oberfläche durchgehend auf Deutsch
- Betrieb über Docker Compose, mit Sicherung, Aktualisierung und Healthcheck
### Geplant
Kalender lesen und schreiben, Wochenplanung per Ziehen und Ablegen,
Benachrichtigungen, Integrationen, Schnellerfassung vom Handy und ein
MCP-Server. Siehe [ROADMAP.md](ROADMAP.md).
## Bildergalerie
| | Hell | Dunkel |
| ------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Anmelden | ![Anmeldeseite, hell](docs/bilder/anmelden-hell-schreibtisch.png) | ![Anmeldeseite, dunkel](docs/bilder/anmelden-dunkel-schreibtisch.png) |
| Start | ![Startseite, hell](docs/bilder/start-hell-schreibtisch.png) | ![Startseite, dunkel](docs/bilder/start-dunkel-schreibtisch.png) |
| Inbox | ![Inbox, hell](docs/bilder/inbox-hell-schreibtisch.png) | ![Inbox, dunkel](docs/bilder/inbox-dunkel-schreibtisch.png) |
| Aufgaben | ![Aufgaben, hell](docs/bilder/aufgaben-hell-schreibtisch.png) | ![Aufgaben, dunkel](docs/bilder/aufgaben-dunkel-schreibtisch.png) |
| Projekte | ![Projekte, hell](docs/bilder/projekte-hell-schreibtisch.png) | ![Projekte, dunkel](docs/bilder/projekte-dunkel-schreibtisch.png) |
| Ideen | ![Ideen, hell](docs/bilder/ideen-hell-schreibtisch.png) | ![Ideen, dunkel](docs/bilder/ideen-dunkel-schreibtisch.png) |
| Bereiche | ![Bereiche, hell](docs/bilder/bereiche-hell-schreibtisch.png) | ![Bereiche, dunkel](docs/bilder/bereiche-dunkel-schreibtisch.png) |
| Einstellungen | ![Einstellungen, hell](docs/bilder/einstellungen-hell-schreibtisch.png) | ![Einstellungen, dunkel](docs/bilder/einstellungen-dunkel-schreibtisch.png) |
| Protokoll | ![Protokoll, hell](docs/bilder/protokoll-hell-schreibtisch.png) | ![Protokoll, dunkel](docs/bilder/protokoll-dunkel-schreibtisch.png) |
Alle Bilder entstehen mit `npm run screenshots` in einer Wegwerf-Instanz mit
ausschließlich erfundenen Daten — niemals aus einer Instanz mit echten Daten.
## Die zwei Instanzen
Ein Abbild, zwei Deployments, gesteuert über `INSTANZ`. Die Umschaltung wird
**serverseitig erzwungen**: Was eine Instanz nicht darf, liefert dort 404 und
lädt seine Konfiguration gar nicht erst.
| | `firma` | `daheim` |
| -------------- | -------------------------------------- | ------------------------------ |
| Anmeldung | OIDC Entra ID | OIDC authentik |
| Kalenderkonten | Graph (Firmen-Tenant) | Graph (Gewerbe-Tenant), Google |
| Mail | Graph Firmenpostfach | Graph Gewerbepostfach, Gmail |
| Integrationen | Zammad, Wartungsplaner, Spiegel senden | Telegram, Spiegel empfangen |
Ein falsch geschriebener Wert bricht den Start ab, statt still auf einen
Standard zu fallen — in der Firma dürfen keine Provider anlaufen, die dort
nichts zu suchen haben.
## Umgebungsvariablen
Alle Werte stehen in `.env`. Vorlage: [`.env.example`](.env.example).
`.env` gehört niemals ins Git.
### Pflicht
| Variable | Beispiel | Bedeutung |
| ----------------- | ---------------------------------------------------- | ---------------------------------------------- |
| `INSTANZ` | `daheim` | `firma` oder `daheim` |
| `APP_URL` | `https://planer.example.de` | Öffentliche Adresse, ohne Schrägstrich am Ende |
| `DATABASE_URL` | `postgresql://planer:…@db:5432/planer?schema=public` | Verbindung zur Datenbank |
| `AUTH_SECRET` | _(32 Byte Base64)_ | Signiert Sitzungen. `openssl rand -base64 32` |
| `WEBAUTHN_RP_ID` | `planer.example.de` | Nackte Domain, ohne Schema und Port |
| `WEBAUTHN_ORIGIN` | `https://planer.example.de` | Exakt die Herkunft im Browser |
### Datenbank im Compose-Stapel
| Variable | Standard | Bedeutung |
| ------------------- | ---------- | ---------------------------------------------- |
| `POSTGRES_USER` | `planer` | Benutzer der Datenbank |
| `POSTGRES_PASSWORD` | _(keiner)_ | Pflicht. Muss zu `DATABASE_URL` passen |
| `POSTGRES_DB` | `planer` | Name der Datenbank |
| `POSTGRES_PORT` | `5432` | Nach außen veröffentlichter Port |
| `APP_PORT` | `3000` | Nach außen veröffentlichter Port der Anwendung |
### Anmeldung
| Variable | Standard | Bedeutung |
| -------------------- | -------- | ------------------------------------------ |
| `AUTH_TRUST_HOST` | `false` | Hinter einem Reverse Proxy auf `true` |
| `ANMELDUNG_PASSWORT` | `false` | Anmeldung mit E-Mail und Passwort erlauben |
| `OIDC_NAME` | _(leer)_ | Beschriftung des Anmeldeknopfs |
| `OIDC_ISSUER` | _(leer)_ | Issuer-URL des Anbieters |
| `OIDC_CLIENT_ID` | _(leer)_ | Client-ID |
| `OIDC_CLIENT_SECRET` | _(leer)_ | Client-Secret |
| `WEBAUTHN_RP_NAME` | `Planer` | Name, den das Gerät beim Passkey anzeigt |
OIDC ist aktiv, sobald Issuer, Client-ID und Secret gesetzt sind. Der
Rückleit-URI beim Anbieter lautet `<APP_URL>/api/auth/callback/oidc`.
### Einrichtung und Sonstiges
| Variable | Standard | Bedeutung |
| ---------------- | --------------- | ---------------------------------------------------------------------------------------- |
| `ADMIN_EMAIL` | _(leer)_ | Erstes Verwaltungskonto, wird vom Seed angelegt |
| `ADMIN_PASSWORT` | _(leer)_ | Mindestens 12 Zeichen. Ohne den Wert entsteht ein Konto, das nur OIDC oder Passkey kennt |
| `TZ` | `Europe/Berlin` | Zeitzone für Anzeigen und Tagesgrenzen |
| `DEMO_MODUS` | `false` | Nur für die Screenshot-Instanz. Im Betrieb niemals `true` |
## Betrieb
### Starten und stoppen
```bash
docker compose up -d # starten
docker compose logs -f app # Protokoll mitlesen
docker compose down # stoppen, Daten bleiben
```
Der Stapel besteht aus drei Diensten: `db`, ein Einmaldienst `migration`, der
die Migrationen einspielt und sich beendet, und `app`.
### Healthcheck
```bash
curl https://planer.example.de/api/health
```
```json
{
"status": "ok",
"version": "0.1.0",
"instanz": "daheim",
"datenbank": "ok",
"zeit": "2026-03-17T08:20:00.000Z"
}
```
Die Auskunft ist bewusst ohne Anmeldung erreichbar und enthält deshalb keine
Zählstände, Namen oder Adressen. Bei `"status": "fehler"` antwortet der
Endpunkt mit HTTP 503.
### Aktualisieren
```bash
./update.sh # auf den neuesten Tag
./update.sh --nur-pruefen # nur zeigen, was passieren würde
./update.sh --version v0.2.0 # auf eine bestimmte Version
./update.sh --ja # ohne Rückfrage
```
Das Skript prüft die Voraussetzungen, zeigt die Änderungen aus dem CHANGELOG,
legt eine Sicherung an, wechselt auf den Ziel-Tag, baut, startet und wartet
auf einen gesunden `/api/health` mit der erwarteten Version. Klappt das nicht,
rollt es auf die vorherige Version zurück und nennt den Pfad der Sicherung.
Jeder Lauf wird nach `logs/update-<Datum>.log` protokolliert.
Die **Datenbank wird beim Zurückrollen nicht automatisch zurückgespielt** —
das wäre bei einem Teilfehler gefährlicher als hilfreich. Das Skript nennt den
Befehl dafür.
### Sicherung und Wiederherstellung
```bash
./backup.sh # Datenbank, .env und Uploads
./backup.sh --tage 90 # längere Aufbewahrung
./backup.sh --nur-datenbank # nur der Dump
```
Je Lauf entstehen zwei Dateien in `sicherungen/`:
ein `pg_dump` im Custom-Format und ein tar.gz mit `.env` und `uploads`.
Wiederherstellen:
```bash
# Datenbank
docker compose exec -T db pg_restore -U planer -d planer --clean --if-exists \
< sicherungen/planer-db-2026-03-17_02-00-00.dump
# Dateien
tar -xzf sicherungen/planer-dateien-2026-03-17_02-00-00.tar.gz
```
Die Sicherung enthält `.env` und damit alle Geheimnisse. Sie gehört so
geschützt wie die Datenbank selbst.
Nächtlich per cron:
```cron
0 2 * * * cd /opt/planer && ./backup.sh --tage 30 >> logs/backup.log 2>&1
```
## Entwicklung
```bash
npm install
docker compose up -d db # nur die Datenbank
npm run db:migrieren # Migrationen einspielen
npm run db:seed # Bereiche und Verwaltungskonto
npm run dev # Entwicklungsserver
```
### Prüfungen
```bash
npm run pruefen # Prettier, ESLint, tsc, Vitest, ShellCheck
npm run test:integration # gegen eine echte Datenbank
npm run test:migration # Migrationen gegen eine Wegwerf-Datenbank
npm run test:e2e # Playwright gegen die gebaute Anwendung
npm run screenshots # Handbuchbilder neu aufnehmen
```
`npm run pruefen` muss vor jedem Commit grün sein. Fehlt `shellcheck` lokal,
wird das Docker-Abbild `koalaman/shellcheck` benutzt.
### Aufbau
```
prisma/ Schema, Migrationen, Seed, Demo-Seed
scripts/ Prüf-, Test- und Betriebsskripte
src/app/ Seiten und API-Routen; (app) = angemeldeter Bereich
src/components/ Bausteine; ui/ enthält die Primitiven
src/lib/ Reine Logik ohne DB und ohne Systemzeit, je Datei ein Test
src/server/ DB-nahe Dienste, Server Actions, Provider
e2e/ Playwright-Rauchtests
screenshots/ Aufnahme der Handbuchbilder
docs/ Schnellstart, Handbuch, Bilder
devlog/ Entwicklungstagebuch und Zeiterfassung
```
Reine Funktionen bekommen die aktuelle Zeit als Parameter, damit sie ohne
Tricks prüfbar bleiben. Die Tests laufen bewusst in UTC — so fällt sofort auf,
wenn irgendwo die Ortszeit des Servers durchschlägt statt Europe/Berlin.
### Bekannte Meldungen
`npm audit` meldet vier Funde in transitiven Abhängigkeiten der
**`prisma`-CLI** (`deepmerge-ts` über `@prisma/config`, `mysql2`). Beide sind
Entwicklungsabhängigkeiten und erreichen das Laufzeitabbild nicht; MySQL wird
ohnehin nie geladen. Der angebotene Fix wäre ein Rückschritt auf Prisma 6.
## Sicherheit
- Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich, damit
sich nicht abfragen lässt, wer hier ein Konto hat.
- Sitzungen sind JWT, aber Rolle und Sperrstatus werden bei jeder Anfrage
frisch aus der Datenbank gelesen — eine Sperre wirkt sofort.
- Jede schreibende Aktion prüft die Rolle in der Seite **und** noch einmal in
der Server Action. Die Oberfläche blendet Knöpfe nur zusätzlich aus.
- Audit-Details werden vor dem Speichern gesäubert: Felder, deren Name nach
Passwort, Token, Betreff oder Mailinhalt aussieht, werden ersetzt.
- Keine Geheimnisse, Tokens, Mailinhalte oder Termin-Betreffs in Protokollen.
Gefundene Schwachstellen bitte nicht als Issue anlegen, sondern direkt melden.
## Lizenz
Privates Projekt, keine Lizenz zur Weiterverwendung.