Plan TP1: Funde der Planpruefung eingearbeitet (Einstellungsfeld, Verwaltungstoken, Verschieben, Testbarkeit, Missbrauchsschutz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-03 08:17:43 +00:00
1 parent e3ff05c804
commit f28060198c
2 files changed
+190 -173

No files matched your search

@@ -74,8 +74,9 @@ legt nur Fehlendes an, Indizes nur bei neuen Collections). Alle Collections `per
`documentSecurity: false` (nur Server-Schlüssel). `test/support/schema.js` liest das neue Skript mit.
**`termineConfig`** – eine Einstellung je Funktion; Dokument-ID = ID des `portalFeatures`-Dokuments.
`customerId`, `projectId` (64), `data` (Text, JSON bis 200 000 Zeichen), `version` (Ganzzahl),
`updatedAt`, `updatedBy` (100). Inhalt von `data` (validiert in `services/termine/einstellungen.js`):
`customerId`, `projectId` (64), `einstellung` (Text, JSON bis 200 000 Zeichen; nicht `data` – das Wort
behandelt `appwriteAdmin.formatRequestBody` als fertig verpackten Body), `version` (Ganzzahl),
`updatedAt`, `updatedBy` (100). Inhalt von `einstellung` (validiert in `services/termine/einstellungen.js`):
```jsonc
{
@@ -202,7 +203,9 @@ Verwaltungslink und ggf. Widerrufsbelehrung; K20 an den Betrieb mit .ics) bzw.
K20-Block „Anfrage“ an den Betrieb). Abgelaufen ⇒ „Diese Reservierung ist abgelaufen“ + Link zur Website.
Ein Token gilt genau einmal.
**Verwaltungslink** `termin.html#m=<token>` (gültig bis Terminende + 1 Tag): zeigt den Termin und je nach
**Verwaltungslink** `termin.html#m=<token>` (gültig bis Terminende + 1 Tag; Token = HMAC über die
Buchungs-ID, damit spätere Mails wie die Erinnerung denselben Link enthalten; gespeichert nur sein Hash):
zeigt den Termin und je nach
Lage
- „Termin absagen“ bis `absageBisStd` vor Beginn (danach Text „Absage nur telefonisch: …“) ⇒ `abgesagt`,
Zellen frei, G06 an Gast (ICS CANCEL), K21 an den Betrieb;
@@ -223,15 +226,17 @@ Betrieb weiter – auch wenn keine Buchung passt (dann Block „nicht zugeordnet
**Betrieb im Portal**: Anfrage annehmen (`angefragt` → `bestaetigt`, G02) oder ablehnen (`abgelehnt`, G04,
Zellen frei); bestätigten Termin absagen (Pflicht-Grund-Auswahl + optionale Nachricht, G06) oder verschieben
(G07); „nicht erschienen“; Termin von Hand eintragen (`quelle: manuell`, Gast-E-Mail optional, G02 nur wenn
(G07); „nicht erschienen“; Gastdaten auf Wunsch löschen (anonymisieren); Termin von Hand eintragen (`quelle: manuell`, Gast-E-Mail optional, G02 nur wenn
der Haken „Bestätigung an den Gast senden“ gesetzt ist; ohne Widerrufsbelehrung durch das System, die ist
bei telefonischen Buchungen Sache des Betriebs); Zeit sperren (`quelle: sperre`).
bei telefonischen Buchungen Sache des Betriebs); Zeit sperren (`quelle: sperre`, höchstens 500 Minuten am Stück; längeres über Abwesenheiten).
**Hintergrund-Jobs** (`server/index.js`, Muster der vorhandenen `setInterval`-Jobs, je Job eigene Fehlerbehandlung):
- jede Minute: abgelaufene `gehalten` ⇒ `verfallen` + Zellen löschen; Warteschlange senden (6).
- alle 5 Minuten: Erinnerungen (`bestaetigt`, `erinnerung`, kein `erinnerungGesendetAt`,
`startsAt` in `[now + stundenVorher − 5 min, now + stundenVorher]`; erst Marker setzen, dann einreihen);
`bestaetigt` mit `endsAt < now − 1 h` ⇒ `erledigt`.
`now + 1 h < startsAt ≤ now + stundenVorher` und Vertragsschluss vor `startsAt − stundenVorher`, damit
ein Ausfall des Jobs keine Erinnerung verschluckt; erst Marker setzen, dann einreihen);
`bestaetigt` mit `endsAt < now − 1 h` ⇒ `erledigt`; `angefragt` mit `startsAt < now` ⇒ `abgelehnt`
(vom System, Zellen frei).
- täglich: Löschfristen (11), verwaiste Zellen (Buchung fehlt oder nicht mehr belegend).
### 5. Doppelbuchung verhindern
@@ -268,13 +273,17 @@ in `farbe`, Fuß mit Betrieb (Name, Adresse, Telefon, E-Mail, Links Impressum/Da
„Diese E-Mail wurde im Auftrag von {Betrieb} über den Terminplaner von WEBklar versendet.“ Jede Vorlage hat
Blöcke `sie`/`du`.
Der Betreff ist je Vorlage der Platzhalter `{{betreff}}`; der Server setzt ihn je Anrede und Fall (Du-Form
sinngemäß: „Bitte bestätige deinen Termin …“). G01 und G08 enthalten keinen vom Gast eingegebenen Text
(kein Missbrauch als Mail-Relais).
| ID | an | Betreff (Sie-Form) | Inhalt |
|---|---|---|---|
| G01 | Gast | Bitte bestätigen Sie Ihren Termin bei {betrieb} | Datum, Uhrzeit, Leistung, Knopf „Termin bestätigen“, „Das waren Sie nicht? Dann ignorieren Sie diese E-Mail.“ Nichts Werbliches. |
| G02 | Gast | Ihr Termin bei {betrieb} am {datum} | Zusammenfassung, Buchungsnummer, Zahlungsart, Stornoregel (Wortlaut), Verwaltungslink, .ics (REQUEST); bei Widerrufsrecht: Widerrufsbelehrung + Muster-Widerrufsformular + Hinweis auf die Widerrufsfunktion (§ 312f Abs. 2 BGB) |
| G03 | Gast | Ihre Terminanfrage bei {betrieb} | „Der Betrieb meldet sich“, Verwaltungslink (nur Absage) |
| G04 | Gast | Ihre Terminanfrage bei {betrieb} | abgelehnt, optionale Nachricht des Betriebs |
| G05 | Gast | Erinnerung: Ihr Termin morgen um {zeit} | Termin, Verwaltungslink; sonst nichts |
| G05 | Gast | Erinnerung: Ihr Termin am {datum} um {zeit} | Termin, Verwaltungslink; sonst nichts |
| G06 | Gast | Ihr Termin am {datum} wurde abgesagt | wer abgesagt hat, Grund/Nachricht, .ics (CANCEL) |
| G07 | Gast | Ihr Termin wurde verschoben | alte/neue Zeit, .ics (REQUEST, Sequenz + 1) |
| G08 | Gast | Eingangsbestätigung Ihres Widerrufs | Inhalt der Erklärung, Datum und Uhrzeit des Eingangs |
@@ -289,7 +298,7 @@ Gästedaten).
„{Leistung} – {Betrieb}“ (an den Betrieb: „{Leistung}: {Gastname}“), `LOCATION` = Adresse, `ORGANIZER` =
Betrieb, `ATTENDEE` = Gast (`RSVP=FALSE`), `METHOD` REQUEST/CANCEL. Keine „In Google eintragen“-Links.
**Kalender-Abo für den Betrieb**: je Ressource geheimer Link `…/v1/kalender/<feedToken>.ics` (nur
**Kalender-Abo für den Betrieb**: je Ressource geheimer Link `…/v1/kalender/<siteKey>/<feedToken>.ics` (nur
bestätigte Termine und Sperren der nächsten 90 Tage, `X-PUBLISHED-TTL: PT15M`; Google aktualisiert nur
alle paar Stunden – steht so im Portal). Link neu erzeugen macht den alten ungültig.
@@ -348,7 +357,7 @@ abgeleitet, nur zur Anzeige).
| `POST /bestaetigen` `{token}` | E-Mail bestätigt | – |
| `POST /absagen`, `POST /verschieben` `{token, …}` | Verwaltung durch den Gast | – |
| `POST /widerruf` `{name, email, nummer, token?}` | Widerrufsfunktion | – |
| `GET /kalender/:feedToken.ics` | Kalender-Abo des Betriebs (Schlüssel im Pfad statt siteKey) | `max-age=300` |
| `GET /kalender/:siteKey/:feedToken.ics` | Kalender-Abo des Betriebs (Token je Ressource, konstantzeitig verglichen) | `max-age=300` |
Schutz (ohne Captcha-Dienst):
- **CORS**: GETs `Access-Control-Allow-Origin: *`; POSTs nur von erlaubten Origins = Hosts des Projekts