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:
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
|
||||
|
||||
Reference in new issue
Block a user