Implement cross-club friendly match concept with invitations and shared matches
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 49s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 49s
- Added controllers for handling friendly match invitations and shared matches. - Created migration scripts for `friendly_match_invitation` and `friendly_match_shared` tables. - Developed models for `FriendlyMatchInvitation` and `FriendlyMatchShared`. - Established routes for managing invitations and shared matches. - Implemented services for business logic related to invitations and shared matches. - Documented the concept plan for the new feature including API endpoints and data models.
This commit is contained in:
153
docs/friendly-match-shared-concept-plan.md
Normal file
153
docs/friendly-match-shared-concept-plan.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Konzeptplan: Vereinsuebergreifende Freundschaftsspiele
|
||||
|
||||
## Ziel
|
||||
- Ein gemeinsames Freundschaftsspiel-Objekt fuer zwei Vereine (statt zwei isolierter Eintraege).
|
||||
- Auffindbarkeit bestehender Spiele ueber Name, Datum und Startzeit.
|
||||
- Einladungsprozess (anbieten, annehmen, ablehnen).
|
||||
- Live-Synchronisierung zwischen beiden Vereinen per Socket.
|
||||
|
||||
## Produktumfang (MVP)
|
||||
- [x] Vereinsuebergreifendes Shared-Match-Datenmodell einfuehren.
|
||||
- [x] Match-Finder fuer Name, Datum, Startzeit implementieren.
|
||||
- [x] Einladung erstellen (Zielverein aus Vereinsliste + Freitext).
|
||||
- [x] Einladung im System speichern.
|
||||
- [x] E-Mail-Benachrichtigung an hinterlegte Personen des Zielvereins versenden.
|
||||
- [x] Einladung annehmen: Shared-Match fuer beide Vereine erstellen.
|
||||
- [x] Einladung ablehnen: Einladung loeschen.
|
||||
- [x] Socket-Sync fuer beide Vereine bei Aenderungen am Shared-Match.
|
||||
|
||||
## Datenmodell
|
||||
### Neue Tabellen
|
||||
- [x] `friendly_match_shared`
|
||||
- [x] `id`
|
||||
- [x] `home_club_id`
|
||||
- [x] `guest_club_id`
|
||||
- [x] `date`
|
||||
- [x] `start_time`
|
||||
- [x] `match_name` (oder Home/Gast-Namenstruktur)
|
||||
- [x] `location_*` Felder
|
||||
- [x] `match_system`, `singles_count`, `doubles_count`, `winning_sets`
|
||||
- [x] `home_participants`, `guest_participants`
|
||||
- [x] `result_details`, `home_match_points`, `guest_match_points`
|
||||
- [x] `is_completed`
|
||||
- [x] `created_by_user_id`
|
||||
- [x] `created_from_invitation_id`
|
||||
- [x] `created_at`, `updated_at`
|
||||
- [x] `friendly_match_invitation`
|
||||
- [x] `id`
|
||||
- [x] `from_club_id`
|
||||
- [x] `to_club_id`
|
||||
- [x] `proposed_date`
|
||||
- [x] `proposed_start_time`
|
||||
- [x] `proposed_match_name`
|
||||
- [x] `message` (Freitext)
|
||||
- [x] `status` (`pending`, `accepted`)
|
||||
- [x] `created_by_user_id`
|
||||
- [x] `created_at`, `updated_at`
|
||||
|
||||
### Indizes und Constraints
|
||||
- [x] Index auf `friendly_match_shared (home_club_id, date, start_time)`.
|
||||
- [x] Index auf `friendly_match_shared (guest_club_id, date, start_time)`.
|
||||
- [x] Index auf `friendly_match_invitation (to_club_id, status, proposed_date)`.
|
||||
- [x] Verhindern, dass ein Verein sich selbst einlaedt (`from_club_id != to_club_id`).
|
||||
|
||||
## Backend API
|
||||
### Finder
|
||||
- [x] `GET /api/friendly-matches/find`
|
||||
- [x] Query-Parameter: `clubId`, `name`, `date`, `startTime`.
|
||||
- [x] Exakt-Matches und Kandidaten mit `confidence` zurueckgeben.
|
||||
|
||||
### Einladungen
|
||||
- [x] `POST /api/friendly-match-invitations/:clubId`
|
||||
- [x] Payload: `toClubId`, `date`, `startTime`, `matchName`, `message`.
|
||||
- [x] `GET /api/friendly-match-invitations/:clubId/incoming`
|
||||
- [x] `GET /api/friendly-match-invitations/:clubId/outgoing`
|
||||
- [x] `POST /api/friendly-match-invitations/:clubId/:invitationId/accept`
|
||||
- [x] `POST /api/friendly-match-invitations/:clubId/:invitationId/decline`
|
||||
|
||||
### Shared Matches
|
||||
- [x] `GET /api/friendly-matches/shared/:clubId`
|
||||
- [x] `PUT /api/friendly-matches/shared/:clubId/:matchId`
|
||||
- [x] `PATCH /api/friendly-matches/shared/:clubId/:matchId/players`
|
||||
- [x] Optional: `DELETE /api/friendly-matches/shared/:clubId/:matchId`
|
||||
|
||||
## Rechte und Zugriff
|
||||
- [x] Zugriff pruefen: User muss in beteiligtem Verein freigeschaltet sein.
|
||||
- [x] Schreibrechte auf Shared-Match nur mit `schedule.write`.
|
||||
- [x] Einladung annehmen/ablehnen nur fuer `to_club_id`.
|
||||
|
||||
## E-Mail-Versand
|
||||
- [x] Empfaengerermittlung: `user_club` (approved=true) + `user.email` fuer Zielverein.
|
||||
- [x] Neue Mailfunktion in `emailService` fuer Einladung.
|
||||
- [x] Inhalt: einladender Verein, Matchdaten, Freitext, Aktion (annehmen/ablehnen in App).
|
||||
- [x] Fehlerbehandlung: Einladung bleibt erhalten, falls Mailversand fehlschlaegt (mit Log).
|
||||
|
||||
## Matching-Logik (Name/Datum/Startzeit)
|
||||
- [x] Normalisierung von Namen (trim, lowercase, Sonderzeichenbereinigung).
|
||||
- [x] Datum exakt matchen.
|
||||
- [x] Startzeit exakt matchen (MVP).
|
||||
- [x] Teamnamen in beide Richtungen pruefen (home/guest vertauscht).
|
||||
- [x] `confidence`-Stufen definieren (`exact`, `high`, `medium`).
|
||||
|
||||
## Socket-Sync
|
||||
- [x] Neue Socket-Events definieren:
|
||||
- [x] `friendly:invitation:created`
|
||||
- [x] `friendly:invitation:accepted`
|
||||
- [x] `friendly:invitation:declined`
|
||||
- [x] `friendly:shared:match:updated`
|
||||
- [x] `friendly:shared:match:deleted`
|
||||
- [x] Bei Shared-Match-Aenderung an beide Club-Raeume senden (`club-<home>`, `club-<guest>`).
|
||||
- [x] Frontend und Mobile auf neue Events subscriben.
|
||||
|
||||
## Frontend (Web)
|
||||
- [x] Bereich "Einladungen" (Eingehend/Ausgehend) in Freundschaftsspielen.
|
||||
- [ ] Dialog "Verein einladen": Zielverein aus bestehender Vereinsliste + Freitext.
|
||||
- [x] Buttons fuer annehmen/ablehnen in eingehenden Einladungen.
|
||||
- [ ] Finder-UI fuer Name/Datum/Startzeit.
|
||||
- [x] Shared-Match-Kennzeichnung in Liste und Detaildialog.
|
||||
- [x] Socket-Event-Handling fuer Echtzeitupdates beider Vereine.
|
||||
|
||||
## Mobile (Android + Shared)
|
||||
- [x] API-Endpoints in `MatchesApi` erweitern.
|
||||
- [x] State-Manager fuer Einladungen und Shared-Matches erweitern.
|
||||
- [ ] UI fuer Einladungen (eingehend/ausgehend) einbauen.
|
||||
- [ ] Finder-UI fuer Name/Datum/Startzeit einbauen.
|
||||
- [ ] Shared-Match-Bearbeitung fuer beide Vereine synchronisieren.
|
||||
- [x] Socket-Events fuer Friendly-Shared-Flow verarbeiten.
|
||||
|
||||
## Migration und Abwaertskompatibilitaet
|
||||
- [ ] Bestehende `friendly_match`-Eintraege unveraendert weiter lesbar halten.
|
||||
- [ ] Schrittweise Migration/Koexistenzstrategie dokumentieren.
|
||||
- [ ] Optionales Mapping alter Einzel-Eintraege auf Shared-Matches definieren.
|
||||
|
||||
## Tests
|
||||
### Backend
|
||||
- [ ] Unit-Tests fuer Match-Finder.
|
||||
- [ ] Unit-Tests fuer Einladung (create/accept/decline).
|
||||
- [ ] Integrations-Tests fuer Shared-Match-CRUD.
|
||||
- [ ] Berechtigungstests (falscher Verein, fehlende Rechte).
|
||||
|
||||
### Frontend/Mobile
|
||||
- [ ] UI-Tests fuer Einladung erstellen/anzeigen/entscheiden.
|
||||
- [ ] UI-Tests fuer Finder.
|
||||
- [ ] Realtime-Tests mit zwei gleichzeitig eingeloggten Vereinen.
|
||||
|
||||
### End-to-End
|
||||
- [ ] Verein A laedt Verein B ein.
|
||||
- [ ] E-Mail wird versendet.
|
||||
- [ ] Verein B nimmt an, Shared-Match entsteht.
|
||||
- [ ] Aenderung in Verein A erscheint sofort in Verein B und umgekehrt.
|
||||
- [ ] Ablehnung loescht Einladung vollstaendig.
|
||||
|
||||
## Rollout in Phasen
|
||||
- [ ] Phase 1: Datenmodell + Read-APIs.
|
||||
- [ ] Phase 2: Einladung + E-Mail.
|
||||
- [ ] Phase 3: Accept/Decline + Shared-Match-Erzeugung.
|
||||
- [ ] Phase 4: Shared-Match-Edit + Socket-Sync.
|
||||
- [ ] Phase 5: Finder + UX-Polish + E2E-Hardening.
|
||||
|
||||
## Offene Entscheidungen
|
||||
- [ ] Mehrfache parallele Einladungen fuer gleiche Daten erlauben oder verhindern?
|
||||
- [ ] Match-Name als einzelnes Feld oder Home/Gast getrennt fuehren?
|
||||
- [ ] Konfliktstrategie bei gleichzeitiger Bearbeitung (Last-Write-Wins vs. Versionierung)?
|
||||
- [ ] Ablehnung immer hard delete oder auditierbar speichern?
|
||||
Reference in New Issue
Block a user