Add client-side watch/bookmark functionality: users can save events to localStorage without RSVPing via a bookmark button next to the "I'm attending" CTA. Watched events appear in the event list with a "Watching" label. Bookmark is only visible for visitors (not attendees or organizers). Includes spec, plan, research, tasks, unit tests, and E2E tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
196 lines
10 KiB
Markdown
196 lines
10 KiB
Markdown
# fete
|
||
|
||
## Grundsätze
|
||
* Soll als PWA im Browser laufen
|
||
* Damit es sich wie eine normale app anfühlt
|
||
* Soll ein kleiner Helfer sein, den man schnell mal nutzen kann
|
||
* Ich will es mir selbst hosten können
|
||
* Das schließt eigentlich schon AI features aus und das ist okay
|
||
* Privacy als first class citizen
|
||
* Schon das Produktdesign muss mit privacy im sinn entworfen werden
|
||
* Keine Registrierung, kein login notwendig (nur z.B. einen code, um den "raum" zu finden oder so)
|
||
* Alternativ könnten etwaige anfallende Daten auch im local storage gespeichert werden
|
||
|
||
## Die Idee
|
||
Eine alternative zu Facebook Event Gruppen oder Telegram Gruppen, in denen eine Veranstaltung bekanntgegeben wird und Teilnahmen bestätigt werden
|
||
|
||
### Zielbild
|
||
Person erstellt via App eine Veranstaltung und schickt seine Freunden irgendwie via Link eine Einladung. Freunde können zurückmelden, ob sie kommen oder nicht.
|
||
|
||
## Gedankensammlung
|
||
* So ne Art Landingpage zu jedem Event
|
||
* Ein Link pro Event, den man z.B. in ne WhatsApp-Gruppe werfen kann
|
||
* Was, wie, wann, wo?
|
||
* Irgendwie auch Designbar, sofern man das will
|
||
* RSVP: "Ich komme" (mit Name) / "Ich komme nicht" (optional mit Name)
|
||
* Wird serverseitig gespeichert + im LocalStorage gemerkt
|
||
* Duplikatschutz: Kein perfekter Schutz ohne Accounts, aber gegen versehentliche Doppeleinträge reicht Gerätebindung via LocalStorage
|
||
* Gegen malicious actors (Fake-Namen spammen etc.) kann man ohne Accounts wenig machen — akzeptables Risiko (vgl. spliit)
|
||
* "Veranstaltung merken/folgen": Rein lokal, kein Serverkontakt, kein Name nötig
|
||
* Löst das Multi-Geräte-Problem: Am Handy zugesagt, am Laptop einfach "Folgen" drücken
|
||
* Auch nützlich für Unentschlossene, die sich das Event erstmal merken wollen
|
||
* View für den Veranstalter:
|
||
* Updaten der Veranstaltung
|
||
* Einsicht angemeldete Gäste, kann bei Bedarf Einträge entfernen
|
||
* Featureideen:
|
||
* Organisator kann einstellen, ob Attendee-Namensliste öffentlich auf der Event-Seite sichtbar ist (default: nur für Organisator). Wenn öffentlich, muss im RSVP-Bottom-Sheet eine Warnung angezeigt werden, dass der Name öffentlich sichtbar sein wird.
|
||
* Link-Previews (OpenGraph Meta-Tags): Generische OG-Tags mit App-Branding (z.B. "fete — Du wurdest eingeladen") damit geteilte Links in WhatsApp/Signal/Telegram hübsch aussehen. Keine Event-Daten an Crawler aus Privacy-Gründen. → Eigene User Story.
|
||
* Kalender-Integration: .ics-Download + optional webcal:// für Live-Updates bei Änderungen
|
||
* Änderungen zum ursprünglichen Inhalt (z.b. geändertes datum/ort) werden iwi hervorgehoben
|
||
* Veranstalter kann Updatenachrichten im Event posten, pro Device wird via LocalStorage gemerkt was man schon gesehen hat (Badge/Hervorhebung für neue Updates)
|
||
* QR Code generieren (z.B. für Plakate/Flyer)
|
||
* Ablaufdatum als Pflichtfeld, nach dem alle gespeicherten Daten gelöscht werden
|
||
* Übersichtsliste im LocalStorage: Alle Events die man zugesagt oder gemerkt hat (vgl. spliit)
|
||
* RSVP editieren: Gast kann seine bestehende Zusage bearbeiten (Name ändern via PUT mit rsvpToken) oder zurückziehen (DELETE mit rsvpToken). Bottom Sheet öffnet sich im Edit-Mode mit pre-filled Name + "Zusage zurückziehen"-Button. Später ergänzen: "Absagen und merken" (Kombination mit 011-bookmark-event). Ausgelagert aus 008-rsvp um den Scope klein zu halten.
|
||
* Organizer-Gästeliste: Namensliste der Zusagen nur für Organisator sichtbar (über Organizer-Link). Gehört thematisch zu 009-guest-list, nicht zu 008-rsvp.
|
||
* Sicherheit/Missbrauchsschutz:
|
||
* Nicht-erratbare Event-Tokens (z.B. UUIDs)
|
||
* Event-Erstellung ist offen, kein Login/Passwort/Invite-Code nötig
|
||
* Max aktive Events als serverseitige Konfiguration (env variable)
|
||
* Honeypot-Felder in Formularen (verstecktes Feld das nur Bots ausfüllen → Anfrage ignorieren)
|
||
* Abgrenzungskriterien
|
||
* KEIN Chat
|
||
* KEIN Discovery Feature über die App: Ohne Zugangslink geht nichts
|
||
* KEINE Planung des Events! Also kein "wer macht/bringt was?", "was machen wir überhaupt?"
|
||
|
||
## Getroffene Designentscheidungen
|
||
|
||
Die folgenden Punkte wurden in Diskussionen bereits geklärt und sind verbindlich.
|
||
|
||
* RSVP-System:
|
||
* Ein Link pro Event (NICHT individuelle Einladungslinks pro Person — zu umständlich für den Veranstalter)
|
||
* Gäste geben beim RSVP einen Namen an, das reicht
|
||
* Duplikate durch versehentliche Mehrfachanmeldung: LocalStorage-Gerätebindung reicht als Schutz
|
||
* Bewusste Doppelanmeldung/Spam: Akzeptables Risiko, Veranstalter kann Einträge manuell löschen
|
||
* Geräte-Sync ohne Account ist nicht sauber lösbar und das ist okay
|
||
* Missbrauchsschutz:
|
||
* Rate Limiting: Bewusst rausgelassen — zu viel Infra-Overhead für den Scope
|
||
* Captcha: Bewusst rausgelassen — entweder Privacy-Problem (Google) oder hässlich
|
||
* Admin-Passwort/Invite-Code für Event-Erstellung: Bewusst rausgelassen — die App soll organisch weitergegeben werden können
|
||
* Erfahrungswert: Spliit-Instanz läuft auch komplett offen ohne nennenswerte Probleme
|
||
* Stattdessen pragmatische Maßnahmen: Nicht-erratbare Tokens, Ablaufdatum als Pflichtfeld, Max Events per Konfiguration, Honeypot-Felder
|
||
* Zielgruppe:
|
||
* Primär Freundeskreise, nicht die breite Öffentlichkeit
|
||
* Trotzdem: Die App hängt im Internet, also muss man grundlegende Absicherung haben
|
||
* Architektur (bereits entschieden):
|
||
* SPA + RESTful API Backend, kein SSR
|
||
* Datenbank: PostgreSQL, wird separat gehostet (nicht im App-Container — der Hoster betreibt seinen eigenen Postgres)
|
||
* Organizer-Authentifizierung: Zwei separate UUIDs pro Event — ein öffentliches Event-Token (in der URL, für Gäste) und ein geheimes Organizer-Token (in localStorage, für Verwaltung). Interne DB-ID ist ein Implementierungsdetail.
|
||
* App wird als einzelner Docker-Container ausgeliefert, verbindet sich per Konfiguration (env variable) mit der externen Postgres-Instanz
|
||
* Techstack:
|
||
* Backend: Java (neuste LTS Version), Spring Boot, Maven, Hexagonal/Onion Architecture
|
||
* Frontend: Vue 3 (mit Vite als Bundler, TypeScript, Vue Router)
|
||
* Architekturentscheidungen die NOCH NICHT getroffen wurden (hier darf nichts eigenmächtig entschieden werden!):
|
||
* (derzeit keine offenen Architekturentscheidungen)
|
||
|
||
## Nicht umgesetzte Feature-Ideen (ehemals Specs 009–026)
|
||
|
||
### 009 – Gästeliste
|
||
Organisator sieht alle RSVPs (Name, Status) und kann einzelne Einträge löschen.
|
||
* Nur mit gültigem Organizer-Token sichtbar
|
||
* Gäste ohne Token sehen keine Gästeliste
|
||
* Löschung serverseitig validiert
|
||
|
||
### 010 – Event bearbeiten
|
||
Organisator kann Titel, Beschreibung, Datum, Ort und Ablaufdatum ändern.
|
||
* Formular vorausgefüllt mit aktuellen Werten
|
||
* Ablaufdatum muss in der Zukunft liegen
|
||
* Ohne Organizer-Token kein Edit-UI sichtbar
|
||
|
||
### 011 – Event merken/bookmarken
|
||
Gäste können Events lokal merken, ohne RSVP abzugeben — rein clientseitig via localStorage.
|
||
* Kein Serverkontakt nötig
|
||
* Unabhängig vom RSVP-Status
|
||
* Auch bei abgelaufenen Events möglich
|
||
|
||
### 012 – Lokale Event-Übersicht
|
||
Startseite (`/`) zeigt alle getrackten Events (erstellt, zugesagt, gemerkt) aus localStorage.
|
||
* Zeigt Titel, Datum, Beziehungstyp (Organisator/Gast/Gemerkt)
|
||
* Vergangene Events als "beendet" markiert
|
||
* Einträge können entfernt werden
|
||
|
||
### 013 – Kalender-Export
|
||
.ics-Download (RFC 5545) mit Event-Details, optional webcal:// für Live-Updates.
|
||
* Stabile UID aus Event-Token (Re-Import aktualisiert statt dupliziert)
|
||
* Bei Absage: STATUS:CANCELLED im .ics
|
||
* Kein externer Kalenderservice kontaktiert
|
||
|
||
### 014 – Änderungen hervorheben
|
||
Geänderte Felder werden visuell hervorgehoben, wenn der Gast seit der letzten Änderung nicht mehr auf der Seite war.
|
||
* Server trackt `last_edited_at` + geänderte Feldnamen
|
||
* Client speichert `last_seen_at` in localStorage
|
||
* Privacy-freundlich: kein serverseitiges Read-Tracking
|
||
|
||
### 015 – Organisator-Updates
|
||
Organisator kann Textnachrichten im Event posten (Pinnwand-Stil).
|
||
* Chronologisch sortiert, löschbar durch Organisator
|
||
* Nach Ablauf kein Posting mehr möglich
|
||
* Ohne Organizer-Token kein Compose-UI
|
||
|
||
### 016 – Gast-Benachrichtigungen
|
||
Badge/Indikator bei ungelesenen Organisator-Updates, rein clientseitig via localStorage.
|
||
* Eigener Timestamp `updates_last_seen_at` (getrennt von Feld-Änderungen)
|
||
* Kein Indikator beim ersten Besuch
|
||
* Kein serverseitiges Tracking (Privacy)
|
||
|
||
### 017 – QR-Code
|
||
Event-Seite zeigt QR-Code mit der öffentlichen Event-URL.
|
||
* Serverseitig generiert (kein externer QR-Service)
|
||
* Download als SVG oder hochauflösendes PNG
|
||
* Auch bei abgelaufenen Events verfügbar
|
||
|
||
### 018 – Datenlöschung
|
||
Automatische Löschung aller Event-Daten nach Ablaufdatum (Privacy-Garantie).
|
||
* Scheduled Job oder Lazy Cleanup bei Zugriff
|
||
* Löscht Event, RSVPs, Updates, Bilder, Metadaten
|
||
* Idempotent, kein PII im Log
|
||
|
||
### 019 – Instanz-Limit
|
||
`MAX_ACTIVE_EVENTS` als Env-Variable begrenzt aktive Events für Self-Hoster.
|
||
* Nur nicht-abgelaufene Events zählen
|
||
* Unset/leer = unbegrenzt
|
||
* Serverseitige Durchsetzung bei Event-Erstellung
|
||
|
||
### 020 – PWA
|
||
Web App Manifest + Service Worker für Installierbarkeit und Offline-Caching.
|
||
* Standalone-Modus ohne Browser-Chrome
|
||
* Icon + Name auf Home-Screen
|
||
* Alle Assets selbstgehostet
|
||
|
||
### 021 – Farbthemen
|
||
Organisator wählt bei Erstellung ein vordefiniertes Farbthema für die Event-Seite.
|
||
* Nur auf der Gast-Seite angewendet (nicht global)
|
||
* Änderbar beim Bearbeiten
|
||
* Unabhängig von Dark/Light Mode
|
||
|
||
### 022 – Headerbild
|
||
Organisator sucht Headerbild über integrierte Unsplash-Suche.
|
||
* Serverseitig geproxied (Client kontaktiert nie Unsplash)
|
||
* Bild lokal gespeichert + Unsplash-Attribution
|
||
* Feature deaktiviert wenn kein API-Key konfiguriert
|
||
|
||
### 023 – Dark Mode
|
||
App erkennt `prefers-color-scheme` und bietet manuellen Toggle.
|
||
* Manuelle Auswahl in localStorage gespeichert
|
||
* Gilt für globales App-Chrome, nicht Event-Farbthemen
|
||
* Beide Modi WCAG AA konform
|
||
|
||
### 024 – Event absagen
|
||
Organisator kann Event absagen (mit optionaler Nachricht, Einweg-Transition).
|
||
* RSVPs werden nach Absage abgelehnt
|
||
* Absage-Nachricht nachträglich editierbar
|
||
* Kann nicht rückgängig gemacht werden
|
||
* Wenn Organisator Event auf der Eventlistenseite löscht, muss dabei das Event abgesagt werden (nicht nur lokal entfernen)
|
||
|
||
### 025 – Event löschen
|
||
Organisator löscht Event permanent und unwiderruflich.
|
||
* Entfernt alle zugehörigen Daten sofort
|
||
* localStorage-Eintrag wird entfernt, Redirect zu `/`
|
||
* Funktioniert in jedem Event-Status
|
||
|
||
### 026 – 404-Seite
|
||
Catch-all Route für ungültige Pfade mit "Seite nicht gefunden" und Link zur Startseite.
|
||
* Folgt dem Design System (Electric Dusk + Sora)
|
||
* WCAG AA konform
|
||
* Verhindert leere Seiten bei Fehlnavigation
|