185 Commits

Author SHA1 Message Date
stefan 99cbfeef11 fix: aktualisiere Logs und dokumentiere Workflow-Änderungen
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 23:03:30 +02:00
stefan ed1cb507cf chore: entferne überflüssigen Abschnitt aus README
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 22:40:01 +02:00
stefan f4fab93a6c fix: setze Windows-Build-Workflow auf manuell und dokumentiere ARM64-Blockade
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 22:37:04 +02:00
stefan 9b9f60a071 fix: stabilisiere CI-Workflow und passe JAR-Namensmuster an
Feature Build — Windows MSI (via Conveyor) / 📦 Windows .msi Packaging (push) Failing after 2m3s
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 22:29:17 +02:00
stefan d219176609 fix: stabilisiere CI-Workflow und passe JAR-Namensmuster an
Feature Build — Windows MSI (via Conveyor) / 📦 Windows .msi Packaging (push) Failing after 1m19s
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 22:24:15 +02:00
stefan 7411038b3b fix: entferne CI-Blockade und aktualisiere Conveyor-Konfiguration
Feature Build — Windows MSI (via Conveyor) / 📦 Windows .msi Packaging (push) Failing after 8m16s
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 21:36:28 +02:00
stefan 77ee608094 feat: implementiere Cross-Packaging für Windows-MSI via Conveyor auf Linux
Feature Build — Windows MSI (via Conveyor) / 📦 Windows .msi Packaging (push) Has been skipped
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-06 21:26:39 +02:00
stefan 9bee2f233e fix: verbessere Fallback-Logik für Gerätenamen in JmDnsDiscoveryService
Feature Build — Windows MSI / 📦 Windows .msi Packaging (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-05 23:21:54 +02:00
stefan c317147ca4 feat: verbesserte Netzwerkfähigkeit und Chat-Test integriert
- **Discovery:** Unterstützung für Multi-Interface-Broadcast und manuelle IP-Eingabe.
- **UI:** Chat-Test für Verbindungsprüfung hinzugefügt.
- **ViewModel:** Datenübertragungslogik (Ping/Pong, Chat) implementiert.
- **Workflow:** Windows-MSI-Build als separaten Job hinzugefügt.

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-05 23:18:25 +02:00
stefan 15222b5453 chore: entferne veraltete Architekturdokumente
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-05 21:23:02 +02:00
stefan 6f15ada447 chore: dc-gui aus docker-compose.yaml auskommentiert
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-05 17:48:23 +02:00
stefan 8b294d947d docs(readme): Gitea-Test-Abschnitt hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-05 15:13:42 +02:00
stefan 66c8838379 docs(journal): POC-Status aktualisiert, Master-UX-Fixes dokumentiert und Follow-up geplant
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-30 16:49:04 +02:00
stefan 022ffccccd feat(device-initialization, core): mDNS-Discovery erweitert, Geräte- und UI-Interaktion optimiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-30 15:58:24 +02:00
stefan 8ab6ab1c2a feat(core, device-initialization): Netzwerk-Discovery verbessert, IP-Binding hinzugefügt und UI optimiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-30 12:12:52 +02:00
stefan 46d993e47f docs(journal): Roadmap und Schritte für Plan-USB-Hardware-PoC ergänzt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 15:08:37 +02:00
stefan 62f9472695 feat(device-initialization, core): Plan-USB-Backup hinzugefügt, BackupService implementiert und UI-Export-Button ergänzt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 15:08:11 +02:00
stefan b94984043c feat(device-initialization, core): Theme-Support hinzugefügt, Fokus- und UI-Optimierungen umgesetzt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 15:03:03 +02:00
stefan fd78404d72 refactor(design-system): Tooltip-Positionierung auf Above geändert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 12:53:15 +02:00
stefan 884ccc0db5 refactor(core, device-initialization): Icons auf AutoMirrored geändert, Tooltip-Positionierung und Alignment vereinfacht
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 12:50:27 +02:00
stefan 8ecc9fbe52 feat(device-initialization, core): Unterstützung für Hilfe-Tooltips, Netzwerk-Interface-Auswahl & Discovery-Radar ergänzt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 12:45:49 +02:00
stefan d0edfa2538 feat(docs): neue ADRs für Plan-USB, Offline-Lizenzierung & Netzwerk-Discovery hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-29 12:35:22 +02:00
stefan e1bf4d8454 docs: MASTER_ROADMAP aktualisiert und ältere Phasen zusammengefasst
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-28 21:19:27 +02:00
stefan 5a08361f83 docs: add unversioned results and event documents for Neumarkt and St. Poelten
Co-authored-by: Junie <junie@jetbrains.com>
2026-04-28 13:25:25 +02:00
stefan 5d6d9efd27 feat(veranstaltung): migrate event wizard to declarative orchestrator (ADR-0025). Transferred logic from EventFlowSample to EventWizardFlow. Renamed Demo* components to EventWizard*. Added OETO-compliant steps: TurnierKonfiguration, BewerbKonfiguration, AbteilungKonfiguration, Summary. Updated DSL flow to include full sequential path. --trailer "Co-authored-by: Junie <junie@jetbrains.com>" 2026-04-28 13:24:27 +02:00
stefan d493734660 ### fix: 41 verbessere Lade- und Navigationslogik
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m50s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m16s
- **OnlineNennungFormular:** `isLoading`-State präziser verwaltet und Erfolgsmeldungen erweitert.
- **WebMainScreen:** Hashwechsel-Logik verbessert, Erfolgsscreen modularisiert.
- **UI-Version:** auf `v2026-04-23.41 - UI NAVIGATION FIX` aktualisiert.
2026-04-23 20:42:01 +02:00
stefan 0aaa160b95 ### fix: 40 aktualisiere SMTP-Port und erweitere Timeout-Parameter
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m53s
- **.env:** SMTP-Port auf `587` geändert.
- **application.yaml:** Timeout-Parameter (`connectiontimeout`, `timeout`, `writetimeout`) hinzugefügt.
- **MailServiceApplication:** Logging um SMTP-Timeout ergänzt.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.40 - SMTP PORT FORCE` aktualisiert.
2026-04-23 20:14:45 +02:00
stefan 03184aa951 ### fix: 39 finalisiere SMTP-Härtung und UI-Synchronisation
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m57s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m56s
- **application.yaml:** Parameter für SMTP-Authentifizierung (`STARTTLS_REQUIRED`) ergänzt.
- **MailServiceApplication:** Logging für SMTP-Passwort und Umgebungsvariablen erweitert.
- **WebMainScreen:** Erfolgsscreen-Logik bei API-Status `200 OK` optimiert.
- **dc-planb.yaml:** SMTP-Konfiguration mit STARTTLS zwingend ergänzt.
- **.env:** Korrekten SMTP-Host und `MAIL_SERVICE_URL` hinzugefügt.
- **Docs:** Änderungslog dokumentiert.
- **UI-Version:** auf `v2026-04-23.39 - FINAL SMTP & UI SYNC` aktualisiert.
2026-04-23 19:47:15 +02:00
stefan 34bd42a009 ### fix: 38 aktualisiere SMTP-Parameter und verbessere Fehlerbehandlung
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m52s
- **application.yaml:** SMTP-Parameter auf Umgebungsvariablen umgestellt.
- **MailServiceApplication:** Logging erweitert, Passwort maskiert.
- **WebMainScreen:** Fehlerbehandlung und Logging für API-Antworten optimiert, Versionsmarker auf `v2026-04-23.38 - SMTP & UI FINAL FORCE` aktualisiert.
2026-04-23 19:08:11 +02:00
stefan 897394e27e ### fix: 37 aktualisiere SMTP-Konfiguration und Versionsmarker
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m56s
- **application.yaml:** SMTP-Parameter (Host, Port, Benutzer, Passwort, Auth/StartTLS) hart kodiert.
- **MailServiceApplication:** Logging-Text für SMTP-Konfiguration angepasst.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.37 - SMTP HARD-CODED` aktualisiert.
2026-04-23 18:40:29 +02:00
stefan 9ab914dbfb ### fix: 36 verbessere SMTP-Logging und Versionsmarker
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m55s
- **dc-planb.yaml:** SMTP-Parameter hart codiert (Host, Port, Benutzer, Passwort).
- **mail-service:** Logging erweitert, SMTP-Konfiguration (`host`, `port`, `user`) wird angezeigt.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.36 - SMTP DIAGNOSE` aktualisiert.
2026-04-23 18:15:49 +02:00
stefan 9659fe3f8a ### fix: 35 verbessere SMTP-Konfiguration und Versionsmarker
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m41s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m51s
- **dc-planb.yaml:** Hard-Coded Passwort entfernt, AUTH/STARTTLS Flags erzwungen.
- **mail-service:** Nutzung der World4You-Credentials gesichert.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.35 - SMTP FIX` aktualisiert.
2026-04-23 17:51:38 +02:00
stefan 5cbf4fdfc0 ### fix: 34 erweitere Logging für Debugging und Callback-Analyse
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m41s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m12s
- **OnlineNennungFormular:** `println`-Logs für Callback-Status (`success`, `error`) hinzugefügt.
- **WebMainScreen:** Erfolgs-, Fehler- und State-Logs ergänzt.
- **Docs:** Neue Logging-Strategie dokumentiert.
- **UI:** Versionsmarker auf `v2026-04-23.34 - CALLBACK LOGGING` aktualisiert.
2026-04-23 17:17:37 +02:00
stefan bd06efe05d ### fix: 33 verbessere API-Antwort-Handling und Versionsmarker
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m13s
- **NennungRemoteRepository:** Handling für leeren JSON-Body angepasst.
- **MailController:** JSON-Antwort mit Erfolgsstatus und ID hinzugefügt.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.33 - JSON RESPONSE FIX` aktualisiert.
2026-04-23 16:59:11 +02:00
stefan 23c3e40390 ### fix: 32 erweitere Debug-Logging und Proxy-Strategie
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m52s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m13s
- **NennungRemoteRepository:** API-Logs erweitert (Antwortstatus und Body).
- **Caddyfile:** Strategie-Version auf `v32` aktualisiert.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.32 - PROXY DEBUG` angepasst.
2026-04-23 16:39:10 +02:00
stefan 1201755077 ### fix: 31 verbessere Fehlermeldungen und Same-Origin-Strategie
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m58s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m12s
- **OnlineNennungFormular:** Fehlermeldung für Server-Aufrufe präzisiert.
- **Caddyfile:** Header `X-Forwarded-Proto` hinzugefügt und Strategie-Version auf `v31` aktualisiert.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.31 - SAME-ORIGIN v3` aktualisiert.
2026-04-23 16:21:16 +02:00
stefan 162e2ef414 ### fix: 30 verbessere Fehlertransparenz und Debug-Logs
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m44s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m12s
- **OnlineNennungFormular:** Hinweis auf Browser-Konsole zu Fehlermeldungen hinzugefügt.
- **MailController:** Zusätzliche Debug-Logs für SMTP-Flows (Sendeversuch und Fehlerbehandlung).
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.30 - COMPILE FIX` aktualisiert.
2026-04-23 16:02:11 +02:00
stefan 3f291c907c ### fix: v28 verbessere Same-Origin-Strategie und Fehlerbehandlung
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m46s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m40s
- **PlatformConfig.wasmJs.kt:** API-URLs auf leere Strings geändert (Root-Proxy).
- **OnlineNennungFormular:** Fehlermeldungen bei Versandfehlern präzisiert.
- **NennungRemoteRepository:** Detaillierte Fehlerrückmeldungen hinzugefügt.
- **Caddyfile:** Reverse Proxy angepasst, Header und Strategie-Version aktualisiert.
- **WebMainScreen:** Versionsmarker auf `v2026-04-23.28 - SAME-ORIGIN v2` aktualisiert.
2026-04-23 15:40:15 +02:00
stefan 251647a6ab ### fix: implementiere Same-Origin-Strategie zur Umgehung von CORS
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m38s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m35s
- **PlatformConfig.wasmJs.kt:** API-URLs auf relative Pfade (`/api`) geändert.
- **Caddyfile:** `/api/*` Anfragen intern weitergeleitet, `/api` Präfix entfernt. Header angepasst.
- **UI:** Versionsmarker auf `v2026-04-23.27 - SAME-ORIGIN PROXY` aktualisiert.
- **Docs:** Analyse und Lösung zur neuen Strategie hinzugefügt.
2026-04-23 15:16:08 +02:00
stefan 277254ebbd ### fix: verbessere CORS-Handling und UI-Markierungen
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m51s
- **Caddyfile:** Ersetze `Access-Control-Allow-Origin` durch `*`, entferne `Access-Control-Allow-Credentials`, füge `Access-Control-Expose-Headers` hinzu.
- **GlobalSecurityConfig:** Lockere `allowedOrigins`, `allowedOriginPatterns` und `exposedHeaders` auf `*`, setze `allowCredentials` auf `false`.
- **MailServiceApplication:** Passe CORS-Mapping durch `allowedOrigins` und `allowCredentials` an.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.26 - NUCLEAR CORS v2`.
2026-04-23 14:42:49 +02:00
stefan f97bfeff47 ### fix: verbessere CORS-Handling im Caddy-Proxy
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m36s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m51s
- **Caddyfile:** `Access-Control-Allow-Headers` auf `*` gelockert, Versionsmarkers angepasst.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.25 - CADDY CATCH-ALL CORS`.
- **Docs:** Ergänzung der Analyse und Lösung für Version 25.
2026-04-23 14:21:26 +02:00
stefan 02a778751a ### fix: verbessere CORS-Handling im Caddy-Proxy
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m39s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m53s
- **Caddyfile:** OPTIONS-Handling optimiert: Hinzufügen spezifischer Header (`X-Requested-With`), Entfernen von `*` und leere Response (`respond "" 204`) eingeführt.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.24 - CADDY CORS FINAL BOSS`.
- **Docs:** Erweiterung der Analyse um Lösung und Status für Version 24.
2026-04-23 14:02:03 +02:00
stefan af0ece8ded ### fix: verbessere CORS-Handling im Caddy-Proxy
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m56s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m51s
- **Caddyfile:** Separates Handling für OPTIONS-Requests mit spezifischen Headern eingeführt, `defer` entfernt.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.23 - CADDY CORS OPTIONS FIX`.
- **Docs:** Ergänzung der Analyse und Lösung für Version 23.
2026-04-23 13:41:04 +02:00
stefan 03fa74abba ### fix: verbessere CORS-Handling im Caddy-Proxy
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m55s
- **Caddyfile:** Hinzufügen des `defer`-Flags zur korrekten Verarbeitung von CORS-Headern.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.22 - CADDY DEFER CORS FIX`.
- **Docs:** Ergänzung der Analyse und Lösung für Version 22.
2026-04-23 13:25:06 +02:00
stefan 71aea3f41d ### fix: verbessere CORS-Handling im Caddy-Proxy
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m54s
- **Caddyfile:** Verlagerung des CORS-Handlings in den Reverse Proxy, inkl. Unterstützung für Preflight-Anfragen und Header-Optimierungen.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.21 - CADDY CORS PROXY FIX`.
- **Docs:** Ergänzung der Problem- und Lösungshistorie für Version 21.
2026-04-23 13:02:52 +02:00
stefan 16c8674eff ### fix: verbessere CORS-Konfiguration und DNS-Verifizierung
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m55s
- **GlobalSecurityConfig:** Optimierung von `allowedOriginPatterns` und Hinzufügen von `exposedHeaders`.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.20 - CLOUDFLARE DNS VERIFIED`.
- **Docs:** Hinzufügen eines Screenshots zur Cloudflare-DNS-Analyse.
2026-04-23 12:43:39 +02:00
stefan df5276abf2 ### fix: verbessere CORS-Konfiguration
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Has been cancelled
- **GlobalSecurityConfig:** Lockerung von `allowedOriginPatterns` auf `*`.
- **MailServiceApplication:** Hinzufügen einer redundanten `WebMvcConfigurer` Bean für zusätzliches CORS-Mapping.
- **UI:** Aktualisierung des Versionsmarkers auf `v2026-04-23.19 - NUCLEAR CORS FIX`.
2026-04-23 12:35:50 +02:00
stefan 636ecc9883 ### fix: verbessere CORS-Konfiguration
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m51s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m54s
- **GlobalSecurityConfig:** Füge `allowedOriginPatterns` für Subdomains von `mo-code.at` hinzu.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.18 - RADICAL CORS PERMISSIVENESS`.
2026-04-23 12:15:49 +02:00
stefan 92950dbbe6 ### fix: behebe fehlende Spring Security Abhängigkeiten
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m40s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 9m25s
- **build.gradle.kts:** Füge `spring-boot-starter-security`, `spring-boot-starter-oauth2-resource-server` und `infrastructure:security` hinzu.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.17 - SECURITY DEPENDENCY FIX`.
2026-04-23 11:47:51 +02:00
stefan 5c51664e6c ### fix: behebe CORS- und Config-Probleme
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m58s
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
- **MailServiceApplication:** Füge `scanBasePackages` hinzu, um `GlobalSecurityConfig` korrekt zu laden.
- **GlobalSecurityConfig:** Erlaube Zugriff auf `/api/mail/nennung` ohne Authentifizierung.
- **MailController:** Entferne redundante `@CrossOrigin` Annotation.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.16 - CORS & CONFIG FIX`.
2026-04-23 11:16:18 +02:00
stefan 3244efd5e0 ### fix: behebe CORS-Probleme und Stabilitätsfehler
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 6m0s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 3m55s
- **MailController:** Erweitere `@CrossOrigin`-Headers und Methoden für Preflight-Checks.
- **GlobalSecurityConfig:** Reaktiviere CORS und füge explizite `CorsConfigurationSource` hinzu.
- **Tests:** Fix für `NoSuchBeanDefinitionException` bei Integrationstests.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.15 - CORS STABILITY`.
2026-04-23 10:53:55 +02:00
stefan af02e14f2d ### feat: verbessere Feedback- und Fehlerhandling im Nennformular
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 6m0s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m39s
- **OnlineNennungFormular:** Ladeindikator und Fehleranzeige bei API-Fehlermeldungen hinzugefügt.
- **WebMainScreen:** Navigation zum Erfolgsscreen erfolgt erst nach erfolgreicher API-Bestätigung.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.11 - NETWORK STATUS FIX`.
2026-04-23 10:16:16 +02:00
stefan 8730ffa7db ### feat: verbessere Feedback- und Fehlerhandling im Nennformular
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m51s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m44s
- **OnlineNennungFormular:** Ladeindikator und Fehleranzeige bei API-Fehlermeldungen hinzugefügt.
- **WebMainScreen:** Navigation zum Erfolgsscreen erfolgt erst nach erfolgreicher API-Bestätigung.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.11 - NETWORK STATUS FIX`.
2026-04-23 09:34:59 +02:00
stefan f7d11ccf97 ### feat: verbessere Feedback- und Fehlerhandling im Nennformular
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m49s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m13s
- **OnlineNennungFormular:** Ladeindikator und Fehleranzeige bei API-Fehlermeldungen hinzugefügt.
- **WebMainScreen:** Navigation zum Erfolgsscreen erfolgt erst nach erfolgreicher API-Bestätigung.
- **UI:** Aktualisiere Versionsmarker auf `v2026-04-23.11 - NETWORK STATUS FIX`.
2026-04-23 09:06:24 +02:00
stefan 76e6cebd90 ### fix: behebe HTTPS- und CORS-Probleme
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m51s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m5s
- **MailController:** Erweitere `@CrossOrigin`-Whitelist um `https://app.mo-code.at`.
- **dc-planb.yaml:** Passe API-URLs auf HTTPS an.
- **WebMainScreen:** Aktualisiere UI-Versionsmarker auf `v2026-04-23.10 - HTTPS FIX`.
2026-04-23 08:31:15 +02:00
stefan dbbca96c69 ### feat: verbessere PDF-Handling und füge neuen Versionsmarker hinzu
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m52s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m19s
- **WebMainScreen:** Implementiere Öffnen von Ausschreibungen (PDFs) in neuem Tab.
- **UI:** Ergänze dezentralen Versions-Marker in der Web-Oberfläche.
- **Docker-Publish:** Kopiere Turnier-Ausschreibungen (PDFs) in den Zielordner.
- **Assets:** Füge neue PDF-Dateien für Neumarkt2026 hinzu.
2026-04-23 08:11:15 +02:00
stefan eea022b862 ### feat(WebMainScreen, OnlineNennungFormular, index.html)
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m58s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m18s
- **WebMainScreen:** Implementiere hash-basiertes Routing für URLs und Synchronisation mit Adressleiste.
- **OnlineNennungFormular:** Aktualisiere Versionsanzeige mit neuer Farbe und Stil.
- **index.html:** Füge Runtime-Konfiguration per JavaScript hinzu (API, Mail, Keycloak).
2026-04-23 07:16:19 +02:00
stefan 6de5b55810 - **feat(OnlineNennungFormular):** verbessere Anzeige der Versionsnummer mit hervorgehobener Formatierung.
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m58s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m21s
- **chore(Caddy):** füge Cache-Buster für `meldestelle-web.js` hinzu.
2026-04-23 06:36:33 +02:00
stefan 07bd114df1 - **feat(OnlineNennungFormular):** Zeige eindeutige Versionsnummer zur besseren Nachverfolgbarkeit an.
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 6m11s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m17s
- **chore(Caddyfile):** Passe Caching-Strategie für `.wasm` und `.js` zur Vermeidung von Ladeproblemen während der Entwicklung an.
2026-04-23 06:14:39 +02:00
stefan 84d38f5eb5 chore: aktualisiere Dockerfile und CI-Workflow
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 2m48s
- **Dockerfile:** Füge `BUILD_DATE` als Argument hinzu, um Layer-Cache zu invalidieren.
- **CI:** Aktualisiere Build-Args mit `BUILD_DATE` aus Commit-Timestamp.
2026-04-23 05:53:26 +02:00
stefan 9db85236ec chore: füge Leerzeile zur README hinzu
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m28s
2026-04-23 05:23:48 +02:00
stefan f2a6078421 ### feat: erweitere und optimiere Online-Nennformular
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Has been cancelled
- **Display:** Link zur Nennungs-URL nur für Desktop hinzugefügt.
- **Inputs:** Unterstütze `singleLine` in allen Texteingabefeldern.
- **UX:** Hinzufügen von `ImeAction.Done` für Bemerkungen mit direkter Verarbeitung bei Abschluss.
2026-04-23 05:18:19 +02:00
stefan 568d9dbb32 ### feat: optimiere Online-Nennformular und Turnier-Integration
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 6m27s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 4m18s
- **`OnlineNennungFormular.kt`:**
  - Erweiterung um Felder für Telefon und Pferdename.
  - Dynamische Validierung und UI-Anpassungen für mobile Geräte.
  - Zusätzliche Bewerbslisten und Auswahlbeschränkungen hinzugefügt.
- **`WebMainScreen.kt`:**
  - Aktualisierte Turniermetadata und verbesserte Responsivität.
2026-04-23 04:48:51 +02:00
stefan f620f46d15 ### chore: aktualisiere Docker-Publish-Workflow
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m47s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Successful in 2m48s
- Entferne veraltete Cache- und Bereinigungsschritte.
- Implementiere neue Staging-Methode für Web-Assets ohne rsync.
- Aktualisiere Tags für Docker-Build und kommentiere ungenutzte Build-Args aus.
- Füge neuen Screenshot für Dokumentation hinzu.
2026-04-23 03:17:22 +02:00
stefan 46d3d7cf35 ### chore: aktualisiere Plan-B-Konfiguration und CI-Workflows
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 6m11s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 4m20s
- **dc-planb.yaml:** Passe Ports und füge Zipkin-Deaktivierung hinzu.
- **Docker-Publish:** Ergänze Bereinigung und Sicherheitsprüfung für Web-Assets.
2026-04-23 02:41:57 +02:00
stefan cb22b1bb96 chore: aktualisiere CI-Workflows
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been skipped
- **Workflows:** Deaktivierbar via `DESKTOP_CI_ENABLED` Repo-Variable.
- **Workflows:** Ergänze zusätzliche Prüfung auf Plan-B-Commits `[planb]`.
2026-04-23 00:40:11 +02:00
stefan 5544b04b07 ### chore: aktualisiere Desktop-Test-Workflow
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 4m23s
- **Workflows:** Begrenze Ausführung auf Änderungen am Desktop-Shell-Modul oder Workflow-Datei.
- **Workflows:** Füge `workflow_dispatch` für manuelles Starten hinzu.
- **Tests:** Korrigiere Run-Name für headless Tests (`xvfb`).
2026-04-23 00:29:55 +02:00
stefan 49d8b205d7 ### chore: aktualisiere Desktop-Test-Workflow
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 4m27s
- **CI:** Ergänze Installation von `xauth` im Desktop-Test-Workflow.
2026-04-23 00:14:36 +02:00
stefan f296a076dc ### chore: aktualisiere Docker-Build und CI für Web-Assets
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 55s
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m56s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 3m57s
- **Dockerfile:** Passe COPY-Pfad für Web-Assets auf neuen CI-Workflow an.
- **CI:** Füge Schritt zum Staging von Web-Assets vor dem Docker-Build hinzu.
2026-04-23 00:02:33 +02:00
stefan 1caefe6603 ### feat: optimiere Plan-B-Builds und CI/CD-Workflows
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been skipped
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m56s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 6m57s
- **Docker-Publish:** Reduziere auf Mail-Service und Web-App für schnellere Builds.
- **Workflows:** Überspringe Plan-B-Builds basierend auf Commit-Message ([planb]).
- **Frontend:** Aktualisiere Build-Skripte für Wasm-Distribution statt JS.
2026-04-22 23:41:03 +02:00
stefan 6b690232ff ### feat: füge Mail-Service-
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 58s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 5m59s
Build and Publish Docker Images / build-and-push (., backend/services/mail/Dockerfile, mail-service, mail-service) (push) Successful in 5m48s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m2s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 1m52s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m44s
2026-04-22 23:17:19 +02:00
stefan 309834d90c ### feat: verbessere DTO-Handling und füge Feature-Flag hinzu
- **NennungViewModel:** Definiere lokales `NennungDto` zur Optimierung der Backend-Response-Verarbeitung.
- **MailPollingService:** Ergänze Bedingung für Scheduler-Aktivierung (`ConditionalOnProperty`).
- **.env & application.yaml:** Füge `MAIL_POLLING_ENABLED` als Feature-Flag hinzu.
- **Dependencies:** Refaktor Import-Reihenfolge für Konsistenz.
2026-04-22 20:27:04 +02:00
stefan 8b44edda90 ### feat: aktualisiere Netzwerk-URLs und vereinfachte Online-Nennung
- **PlatformConfig:** Passe Standard-URLs für lokale Dienste an (`8092` statt `8083/8085`).
- **OnlineNennungFormular:** Entferne zusätzliche Felder und vereinfachere Validierungslogik.
- **OnlineNennungViewModel:** Ersetze HttpClient-Logik durch Repository-Injektion.
- **DI:** Aktualisiere Dependencies für `NennungRemoteRepository`.
2026-04-22 16:22:59 +02:00
stefan 255343145d ### feat: optimiere Architektur und verbessere E-Mail-Handling
- **ArchTests:** Passe Slices-Matching für `FrontendArchitectureTest` an Package-Struktur an.
- **Mail-Service:** Füge Plan-B-Benachrichtigung für Nennungen an Meldestelle hinzu; entferne Plus-Addressing (Fallback).
- **Build:** Deaktiviere Desktop-Build standardmäßig (`enableDesktop=false`) und mache Module-Registrierung optional.
2026-04-22 16:01:55 +02:00
stefan 5baa971b46 ### docs: aktualisiere ADR 0028 für E-Mail-basiertes Routing
- Ersetze Catch-All-Ansatz durch Betreff-basiertes Routing.
- Reduziere Infrastruktur-Aufwände durch generische Zieladresse.
2026-04-22 15:18:35 +02:00
stefan e65384768f ### feat: initialisiere Plan-B für E-Mail-basierte Online-Nennung
- **ADR 0028:** Dokumentiere MVP-Entscheidung für E-Mail-gesteuertes Nennsystem.
- **Gradle:** Aktiviere `enableWasm` für die Web-App-Generierung.
2026-04-22 15:11:00 +02:00
stefan beb20e0cf7 ### feat: erweitere ZNS und SQLDelight-Integration
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
- **SQLDelight:** Füge neue Queries (`countVereine`, `maxUpdated...`) zur SQLite-Datenbank hinzu und aktualisiere `DesktopMasterdataRepository`.
- **ZNS-Sync:** Passe `ZnsImportState` an, um Pferde- und Funktionärsdaten zu unterstützen.
- **Cloud-Sync:** Entferne redundante Auth-Header und setze Limits für Massensynchronisation auf 50.000 Datensätze.
- **Masterdata-Service:** Stabilisiere Consul Health-Checks und implementiere Limit-Beschränkungen auf Controller-Ebene.
2026-04-22 14:14:39 +02:00
stefan 98c241fc64 ### feat: erweitere Stammdaten-Integration
- **Repositories:** Implementiere und integriere `KtorPferdRepository` und `KtorFunktionaerRepository`.
- **SQLite:** Erweitere Schema um `LocalPferd` und `LocalFunktionaer` mit passenden Queries.
- **ViewModels:** Passe `PferdeViewModel` und `FunktionaerViewModel` an, um Flows und Repository-Injektion zu nutzen.
- **DI-Module:** Aktualisiere `PferdeModule` und `FunktionaerModule` für Backend-Anbindung.
2026-04-22 12:25:43 +02:00
stefan d4cc0eb77d ### feat: verbessere DI, Healthcheck-Logik und Reiter-API
- **Healthcheck:** Aktualisiere Dockerfile und konsolidiere Ports für konsistente Service-Gesundheitsprüfungen (8086 für Actuator, 8091 für API-Traffic).
- **ReiterRepository:** Implementiere `KtorReiterRepository` zur Nutzung der Backend-Stammdaten über API.
- **DI-Module:** Passe `ReiterModule` und `VereinFeatureModule` an, um den authentifizierten `apiClient` zu verwenden.
- **Masterdata-Service:** Synchronisiere Environment-Variablen und Konsul-Konfiguration mit aktualisierten Ports.
2026-04-22 12:11:38 +02:00
stefan e0b1ce8836 ### feat: implementiere SQLite-Integration und Repository-Refactoring
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 58s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m0s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m10s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 2m0s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m55s
- Erstelle Persistenz-Layer mit SQLite-Tabellen für `Verein` und `Reiter` inkl. Queries.
- Entferne Mock-Daten in `ReiterViewModel` und nutze Repository-Injektion.
- Integriere neue Tabellen und Queries im `DesktopMasterdataRepository`.
- Erweitere `VeranstalterWizardViewModel` um lokale Suche mit SQLite-Queries.
- Harmonisiere Feldnamen (`remoteReiterResults`) über alle Module hinweg.
- Aktualisiere DI-Module (`VeranstalterModule`, `ReiterModule`, `DesktopModule`) mit SQLite-Injektionen.
- Refaktor UI-Komponenten und Screens (`ReiterScreen`, `StammdatenImportScreen`) mit neuer Logik.
2026-04-22 02:20:55 +02:00
stefan f18b002f4e ### feat: füge Validierung und Fehler-Handling zur Veranstalter-Erstellung hinzu
- Implementiere Validierungslogik im `VeranstalterWizardViewModel` (Pflichtfelder, E-Mail-Format).
- Zeige Validierungsfehler direkt in der `VeranstalterNeuScreen` an.
- Erweiterung der State-Klasse um `errors` für direktes UI-Feedback.
2026-04-22 00:06:47 +02:00
stefan f8913f81b8 ### feat: überarbeite Veranstalter-Erstellung mit ZNS-Integration
- Implementiere "Search & Populate"-Logik im `VeranstalterWizardViewModel` und aktualisiere die Abhängigkeiten (`MasterdataRepository`, `ZnsImportProvider`).
- Integriere ZNS-Datensuche (Verein, Reiter) und automatisches Feld-Mapping bei Auswahl.
- Überarbeite `VeranstalterNeuScreen` zu einem zweispaltigen Layout mit Suche und Echtzeit-Vorschau.
- Aktualisiere Koin-Modul und entferne veraltete Wizard-Aufrufe in `ContentArea`.
- Füge zusätzliche ScreenPreviews hinzu und passe `ScreenPreviews.kt` an.
- Aktualisiere Dokumentation (`2026-04-21_Veranstalter-Neu-Overhaul.md`), Screenshots und relevante UI-Komponenten.
2026-04-21 23:22:14 +02:00
stefan 9195cdb14d ### feat: verbessere Wizard-Validierung und UI-Feedback
- Integriere Fortschrittsanzeige während der Veranstalter-Suche (`isCheckingStats`).
- Zeige Fehlermeldungen bei Suchfehlern im `EventWizardScreen`.
- Füge `hasSelectedVeranstalter`-Guard und zugehörige Tests hinzu.
- Präzisiere `DemoEventFlow` mit expliziter Guard-Logik.
- Aktualisiere Unit-Tests zur Abdeckung neuer Guard-Szenarien.
2026-04-21 21:26:06 +02:00
stefan 3f4ba9eea9 ### feat: erweitere Wizard- und UI-Logik
- Füge automatische Re-Evaluierung im `EventWizardViewModel` nach Import hinzu.
- Aktualisiere `StammdatenImportScreen` mit `onBack`-Callback für Status-Prüfung.
- Erweitere `PingScreen` im `ConnectivityCheck`-Screen um Navigation zum Login.
- Präzisiere `hasZns`-Guard mit Prüfung auf Import-Datum.
2026-04-21 21:10:58 +02:00
stefan 92028d9e02 ### feat: erweitere Wizard- und UI-Logik
- Füge automatische Re-Evaluierung im `EventWizardViewModel` nach Import hinzu.
- Aktualisiere `StammdatenImportScreen` mit `onBack`-Callback für Status-Prüfung.
- Erweitere `PingScreen` im `ConnectivityCheck`-Screen um Navigation zum Login.
- Präzisiere `hasZns`-Guard mit Prüfung auf Import-Datum.
2026-04-21 20:43:15 +02:00
stefan bdb45eefe4 ### feat: verbessere Validierungs- und Draft-Funktionalität im Wizard
- Entferne `onNavigateToVeranstalterNeu` aus `EventWizardScreen` und zugehörigen Komponenten.
- Füge persistente Speicherung für Drafts über `DraftStore` hinzu (JSON für JVM, No-op für Wasm).
- Ergänze WizardScaffold um `errorSummary` zur Anzeige von Validierungsfehlern.
- Bereinige und optimiere Schritt-Logik in `EventWizardViewModel`.
2026-04-21 20:12:53 +02:00
stefan 148b71db48 chore(devops): remove .idea files from git index and update .gitignore 2026-04-21 19:26:15 +02:00
stefan c54ad3830d feat: füge Wizard-Orchestrator mit Runtime, Scaffold und DraftStore (MVP) hinzu
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 18:30:16 +02:00
stefan d66bd63cc9 feat: füge DraftStore und Speichern/Resume von Wizard-Status hinzu
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 18:26:34 +02:00
stefan 3b4e3db51d feat: erweitere DemoEventFlow und EventWizardViewModel um neue Schritte
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 18:16:56 +02:00
stefan 2d7046d0e3 feat: passe EventWizardViewModel-Initialisierung für optionale Parameter an
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 18:03:55 +02:00
stefan d9b5c6bfea feat: aktiviere neues EventWizardScreen-Scaffold hinter Feature-Flag
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:58:48 +02:00
stefan 91a8c38b25 feat: implementiere WizardScaffold und Hotkey-Integration mittels Compose
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:56:56 +02:00
stefan 19ba044ec0 feat: integriere WizardRuntime in EventWizardViewModel und erweitere Schritt-Logik
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:44:31 +02:00
stefan 9556e0ac67 test: füge Unit-Tests für WizardRuntime hinzu
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:03:41 +02:00
stefan 4692bd186c chore: füge Core-Wizard-Modul hinzu und integriere in Veranstaltung-Feature
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:02:28 +02:00
stefan b11432df16 chore: implementiere Ping-Screen mit UI-Logik, ViewModel und Preview-Komponenten
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:02:15 +02:00
stefan 319cb52160 chore: implementiere Ping-Feature mit Repository, Sync-Service und API Client
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:02:03 +02:00
stefan a35dfa1434 chore: füge Event-Wizard-Screen und Schritt-Logik für neue Veranstaltungen hinzu
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:01:49 +02:00
stefan 237c71e5a0 chore: implementiere Wizard-Framework mit State- und Flow-Logik sowie Feature-Flags für Migration
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 17:01:31 +02:00
stefan ec124e9acd chore(docs): füge ADRs 0025–0027 und Wizard-DSL-Referenz hinzu, aktualisiere Roadmap und ADR-Index
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 16:21:22 +02:00
stefan 0ab1807235 chore: vereinheitliche Imports und ersetze androidx.compose.foundation.Image durch Image im Veranstalter-Wizard
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 15:18:04 +02:00
stefan 7cfdd06d1e chore: integriere Logo-Upload und Vorschau in Veranstalter-Wizard, verbessere Navigationslogik und erweitere Datenmodelle
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 15:16:05 +02:00
stefan 544fbf792c chore: erweitere Veranstalter-Wizard um Bearbeitungsmodus, füge Kontaktdaten und Step-Logik hinzu
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 14:37:43 +02:00
stefan 18e619abfc chore: erweitere Datenmodelle um neue Felder, verbessere Styling und aktualisiere Veranstalter-UI
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 14:24:54 +02:00
stefan 5eeff24b3a chore: refaktoriere Veranstaltungs-Wizard zu Event-Wizard, entferne überflüssige Komponenten und passe DI-Konfiguration an
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 14:00:04 +02:00
stefan f13c2eb35b chore: erweitere Datenmodelle um Nation und Bundesland, verbessere UI im Profil- und Veranstaltungs-Wizard
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 13:56:08 +02:00
stefan 2662d4e82e chore: erweitere Pferd-, Funktionär- und Reiter-Modelle um neue Felder, verbessere UI und Suche
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 13:47:37 +02:00
stefan 574f8c470c chore: refaktoriere Veranstaltungs-UI zu Events, implementiere ZNS-Suche und verbessere Navigationslogik
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 13:42:45 +02:00
stefan 9b4af2bb56 chore: füge Navigation zum Veranstalter-Wizard hinzu, erweitere Mock-Daten und verbessere Veranstaltungs-Flow
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 10:57:30 +02:00
stefan 1a295c18c8 chore: integriere Turnier-Wizard und ZNS-Importer in Veranstaltungsscreen, implementiere Profil-Onboarding und aktualisiere Modulabhängigkeiten
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 10:42:47 +02:00
stefan 01bf440f21 chore: behebe Kompilierungsfehler in ScreenPreviews.kt durch Implementierung von getStats() in Mock-Repos
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 09:41:57 +02:00
stefan 7acd9ea4c2 chore: implementiere Suche nach Veranstalter via OEPS-Nummer, verbessere UI-Flow im Veranstaltungs-Wizard und erweitere VereinRepository um OEPS-Abfrage
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 09:27:38 +02:00
stefan 30b53584f8 chore: implementiere Suche nach Veranstalter via OEPS-Nummer, verbessere UI-Flow im Veranstaltungs-Wizard und erweitere VereinRepository um OEPS-Abfrage
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-21 00:50:14 +02:00
stefan c1327f3186 chore: erweitere Veranstaltungs-Wizard um Ansprechperson-Anzeige, verbessere Fehlerhandling bei fehlenden Stammdaten und implementiere MsStringDropdown
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 23:53:43 +02:00
stefan 7a2c5700f9 chore: füge Warn-Dialoge für Rollenwechsel und Bearbeitungsmodus hinzu, verbessere Zustandshandhabung im Device-Setup und implementiere Turnierverwaltung im Veranstaltungs-Wizard
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 23:48:53 +02:00
stefan 5b8ef5ea2d chore: implementiere Lockscreen-Logik für Geräte- und Veranstaltungsinitialisierung, füge Zustandsprüfungen und neue UI-Komponenten hinzu
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 23:37:58 +02:00
stefan db58c24613 chore: entferne settings.json und Veranstaltungskomponenten, refaktoriere Veranstaltungsverwaltung und implementiere StoreVeranstaltungRepository
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 18:33:45 +02:00
stefan edfe05cbe3 chore: entferne ungenutzte Importe, reduziere Redundanz in DesktopMainLayout und verlagere Komponenten in Module
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 15:59:36 +02:00
stefan 6feb139a46 chore: füge SyncManager und Peer-Zähler hinzu, verbessere Navigation-Breadcrumbs und passe MD3-Stil an
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 15:09:25 +02:00
stefan b94e0f2d9d chore: implementiere Zustandsprüfung für DiscoveryService und ConnectivityTracker, verbessere Plug-and-Play-Kompatibilität und optimiere LAN-Discovery
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 14:39:48 +02:00
stefan 8806d11e3c chore: implementiere Auth-Status-abhängige Navigation und Icons, deaktiviere Module ohne Initialisierung und passe NavRail sowie Header für besseren UX an
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-20 14:23:47 +02:00
stefan a1bf93342e chore: fix 500 errors in ping-service, improve security annotations, update parameter mapping, integrate Resilience4j with Kotlin, and refine test suite 2026-04-20 14:21:53 +02:00
stefan 5887ac7b6c chore: update roadmap with finalized frontend modernization phase, refactor turnier and veranstalter features to adhere to Plug-and-Play architecture, and improve code hygiene in core frontend modules
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
2026-04-20 10:55:28 +02:00
stefan 8aef46bba1 chore: refactor Veranstalter screens and ViewModels to decouple from Koin, implement hoisting for better testability, consolidate legacy shell logic into modern components, and add mock repositories for previews 2026-04-20 10:31:45 +02:00
stefan 2489beab59 chore: refactor TurnierDetailScreen and related components, remove unused parameters, centralize date validation logic, implement TurnierStammdatenViewModel, and eliminate reflection dependencies 2026-04-20 10:11:10 +02:00
stefan f8820847fa chore: implement Turnier domain logic, add repository interfaces and default implementations, and disable WASM builds 2026-04-20 09:38:39 +02:00
stefan 345c329350 chore: enhance Stammdaten-Verwaltung and refine desktop UX across multiple features, fix typo in settings.json, enable WASM builds, and add Master-Detail layout for Funktionäre
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
2026-04-20 02:49:34 +02:00
stefan d4aeba4666 chore: implementiere MsFilePicker-Komponente, ersetze veraltete Input-Felder in Geräteneukonfiguration und ZNS-Importer, verbessere Vereinskarten-Darstellung und Detail-UX, behebe Tippfehler in settings.json 2026-04-20 02:00:36 +02:00
stefan 9fe889b2c1 chore: bereinige unbenutzte Importe in VereinScreens.kt und LogoUploadZone.jvm.kt, verbessere Code-Readability durch konsistenten Importstil 2026-04-20 01:21:18 +02:00
stefan 85ac1cae9c chore: implementiere Logo-Upload-Zone mit Base64-Unterstützung, verbessere Vereinsverwaltung mit kompakten Feldern und nutzerspezifischen Uploadoptionen, optimiere Desktop-UX und Navigation 2026-04-20 01:20:20 +02:00
stefan dfaa2e8545 chore: consolidate redundant controllers in mail-service, improve backend stability, refine desktop UX, and enhance Vereinsverwaltung functionality 2026-04-20 00:21:20 +02:00
stefan bcabb86841 chore: fix DI binding for ZnsImportProvider in zns-import-feature, ensure proper Koin resolution 2026-04-19 22:18:18 +02:00
stefan 189ebc6565 chore: entferne XML-Deklaration aus .idea/misc.xml
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
2026-04-19 21:50:42 +02:00
stefan 83adb4ae07 chore: erweitere Resilience4j-Bundle um Kotlin-Support, aktualisiere PingController um Fallback-Logik, füge Fehlerhandler hinzu, verbessere PingControllerTest, synchronisiere .env und dc-infra.yaml 2026-04-19 21:50:33 +02:00
stefan 54f91c7309 chore: entferne unbenutzten PingEvent Import aus PingSyncIntegrationTest
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
2026-04-19 17:46:48 +02:00
stefan a645bb4dbc chore: migriere ping-feature Modul auf Module Structure Blueprint, füge Fake-Repository und neue Integrationstests hinzu, dokumentiere Änderungen 2026-04-19 17:46:29 +02:00
stefan 691861a706 chore: entferne veraltete turnier-feature Artefakte und ViewModels nach Migration auf Module Structure Blueprint 2026-04-19 17:39:34 +02:00
stefan ef5d4fdc81 chore: migriere meldestelle-web Shell auf Module Structure Blueprint, aktualisiere group, setze version, passe DI-Imports an, dokumentiere Änderungen 2026-04-19 17:36:20 +02:00
stefan afad3c5a02 chore: migriere Artefakte für Module auf Module Structure Blueprint (meldestelle-desktop, sync, network), aktualisiere Ausgabepfade, füge wasmJsMain und jvmMain Outputs hinzu 2026-04-19 17:33:31 +02:00
stefan 512eb730b0 chore: migriere meldestelle-desktop Shell auf Module Structure Blueprint, aktualisiere group, setze version, passe DI-Imports an, dokumentiere Änderungen 2026-04-19 17:33:18 +02:00
stefan 8c1abaebad chore: migriere zns-import-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:24:01 +02:00
stefan 3428261bff chore: migriere verein-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:13:05 +02:00
stefan 34cab61567 chore: migriere veranstaltung-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:12:07 +02:00
stefan 4419e55ee1 chore: migriere veranstalter-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:11:07 +02:00
stefan bd8899a829 chore: migriere turnier-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:10:03 +02:00
stefan 8148ceb318 chore: migriere reiter-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:08:43 +02:00
stefan 58454ec9af chore: migriere profile-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:07:38 +02:00
stefan 2e7078424d chore: migriere ping-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:06:37 +02:00
stefan 56ecee4cba chore: migriere pferde-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:05:27 +02:00
stefan 9222ae7a1c chore: migriere nennung-feature Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:04:04 +02:00
stefan 9578b92e7a chore: migriere funktionaer-feature Modul auf Module Structure Blueprint, passe group an, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:02:34 +02:00
stefan f02e172ff0 chore: migriere device-initialization Modul auf Module Structure Blueprint, aktualisiere group, füge wasmJsMain Dependency hinzu, dokumentiere Änderungen 2026-04-19 17:00:52 +02:00
stefan cef579f91b chore: migriere billing-feature Modul auf Module Structure Blueprint, setze group und version, füge wasmJsMain Dependency hinzu 2026-04-19 16:59:12 +02:00
stefan c8655bfc7f chore: dokumentiere Migration des core/sync Moduls auf Module Structure Blueprint, inkl. group und version Anpassungen 2026-04-19 16:53:16 +02:00
stefan 28a7c5dc44 chore: migriere core/sync Modul auf Module Structure Blueprint, setze group und version, füge wasmJsMain Dependency hinzu 2026-04-19 16:52:08 +02:00
stefan b19f7cadb8 chore: migriere core/network Modul auf Module Structure Blueprint, setze group und version, dokumentiere Änderungen 2026-04-19 16:50:33 +02:00
stefan cb6db36adb chore: migriere core/navigation Modul auf Module Structure Blueprint, aktualisiere group und füge wasmJsMain Dependency hinzu 2026-04-19 16:48:37 +02:00
stefan 0e694341b8 chore: füge Artifact-Konfigurationsdateien für Domain- und LocalDb-Module hinzu 2026-04-19 16:45:56 +02:00
stefan 2ab1840237 chore: setze group und version in Core-LocalDb-Modul, dokumentiere Migration auf Blueprint 2026-04-19 16:45:32 +02:00
stefan 96bdc92723 chore: setze group und version in Core-Domain-Build, füge wasmJsMain Dependency hinzu 2026-04-19 16:42:46 +02:00
stefan cee0a8437f chore: füge fehlende Artifact-Konfigurationsdateien für Projektmodule hinzu 2026-04-19 16:39:50 +02:00
stefan 2b05eebad9 chore: migriere ComponentPreview-Annotation in konsistente Paketstruktur und bereinige referenzierende Importe 2026-04-19 16:39:32 +02:00
stefan 9037b6ce1c chore: entferne veraltete .gitignore-Einträge und nicht genutzte IDE-Konfigurationsdateien 2026-04-19 16:15:59 +02:00
stefan ec861b8f81 chore: entferne DesktopApp und DesktopMainLayout, da diese nicht mehr benötigt werden 2026-04-19 16:07:58 +02:00
stefan 767d78af27 chore: bereinige Import-Anweisungen und entferne nicht genutzten Code in DeviceInitialization-Präsentationsmodulen 2026-04-19 15:59:29 +02:00
stefan 8a3ef98c44 chore: entferne AuthApiClient, AuthTokenManager und DeviceInitializationConfig.jvm, da diese nicht mehr benötigt werden 2026-04-19 15:58:03 +02:00
stefan dc66dfb537 chore: konsolidiere Exception-Handling durch _-Platzhalter, bereinige Import-Anweisungen und entferne nicht genutzten Code
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 58s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m13s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m27s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 1m59s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m50s
2026-04-19 00:54:34 +02:00
stefan ae39eb4637 chore: entferne nicht genutzte NennungsMaske-Komponente, extrahiere AktionsButtonLeiste in separaten Komponentenordner 2026-04-19 00:52:36 +02:00
stefan 64d749be3a chore: entferne nicht genutzte NennungsMaske-Komponente, extrahiere AktionsButtonLeiste in separaten Komponentenordner 2026-04-19 00:52:12 +02:00
stefan 1b20e480f4 feat: verbessere Device-Setup-UX durch präzise Fokus-Navigation, Plug-and-Play-Optimierungen und Logging-Standardisierung
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 1m0s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
2026-04-18 22:29:15 +02:00
stefan c29c8179a1 feat: flexibilisiere JWT-Validierung durch benutzerdefinierte Decoder und verbessere CORS-Konfiguration 2026-04-18 20:40:40 +02:00
stefan 2bd2a26ab9 chore: entferne Backup-Dateimuster *~ aus .gitignore 2026-04-18 20:40:03 +02:00
stefan fb520c6607 chore: entferne nicht mehr genutzten Code und Backup-Dateien aus shared-Modul 2026-04-18 20:39:42 +02:00
stefan bad6f44122 chore: entferne nicht mehr genutzte Backup-Dateien, IDE-Konfigurationen und Kotlin-Build-Skripte 2026-04-18 20:38:57 +02:00
stefan 280debce09 refactor(web): Komplettumstellung auf WASM, Altlasten aus Gradle und Architektur-Tests entfernt
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-18 15:48:35 +02:00
stefan fb77a5065b refactor(device-initialization): Code-Bereinigung, ungenutzte Parameter entfernt und WasmJS-Unterstützung vervollständigt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-18 14:47:39 +02:00
stefan e91b10daa3 JS-spezifische Module und Dateien entfernt, Multiplattform-Targets korrigiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-18 14:16:29 +02:00
stefan 7bbb991e69 refactor(desktop, core): Onboarding zu DeviceInitialization umbenannt, Navigation und Screens angepasst
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-18 11:10:05 +02:00
stefan 315517f03f feat(onboarding): Property isConfigured ergänzt, Build-Fehler behoben
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-18 09:08:42 +02:00
stefan 88983f2b4e feat: verbessere Onboarding-Workflow, verbessere mDNS-Discovery & ZNS-Import
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 1m1s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m29s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m14s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 1m17s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m48s
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-17 22:52:37 +02:00
stefan 8f6044abe3 feat(onboarding, screens): Logging für Screen-Loads ergänzt & Biest-Referenzen entfernt
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 1m2s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m7s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 6m18s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 59s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 2m0s
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-17 13:13:47 +02:00
stefan 8857d52f44 refactor(desktop): Alte Verwaltungsscreens entfernt und Code reduziert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-17 12:26:28 +02:00
stefan 3949ab21db refactor(desktop): V2-Suffixe entfernt und VeranstaltungKomponenten modularisiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-17 11:40:09 +02:00
stefan 0128f98164 feat(desktop, masterdata): ZNS-Sync-Status in Footer hinzugefügt & Consul-Healthcheck stabilisiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-17 09:54:26 +02:00
628 changed files with 24891 additions and 22912 deletions
+263
View File
@@ -0,0 +1,263 @@
# ==========================================
# Meldestelle Docker Compose Environment
# Single Source of Truth (SSoT)
# ==========================================
# WARNING: This file contains secrets (passwords).
# Do NOT commit this file to version control if it contains production secrets.
# --- PROJECT ---
PROJECT_NAME=meldestelle
# --- BACKUP ---
BACKUP_DIR=/home/stefan/backups/meldestelle
BACKUP_RETENTION_DAYS=7
# Docker build versions (optional overrides)
DOCKER_VERSION=1.0.0-SNAPSHOT
DOCKER_REGISTRY=git.mo-code.at/mocode-software/meldestelle
DOCKER_BUILD_DATE=2026-03-16T12:00:00Z
DOCKER_GRADLE_VERSION=9.3.1
DOCKER_JAVA_VERSION=25
DOCKER_NODE_VERSION=24.12.0
DOCKER_NGINX_VERSION=1.28.0-alpine
DOCKER_CADDY_VERSION=2.11-alpine
# JVM Power Flags (Lokal leer lassen, da Intel/AMD Architektur)
JVM_OPTS_ARM64=
# --- POSTGRES ---
POSTGRES_IMAGE=postgres:16-alpine
POSTGRES_SHARED_BUFFERS=256MB
POSTGRES_EFFECTIVE_CACHE_SIZE=768MB
POSTGRES_USER=pg-user
POSTGRES_PASSWORD=pg-password
POSTGRES_DB=pg-meldestelle-db
POSTGRES_PORT=5432:5432
POSTGRES_DB_URL=jdbc:postgresql://postgres:5432/pg-meldestelle-db
# --- VALKEY (formerly Redis) ---
VALKEY_IMAGE=valkey/valkey:9-alpine
VALKEY_PASSWORD=valkey-password
VALKEY_PORT=6379:6379
VALKEY_SERVER_HOSTNAME=valkey
VALKEY_SERVER_PORT=6379
VALKEY_SERVER_CONNECT_TIMEOUT=5s
VALKEY_POLICY=allkeys-lru
VALKEY_MAX_MEMORY=256MB
SPRING_DATA_VALKEY_HOST=localhost
SPRING_DATA_VALKEY_PORT=6379
SPRING_DATA_VALKEY_PASSWORD=valkey-password
# --- KEYCLOAK ---
KEYCLOAK_IMAGE_TAG=latest
KC_HEAP_MIN=512M
KC_HEAP_MAX=1024M
# Lokale Entwicklung: start-dev (kein Pre-Build nötig, kein --optimized)
# Server/Produktion: start --optimized --import-realm (nutzt das pre-built Registry-Image)
KC_COMMAND=start-dev --import-realm
# System-Admin (Master Console)
KC_BOOTSTRAP_ADMIN_USERNAME=kc-admin
KC_BOOTSTRAP_ADMIN_PASSWORD=kc-password
# Fach-Admin User Passwort (wird im Realm Import genutzt)
# Hinweis: Wenn du das hier änderst, müsstest du auch die JSON anpassen
# oder dort eine Variable nutzen.
KC_DB=postgres
KC_DB_SCHEMA=keycloak
KC_DB_URL=jdbc:postgresql://postgres:5432/pg-meldestelle-db
KC_DB_USERNAME=pg-user
KC_DB_PASSWORD=meldestelle
# Lokal: localhost | Server: echte IP oder Domain (z.B. 10.0.0.50 oder auth.meldestelle.at)
# WICHTIG: Nur den Hostnamen angeben, OHNE Port (Keycloak 26.x hostname v2)
KC_HOSTNAME=localhost
# false = Zugriff über beliebige Hostnamen erlaubt (nötig ohne TLS / für HTTP-Betrieb)
KC_HOSTNAME_STRICT=false
KC_HOSTNAME_STRICT_HTTPS=false
KC_PORT=8180:8080
KC_MANAGEMENT_PORT=9000:9000
KC_HTTP_ENABLE=true
KC_API_GATEWAY_CLIENT_SECRET=K5RqonwVOaxPKaXVH4mbthSRbjRh5tOK
# KC_POSTMAN_CLIENT_SECRET=postman-secret-123
# KC_BOOTSTRAP_ADMIN_PASSWORD=Admin#1234
KC_FRONTEND_URL=http://localhost:8180
KC_PROXY_HEADERS=xforwarded
# --- KEYCLOAK TOKEN VALIDATION ---
# Public Issuer URI (must match the token issuer from browser/postman)
# Lokal: http://localhost:8180 | Produktion: http://10.0.0.50:8180
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8180/realms/meldestelle
# Internal JWK Set URI (for service-to-service communication within Docker)
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=http://keycloak:8080/realms/meldestelle/protocol/openid-connect/certs
# --- CONSUL ---
CONSUL_IMAGE=hashicorp/consul:1.22.1
CONSUL_PORT=8500:8500
CONSUL_UDP_PORT=8600:8600/udp
CONSUL_HOST=consul
CONSUL_HTTP_PORT=8500
SPRING_CLOUD_CONSUL_HOST=consul
SPRING_CLOUD_CONSUL_PORT=8500
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME=api-gateway
SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS=true
# --- Zipkin ---
ZIPKIN_IMAGE=openzipkin/zipkin:3
ZIPKIN_MIN_HEAP=256M
ZIPKIN_MAX_HEAP=512M
ZIPKIN_PORT=9411:9411
ZIPKIN_ENDPOINT=http://zipkin:9411/api/v2/spans
ZIPKIN_SAMPLING_PROBABILITY=1.0
# --- Mailpit ---
MAILPIT_IMAGE=axllent/mailpit:v1.29
MAILPIT_WEB_PORT=8025:8025
MAILPIT_SMTP_PORT=1025:1025
# --- PGADMIN ---
PGADMIN_IMAGE=dpage/pgadmin4:8
PGADMIN_EMAIL=meldestelle@mo-code.at
PGADMIN_PASSWORD=pgadmin
PGADMIN_PORT=8888:80
# --- POSTGRES-EXPORTER ---
POSTGRES_EXPORTER_IMAGE=prometheuscommunity/postgres-exporter:v0.18.0
# --- ALERTMANAGER ---
ALERTMANAGER_IMAGE=prom/alertmanager:v0.29.0
ALERTMANAGER_PORT=9093:9093
# --- PROMETHEUS ---
PROMETHEUS_IMAGE=prom/prometheus:v3.7.3
PROMETHEUS_PORT=9090:9090
# --- GRAFANA ---
GF_IMAGE=grafana/grafana:12.3
GF_ADMIN_USER=gf-admin
GF_ADMIN_PASSWORD=gf-password
GF_PORT=3000:3000
# --- API-GATEWAY ---
GATEWAY_PORT=8081:8081
GATEWAY_DEBUG_PORT=5005:5005
GATEWAY_SERVER_PORT=8081
GATEWAY_SPRING_PROFILES_ACTIVE=docker
GATEWAY_DEBUG=true
GATEWAY_SERVICE_NAME=api-gateway
GATEWAY_CONSUL_PREFER_IP=true
# --- PING-SERVICE ---
PING_SPRING_PROFILES_ACTIVE=docker
PING_PORT=8082:8082
PING_DEBUG_PORT=5006:5006
PING_SERVER_PORT=8082
PING_DEBUG=true
PING_SERVICE_NAME=ping-service
PING_CONSUL_PREFER_IP=true
# --- MAIL-SERVICE ---
MAIL_PORT=8083:8083
MAIL_DEBUG_PORT=5014:5014
MAIL_SERVER_PORT=8083
MAIL_SERVICE_URL=http://10.0.0.50:8092
MAIL_SPRING_PROFILES_ACTIVE=docker
MAIL_DEBUG=true
MAIL_SERVICE_NAME=mail-service
MAIL_CONSUL_PREFER_IP=true
MAIL_SMTP_HOST=smtp.world4you.com
MAIL_SMTP_PORT=587
MAIL_SMTP_USER=online-nennen@mo-code.at
MAIL_SMTP_PASSWORD=Mogi#2reiten
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true
SPRING_MAIL_HOST=smtp.world4you.com
SPRING_MAIL_PORT=587
SPRING_MAIL_USERNAME=online-nennen@mo-code.at
SPRING_MAIL_PASSWORD=Mogi#2reiten
SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=false
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=false
SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED=false
SPRING_CLOUD_CONSUL_ENABLED=false
MAIL_POLLING_ENABLED=false
# --- MASTERDATA-SERVICE ---
MASTERDATA_PORT=8086:8086
MASTERDATA_DEBUG_PORT=5007:5007
MASTERDATA_SERVER_PORT=8086
MASTERDATA_KTOR_PORT=8091
MASTERDATA_SPRING_PROFILES_ACTIVE=docker
MASTERDATA_DEBUG=true
MASTERDATA_SERVICE_NAME=masterdata-service
MASTERDATA_CONSUL_PREFER_IP=true
MASTERDATA_SERVICE_HOSTNAME=masterdata-service
# --- EVENTS-SERVICE ---
EVENTS_PORT=8085:8085
EVENTS_DEBUG_PORT=5008:5008
EVENTS_SERVER_PORT=8085
EVENTS_SPRING_PROFILES_ACTIVE=docker
EVENTS_DEBUG=true
EVENTS_SERVICE_NAME=events-service
EVENTS_CONSUL_PREFER_IP=true
# --- ZNS-IMPORT-SERVICE ---
ZNS_IMPORT_PORT=8095:8095
ZNS_IMPORT_DEBUG_PORT=5009:5009
ZNS_IMPORT_SERVER_PORT=8095
ZNS_IMPORT_SPRING_PROFILES_ACTIVE=docker
ZNS_IMPORT_DEBUG=true
ZNS_IMPORT_SERVICE_NAME=zns-import-service
ZNS_IMPORT_CONSUL_PREFER_IP=true
# --- RESULTS-SERVICE ---
RESULTS_PORT=8088:8088
RESULTS_DEBUG_PORT=5010:5010
RESULTS_SERVER_PORT=8088
RESULTS_SPRING_PROFILES_ACTIVE=docker
RESULTS_DEBUG=true
RESULTS_SERVICE_NAME=results-service
RESULTS_CONSUL_PREFER_IP=true
# --- BILLING-SERVICE ---
BILLING_PORT=8087:8087
BILLING_DEBUG_PORT=5012:5012
BILLING_SERVER_PORT=8087
BILLING_SPRING_PROFILES_ACTIVE=docker
BILLING_DEBUG=true
BILLING_SERVICE_NAME=billing-service
BILLING_CONSUL_PREFER_IP=true
# --- SCHEDULING-SERVICE ---
SCHEDULING_PORT=8084:8084
SCHEDULING_DEBUG_PORT=5013:5013
SCHEDULING_SERVER_PORT=8084
SCHEDULING_SPRING_PROFILES_ACTIVE=docker
SCHEDULING_DEBUG=true
SCHEDULING_SERVICE_NAME=scheduling-service
SCHEDULING_CONSUL_PREFER_IP=true
# --- SERIES-SERVICE ---
SERIES_PORT=8089:8089
SERIES_DEBUG_PORT=5011:5011
SERIES_SERVER_PORT=8089
SERIES_SPRING_PROFILES_ACTIVE=docker
SERIES_DEBUG=true
SERIES_SERVICE_NAME=series-service
SERIES_CONSUL_PREFER_IP=true
# --- WEB-APP ---
CADDY_VERSION=2.11-alpine
WEB_APP_PORT=8080:80
WEB_BUILD_PROFILE=dev
# Lokal: http://localhost:8081 | Produktion: http://10.0.0.50:8081
WEB_APP_API_URL=http://localhost:8081
WEB_APP_KEYCLOAK_URL=http://auth.mo-code.at
# --- DESKTOP-APP ---
DESKTOP_APP_VNC_PORT=5901:5901
DESKTOP_APP_NOVNC_PORT=6080:6080
+13 -2
View File
@@ -1,13 +1,24 @@
name: Desktop CI — Headless Tests & Build
on:
# Nur ausführen, wenn explizit das Desktop-Shell-Modul geändert wurde
push:
branches: [ main, master ]
paths:
- 'frontend/shells/meldestelle-desktop/**'
- '.gitea/workflows/desktop-tests.yml'
pull_request:
branches: [ main, master ]
paths:
- 'frontend/shells/meldestelle-desktop/**'
# Manuell startbar, falls benötigt
workflow_dispatch:
jobs:
desktop-tests:
# Komplett deaktivierbar über Repo-Variable: Settings → Variables → DESKTOP_CI_ENABLED=true
# Zusätzlich: Für PlanBBuilds überspringen, wenn Commit-Message [planb] enthält
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(github.event.head_commit.message, '[planb]') }}
name: Compose Desktop — Tests (headless) & Build
runs-on: ubuntu-latest
@@ -38,12 +49,12 @@ jobs:
- name: Show Gradle version
run: ./gradlew --version
- name: Run Desktop tests headless (Xvfb)
- name: Run Desktop tests headless (xvfb)
env:
_JAVA_OPTIONS: -Djava.awt.headless=true
run: |
sudo apt-get update -y
sudo apt-get install -y Xvfb
sudo apt-get install -y xvfb xauth
xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" \
./gradlew :frontend:shells:meldestelle-desktop:jvmTest --stacktrace --no-daemon
+37 -46
View File
@@ -4,6 +4,8 @@ run-name: Build & Publish by @${{ github.actor }}
on:
push:
branches: [ "main" ]
tags:
- 'v*'
paths:
- 'backend/**'
- 'platform/**'
@@ -33,18 +35,11 @@ jobs:
max-parallel: 1
matrix:
include:
- service: keycloak
# Plan-B fokussiert: Nur Mail-Service + Web-App bauen/pushen (beschleunigt CI deutlich)
- service: mail-service
context: .
dockerfile: config/docker/keycloak/Dockerfile
image: keycloak
- service: api-gateway
context: .
dockerfile: backend/infrastructure/gateway/Dockerfile
image: api-gateway
- service: ping-service
context: .
dockerfile: backend/services/ping/Dockerfile
image: ping-service
dockerfile: backend/services/mail/Dockerfile
image: mail-service
- service: web-app
context: .
dockerfile: config/docker/caddy/web-app/Dockerfile
@@ -61,43 +56,42 @@ jobs:
distribution: "temurin"
cache: gradle
- name: Setup Gradle Cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# Verhindert mysteriöse Build-Fehler durch korrupte Node/Kotlin-Caches (nur web-app relevant)
- name: Cleanup stale build caches
if: matrix.service == 'web-app'
run: |
rm -rf frontend/shells/meldestelle-portal/build/js/node_modules/.cache || true
rm -rf frontend/shells/meldestelle-portal/build/js/.yarn/cache || true
rm -rf ~/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-compiler-embeddable || true
- name: Build Frontend (Kotlin JS)
# --- SCHRITT 1: Build mit radikalem Clean (gegen die März-Leichen) ---
- name: Build Frontend (Wasm JS)
if: matrix.service == 'web-app'
run: |
chmod +x gradlew
./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution \
# Löscht alte Build-Stände komplett
./gradlew :frontend:shells:meldestelle-web:clean
./gradlew :frontend:shells:meldestelle-web:wasmJsBrowserDistribution \
-Pproduction=true \
--max-workers=4 \
-Dkotlin.daemon.jvm.options="-Xmx4g"
# Pangolin-Bypass: Credentials direkt in config.json schreiben.
# Kein "docker login" → kein Daemon-Ping → kein HTTPS-Fehler.
# BuildKit liest ~/.docker/config.json und verwendet diese Credentials beim Push.
# - name: Registry-Credentials konfigurieren (kein Daemon-Kontakt)
# run: |
# mkdir -p ~/.docker
# AUTH=$(echo -n "${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_TOKEN }}" | base64 -w 0)
# printf '{"auths":{"%s":{"auth":"%s"}}}\n' "${{ env.REGISTRY_INTERNAL }}" "${AUTH}" > ~/.docker/config.json
# echo "✓ Credentials für ${{ env.REGISTRY_INTERNAL }} gespeichert"
# --- SCHRITT 2: Staging ohne rsync (Fix für dein Log-Fehler) ---
- name: Stage Web Assets for Docker build
if: matrix.service == 'web-app'
run: |
set -e
DIST_DIR="frontend/shells/meldestelle-web/build/dist/wasmJs/productionExecutable"
TARGET_DIR="config/docker/caddy/web-app/_site"
if [ ! -d "$DIST_DIR" ]; then
echo "❌ Fehler: Build-Verzeichnis nicht gefunden!"
exit 1
fi
# Ersetzt rsync durch sicheres Löschen & Kopieren
rm -rf "$TARGET_DIR"
mkdir -p "$TARGET_DIR"
cp -r "$DIST_DIR"/. "$TARGET_DIR/"
# Kopiere Turnier-Ausschreibungen (PDFs) für Plan-B
cp docs/Neumarkt2026/*.pdf "$TARGET_DIR/" || true
echo "✓ Assets für Docker vorbereitet (Stand: $(date))"
# --- SCHRITT 3: Login & BuildX ---
# NEU (sauber, nach daemon.json-Fix):
- name: Login to Gitea Registry
uses: docker/login-action@v3
@@ -122,8 +116,9 @@ jobs:
with:
images: ${{ env.REGISTRY_INTERNAL }}/${{ env.IMAGE_PREFIX }}/${{ matrix.image }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
@@ -137,9 +132,5 @@ jobs:
provenance: false
sbom: false
build-args: |
DOCKER_BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
BUILD_DATE=${{ github.event.head_commit.timestamp || 'unknown' }}
VERSION=${{ github.sha }}
GRADLE_VERSION=${{ env.GRADLE_VERSION }}
JAVA_VERSION=${{ env.JAVA_VERSION }}
KEYCLOAK_IMAGE_TAG=${{ env.KEYCLOAK_IMAGE_TAG }}
JVM_OPTS_APPEND=${{ env.JVM_OPTS_ARM64 }}
+56
View File
@@ -0,0 +1,56 @@
name: Feature Build — Windows MSI (via Conveyor)
on:
workflow_dispatch: # Nur noch manueller Start möglich, da ARM64-Runner inkompatibel
# push:
# branches: [ "feature/*" ] # Deaktiviert wegen ARM64 Exec Format Error
jobs:
package-windows:
name: 📦 Windows .msi Packaging
# Desktop-CI ist nun via Conveyor auf Linux möglich
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK 21 (Temurin)
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: gradle
- name: Gradle Build (Uber-JAR)
run: |
./gradlew :frontend:shells:meldestelle-desktop:jvmJar --no-daemon
ls -lh frontend/shells/meldestelle-desktop/build/libs/
- name: Setup Conveyor
run: |
# Conveyor-Installation via Debian-Paket (stabiler in CI)
sudo apt-get update && sudo apt-get install -y curl
# Wir nutzen die offizielle Empfehlung für Debian-basierte Systeme
curl -L https://conveyor.hydraulic.dev/install.sh -o install-conveyor.sh
# Validierung: Wenn es kein Shell-Skript ist (sondern HTML), abbrechen
if grep -q "<!DOCTYPE HTML" install-conveyor.sh; then
echo "Fehler: Download-URL lieferte HTML statt Skript. Nutze npm-Fallback."
npm install -g @hydraulic/conveyor
else
chmod +x install-conveyor.sh
./install-conveyor.sh
fi
echo "$HOME/.conveyor/bin" >> $GITHUB_PATH
- name: Windows .msi mit Conveyor bauen
run: |
# HINWEIS: Erfordert aktuell einen x64-Linux-Runner.
# Schlägt auf ARM64 (Zora) mit 'Exec format error' fehl.
CONVEYOR_BIN=$(which conveyor || echo "$HOME/.conveyor/bin/conveyor")
$CONVEYOR_BIN make windows-msi
- name: .msi Artefakt hochladen
uses: actions/upload-artifact@v4
with:
name: meldestelle-windows-feature-build
path: output/*.msi
if-no-files-found: error
+2
View File
@@ -4,6 +4,8 @@ on:
branches: [ "**" ]
jobs:
no-hardcoded-versions:
# Für Plan-B-Builds überspringen: Commit-Message enthält [planb]
if: ${{ !contains(github.event.head_commit.message, '[planb]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+6
View File
@@ -22,6 +22,8 @@ jobs:
# =============================================================
tag-release:
name: 🏷️ Git-Tag setzen
# Für Plan-B-Builds überspringen: Commit-Message enthält [planb]
if: ${{ !contains(github.event.head_commit.message, '[planb]') }}
runs-on: ubuntu-latest
outputs:
version: ${{ steps.read-version.outputs.version }}
@@ -77,6 +79,8 @@ jobs:
# =============================================================
package-linux:
name: 📦 Linux .deb Packaging
# Nur ausführen, wenn Desktop-CI explizit aktiviert ist UND kein PlanB Commit
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(github.event.head_commit.message, '[planb]') }}
runs-on: ubuntu-latest
needs: tag-release
@@ -123,6 +127,8 @@ jobs:
# =============================================================
package-windows:
name: 📦 Windows .msi Packaging
# Nur ausführen, wenn Desktop-CI explizit aktiviert ist UND kein PlanB Commit
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(github.event.head_commit.message, '[planb]') }}
runs-on: windows-latest
needs: tag-release
+58
View File
@@ -0,0 +1,58 @@
# 🐧 [DevOps Engineer] Optimierte .gitignore für Meldestelle (KMP / Gradle / Docker)
# --- IDE & Editor ---
.idea/
*.iml
*.iws
*.ipr
out/
.vscode/
.history/
.shelf/
# --- Gradle ---
.gradle/
build/
!**/src/**/build/
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache
bin/
# --- Kotlin / KMP ---
.kotlin/
kotlin-js-store/
.jetbrains/
# --- Android (falls relevant) ---
*.ap_
*.apk
*.dex
local.properties
# --- Node / JS (Compose Web / KMP JS) ---
node_modules/
package-lock.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm/
# --- Docker & Infrastructure ---
.docker/
*.log
logs/
.env
!.env.example
.data/
postgres-data/
# --- OS Specific ---
.DS_Store
Thumbs.db
desktop.ini
# --- Project Specific ---
docs/temp/
docs/Bin/
docs/_archive/
+16 -10
View File
@@ -1,13 +1,17 @@
# 🤖 Projekt Agenten & Protokoll (Meldestelle-Biest)
# 🤖 Projekt Agenten & Protokoll (Meldestelle)
Dieses Dokument definiert die Zusammenarbeit zwischen dem User (Owner) und den spezialisierten KI-Agenten.
Es dient als zentraler **System-Prompt-Erweiterung** für neue Chat-Sessions.
## 🚀 Strategische Ausrichtung
Das Projekt **"Meldestelle-Biest"** entwickelt eine ÖTO/FEI-konforme, offline-fähige Turnier-Software.
## 🚀 Strategische Ausrichtung (Reality-Reset 28.04.2026)
Das Projekt **"Meldestelle"** entwickelt eine ÖTO/FEI-konforme, offline-fähige Turnier-Software.
1. **Desktop-First:** Primäres Ziel ist die Compose Desktop App (KMP). UX & Performance sind auf Profis optimiert.
2. **Offline-First:** Das System muss autark (ohne Internet) funktionieren. Sync-Logik ist Kernbestandteil.
3. **Domain-Driven:** 6 Bounded Contexts (SCS) bilden den fachlichen Rahmen.
2. **Offline-First:** Das System muss autark (ohne Internet) funktionieren.
3. **Domain-Driven:** Die Hierarchie **Veranstaltung -> Turnier -> Bewerb/Abteilung** ist das absolute Fundament.
**WICHTIG:** Alle Agenten arbeiten ab sofort nur noch auf Basis von verifiziertem Code. "Halluzinationen" über
abgeschlossene Phasen ohne entsprechende Implementierung sind untersagt.
## 1. Protokoll & Rollen-Badges
Jede Agenten-Antwort **muss** mit dem entsprechenden Badge beginnen, um den Kontext und die Verantwortlichkeit zu klären.
@@ -29,14 +33,16 @@ Jede Agenten-Antwort **muss** mit dem entsprechenden Badge beginnen, um den Kont
* **🧹 [Curator]**: Wissens-Management & Dokumentations-Check (ADR, Reference, Journal). Beendet jede Session.
* [Playbook](docs/04_Agents/Playbooks/Curator.md)
## 2. Der "Biest"-Workflow
## 2. Der "Meldestelle"-Workflow
1. **Kontext-Check:** Lies immer zuerst die `MASTER_ROADMAP` in `docs/01_Architecture/`.
2. **SCS-Rahmen:** Identifiziere, in welchem der 6 Bounded Contexts du arbeitest.
3. **Fokus:** Bearbeite immer nur EINE fachliche Aufgabe pro Session.
4. **Doku-as-Code:** Änderungen an Code/Architektur müssen sofort in `docs/` (ADR/Reference) reflektiert werden.
5. **Session-Abschluss:** Jede Session endet mit einem Eintrag durch den **Curator** (Journal oder Artefakt).
## 3. Projekt-Philosophie
* **Information Density over White Space:** Wir bauen ein Profi-Werkzeug, kein Spielzeug.
* **Speed over Animation:** Reaktionsgeschwindigkeit der UI hat höchste Priorität.
* **Offline-Authentizität:** Lokale Daten sind die "Source of Truth" für den User; der Server ist das Backup/Sync-Target.
## 🚫 Anti-Halluzinations-Protokoll (WICHTIG)
Um Fehlentscheidungen und falsche Status-Meldungen zu verhindern, gelten ab sofort folgende Regeln:
1. **Kein "Erledigt" ohne Beweis:** Ein Task darf erst dann als abgeschlossen markiert werden, wenn ein Test-Log, ein erfolgreicher Build oder eine explizite Bestätigung des Users vorliegt.
2. **Status "Verifikation ausstehend":** Code, der geschrieben, aber nicht auf Hardware getestet wurde, muss zwingend diesen Zusatz tragen.
3. **Fakten-Check vor Abschluss:** Vor dem Senden der `submit`-Meldung muss der Agent prüfen: "Habe ich das wirklich laufen sehen oder nehme ich es nur an?"
4. **Fehler-Eingeständnis:** Bei Entdeckung einer Halluzination ist sofort der User zu informieren und der Status in allen Dokumenten (Roadmap, Journal) zu korrigieren.
+10 -207
View File
@@ -17,216 +17,19 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
### Hinzugefügt
- **Onboarding & Desktop-UX - 15.04.2026:**
- **Desktop-App:** Dynamisierung der Statusanzeigen im App-Footer ("Cloud synchronisiert" & "Verbunden").
- **Connectivity-Tracking:** Implementierung des `ConnectivityTracker` (KMP) zur Echtzeit-Überwachung der API-Gateway
Erreichbarkeit.
- **LAN-Erkennung:** Integration des `NetworkDiscoveryService` (mDNS) im Footer zur Anzeige aktiver Instanzen im
lokalen Netzwerk.
- **Onboarding:** Datenfluss vom `SettingsManager` bis in den Footer finalisiert (Anzeige des echten Gerätenamens).
- **Online-Nennung & Integration - 15.04.2026:**
- **Backend (Mail-Service):** Finalisierung des `MailController` für Web-Nennungen inkl. SMTP-Versand via World4You.
- **Frontend (Desktop):** `NennungsEingangScreen` an Live-Daten vom `mail-service` angebunden.
- **Repository:** `NennungRemoteRepository` (KMP) um `holeNennungen()` erweitert.
- **Billing & ÖTO - 15.04.2026:**
- **Sportförderbeitrag:** Automatische Buchung von 1,00 EUR (§16 ÖTO) bei jeder Nennung im `entries-service`
implementiert.
- **Basis-Infrastruktur & Domain-Definition:**
- DDD-Modelle für `Veranstaltung`, `Turnier`, `Bewerb` und `Abteilung` gemäß ÖTO definiert.
- ZNS-Parser Prototyp für Dateiformate (VEREIN01, LIZENZ01, PFERD01, RICHT01).
- Plan-B Mail-Service (Spring Boot) für Nennungs-Versand via World4You.
- Desktop-App Skelett mit Navigation und UI-Hüllen (Compose Desktop).
### Behoben
### Reality-Reset (28.04.2026)
- **Identity-Modul:** Umstellung auf `kotlin.time.Instant` zur Vermeidung von Deprecation-Warnungen und Behebung von
Persistenz-Konflikten im `ExposedDeviceRepository`.
- **Koin DI:** Korrektur von Typ-Inferenz-Fehlern beim `HttpClient` im `nennung-feature` durch explizite Qualifier.
- **Turnier-Feature:** Behebung eines unsicheren Casts (`Any!` zu `List<String>`) in `TurnierStammdatenTab.kt`.
- **Konfiguration:** Harmonisierung der Ports (Mail-Service auf 8083) in `.env`, `dc-backend.yaml` und
`PlatformConfig.jvm.kt`.
- **Korrektur:** Vormalige Einträge über "abgeschlossene" Billing-, Results- und Zeitplan-Features wurden entfernt, da
diese im Code nicht funktional hinterlegt waren.
- **Status:** Fokus zurück auf die Kern-Hierarchie (Veranstaltung -> Turnier -> Bewerb).
### Hinzugefügt
- **Phase 12 (Abrechnung & Infrastruktur) - 12.04.2026:**
- **Infrastruktur:** Docker-Integration für `billing-service` (Port 8087) und API-Gateway Routing vervollständigt.
- **Service Discovery:** Alle relevanten Microservices (`masterdata`, `events`, `results`, `series`, `billing`) sind nun bei Consul registriert.
- **Frontend Billing:** `BillingRepository` und `BillingViewModel` auf reale API-Anbindung (Ktor) umgestellt; `BillingScreen` funktionalisiert.
- **Backend (Series):** JPA-Entitäten `Serie` und `SeriePunkt` im `series-service` stabilisiert und Flyway-Migrationen für das Datenbankschema erstellt.
- **Fix:** Behebung von IDE-Mapping-Warnungen durch explizite `@Column` Namen in den JPA-Entitäten.
- **Backend Fixes - 12.04.2026:**
- **Infrastruktur:** Behebung von Startfehlern im `events-service` (DataSource) und `masterdata-service` (Consul).
- **Build:** Integration von `results-service` und `series-service` in `settings.gradle.kts`.
- **Domain:** `Serie` und `SeriePunkt` zu `data class` konvertiert (copy() Unterstützung).
- **Phase 11 (Ergebniserfassung & Platzierung) - 12.04.2026:**
- **Backend (Results):** `results-service` um JPA-Entitäten, Repositories und Business-Logik für Platzierungsberechnungen (Wertnote, Zeit, Fehler) ergänzt.
- **Infrastructure:** `dc-backend.yaml` und `GatewayConfig.kt` um den Service `results` (Port 8088) erweitert.
- **Frontend Domain:** `ErgebnisRepository` und `Ergebnis`-Modell für Wertnoten, Zeiten und Status erstellt.
- **Frontend UI:** `ErgebnisEditDialog` zur schnellen Ergebniserfassung hinzugefügt; `TurnierStartlistenTab` ermöglicht nun Erfassung per Zeilen-Klick.
- **Frontend UI:** `TurnierErgebnislistenTab` vervollständigt: Buttons für "Platzierung berechnen" und "Drucken" (PDF) funktionalisiert.
- **Fix:** Kompilierungsprobleme im `TurnierFeatureModule` und `ScreenPreviews.kt` behoben (fehlende `ergebnisRepo` Parameter).
### Hinzugefügt
- **Phase 10.4 (Series-Context Vertiefung) - 12.04.2026:**
- **Backend (Series):** `series-service` um Logik für Streichresultate (`ReglementTyp`) und Bindungsarten (Reiter-zentriert, Pferde-zentriert, Paar-Bindung) erweitert.
- **Infrastructure:** `dc-backend.yaml` und `GatewayConfig.kt` um den Service `series` (Port 8089) erweitert.
- **Frontend Domain:** `SeriesRepository` und Modelle an das neue Ranking-Format (`SerieStandEntry`) angepasst.
- **UI:** `SeriesScreen.kt` überarbeitet: Zeigt nun Reiter- und Pferde-IDs sowie Fortschritt pro Teilnehmer an.
- **Dokumentation:** `MASTER_ROADMAP.md` aktualisiert (Phase 10 & 11 auf 'Completed' gesetzt).
### Hinzugefügt
- **Phase 10.3 (Echter Datenverkehr & Infrastruktur) - 12.04.2026:**
- **Infrastructure:** Docker-Services für `masterdata`, `events` und `zns-import` in `dc-backend.yaml` ergänzt.
- **Gateway:** API-Gateway Routing für Masterdata (`/api/v1/masterdata`) und Events (`/api/v1/events`) konfiguriert.
- **Frontend (Vereine):** `VereinRepository` (Ktor) und `VereinViewModel` implementiert für echtes Anlegen von Veranstaltern.
- **Frontend (Events):** `TurnierViewModel` an das reale `TurnierRepository` angebunden.
- **Fix:** `verein-feature` Abhängigkeiten korrigiert (Network/Ktor).
- **Fix:** Polling-Endpoints im `ZnsImportViewModel` an das neue Gateway-Routing angepasst.
### Hinzugefügt
- **Phase 10.2 (Masterdata-Editoren & Organisation) - 12.04.2026:**
- **Frontend:** `MasterdataEditDialogs.kt` für die Bearbeitung von Reiter- und Pferdedaten direkt im Turnier-Kontext.
- **Frontend:** Erweiterung des `MasterdataRepository` um Schreibzugriffe (`saveReiter`, `savePferd`).
- **Frontend:** Funktionale Suche für Turnierleiter im `Organisation`-Tab via `NennungViewModel` und Masterdata-API.
- **Frontend:** State-Management für Stammdaten-Editoren im `NennungViewModel`.
- **Fix:** Kompilierungsfehler in `ScreenPreviews.kt` behoben (fehlende Interface-Methoden in Mocks).
- **Fix (Desktop Shell):** Fehlendes `turnierFeatureModule` in `main.kt` registriert und Login-Gate in `DesktopApp.kt` optimiert.
### Hinzugefügt
- **Phase 10 (Series-Context & Stammdaten) - 11.04.2026:**
- **Frontend:** Stammdaten-Infrastruktur im `turnier-feature` (Repositories, DTOs, Domänenmodelle) für Reiter, Pferde, Funktionäre und Vereine.
- **Frontend:** `NennungViewModel` zur Steuerung der Suche und Status-Verwaltung von Nennungen.
- **Frontend:** Funktionalisierung des `Nennungen`-Tabs (Suche, Echt-Datenanbindung) und Vorbereitung des `Organisation`-Tabs.
- **Frontend:** `DefaultMasterdataRepository` zur Suche in Reitern, Pferden und Funktionären via Backend-API.
- **Netzwerk:** Erweiterung der `ApiRoutes` um Endpunkte für Masterdata und Nennungen.
- **Phase 10 (Series-Context) Vorbereitung:**
- **Frontend:** Neuer `SeriesScreen.kt` für die Verwaltung von Cups und Meisterschaften (konfigurierbare Reglements).
- **Frontend:** Erweiterung des `AdminUebersichtScreen` (Cockpit) um KPI-Kacheln mit Direkt-Links zu Cups und Meisterschaften.
- **Frontend:** Integration der Series-Navigation in die Breadcrumbs und das globale Routing (`Meisterschaften`, `Cups`).
- **Turnier-Feature Hardening:**
- **Frontend:** `STARTLISTEN` und `ERGEBNISLISTEN` Tabs vollständig an das `BewerbViewModel` angebunden (Bewerbs-Auswahl mit echten Daten).
- **Frontend:** Implementierung der Starter-Anzeige in der Startliste (LazyColumn).
### Geändert
- **Turnier-Feature:** Sichtbarkeit von `BewerbViewModel.generateStartliste()` auf `public` geändert, um den Aufruf aus dem Tab zu ermöglichen.
- **Frontend (Desktop):** `ScreenPreviews.kt` aktualisiert zur Berücksichtigung der neuen ViewModel-Abhängigkeiten (`NennungViewModel`, `MasterdataRepository`).
### [Phase 9] - 11.04.2026
- **Frontend:** Interaktiver Drag & Drop Zeitplan mit automatischem 5-Minuten-Snapping und Konflikt-Visualisierung.
- **Frontend:** "B-Satz Export (ZNS)" Toolbar-Aktion mit integriertem Vorschau-Dialog.
- **Frontend:** "Änderungs-Historie" (Audit-Log) Sektion zur Nachverfolgung von Zeitplan-Anpassungen.
- **Backend:** `audit_log` Persistenz und Abfrage-API für manuelle Eingriffe in Bewerbe.
- **Backend:** ZNS B-Satz Export Endpunkt (`/export/zns/b-satz`) zur Generierung von `BBEWERBE` Datensätzen.
- **Core:** `FixedWidthLineBuilder` zur präzisen Generierung von ZNS-konformen Festbreiten-Formaten.
### Behoben
- **Infrastruktur:** Veraltete `newSuspendedTransaction` in `DatabaseFactory.kt` durch moderne `suspendTransaction` (Exposed v1) ersetzt.
- **Frontend (Desktop):** Kompilierfehler in `ScreenPreviews.kt` behoben, indem fehlende Interface-Methoden im Mock-Repository implementiert wurden.
- **Backend (Tests):** `JdbcSQLSyntaxErrorException` im `BewerbeZeitplanIntegrationTest` durch Korrektur des Schema-Setups (Audit-Log Tabelle) gelöst.
### Hinzugefügt
- **Bugfix**: Behebung von Build-Fehlern im `veranstalter-feature` nach der Paket-Konsolidierung.
- **Frontend**: `FakeVeranstalterRepository` in `commonMain` implementiert, um saubere KMP-DI zu ermöglichen.
- **Frontend**: Veraltete Imports und Referenzen im `meldestelle-desktop` Shell und Previews korrigiert.
- **Architektur:** Fachliches Konzept für Zeitplan-Optimierung (Drag & Drop) erstellt (`konzept-zeitplan-optimierung-de.md`).
- **Architektur:** Spezifikation des Status-Automaten für Nennungen und Synchronisations-Logik (`status-automat-nennungen-de.md`).
- **Rulebook:** Überprüfung und Spezifikation der Parcoursbesichtigung zu Pferd (§43 ÖTO) inkl. 5-Minuten-Puffer-Regel.
- **Backend (Entries):** Erweiterung der Domain-Modelle `Bewerb` und `Abteilung` um Besichtigungs- und Pausen-Konfigurationen.
- **Backend (Entries):** Neues Datenmodell `BesichtigungsBlock` für wettbewerbsübergreifende Parcoursbesichtigungen.
- **Backend (Entries):** API-Endpunkt `PATCH /bewerbe/{id}/zeitplan` für schnelle Zeitplan-Updates implementiert.
- **Backend (Entries):** `StartlistenService` um ÖTO-konforme Zeitberechnung (Besichtigungs-Puffer, Pausen-Intervalle) erweitert.
### Geändert
- Masterdata/Domain: Umbenennungen zur Vereinheitlichung der Terminologie (DE):
- `MasterdataLicenseRepository``LizenzRepository`
- `LicenseMatrixService``LizenzMatrixService`
- `LicenseMatrixServiceImpl``LizenzMatrixServiceImpl`
- Test: `LicenseMatrixServiceTest``LiznezMatrixServiceTest` (exakt nach Vorgabe)
- Infrastructure (Exposed): `LicenseTable``LizenzTable`
- Docs: Begriff „reit_lizenzen“ → „reiterlizenzen“ in Glossar/UL konsolidiert.
### Hinzugefügt
- **Events-Service Bundle:** Vollständige Stabilisierung der `events` Services (Domain, Infrastructure, API, Service).
- **Domain:** Umstellung auf `kotlin.time.Instant` zur Vermeidung von Deprecation-Warnungen (Kotlin 2.1.20+) und Harmonisierung mit dem Rulebook-Expert.
- **Infrastructure:** Anpassung an den `org.jetbrains.exposed.v1` Namespace und Implementierung von UUID-Konvertierungen zwischen `kotlin.uuid.Uuid` (Domain) und `java.util.UUID` (DB).
- **API:** Refactoring des `VeranstaltungController` zur direkten Repository-Nutzung (Alignment mit `entries` Service).
- **Service-Config:** Umstellung auf Flyway-basiertes Tenant-Schema-Management in `EventsDatabaseConfiguration`.
- **Build:** Behebung des `shadowJar` Fehlers in `events-infrastructure` durch Entfernen des unnötigen `ktor` Plugins in der Library-Schicht.
- Masterdata: Automatisches Seeding aller Reiterlizenzen (license_matrix) beim Start des `masterdata-service` via `ReiterlizenzenSeeder` (idempotent; SPRINGEN: LIZENZFREI,R1R4; DRESSUR: LIZENZFREI,RD1RD3).
- **ZNS-Import (LIZENZ01.dat):** Robuster Lizenz-Tokenizer und Normalizer implementiert.
- Erkennung: `RD1..RD4`, `R1..R4`, `S1..S4`, `D2..D4`, Kombis `R{n}D{m}`, `R{n}S{k}`, `RDS4` (rechts-/letztes Vorkommen gewinnt).
- Normalisierung: `S*→R*`, `D*→RD*`, `RD4→RD3` (bis Enum verfügbar), `R{n}S{k}→Rmax(n,k)`, `R{n}D{m}→R{n}+RD{m}`.
- Integration: `ZnsReiterParser` füllt `lizenzen`-Liste (1:n) entsprechend und leitet `lizenzKlasse` bei fehlendem 4SpaltenCode aus Token ab.
- QA: Neue Unit-Tests (Tokenizer) für Beispiele `R2S3`, `R2D4`, `RD2` u. a.; alle Parser-Tests grün.
- **Core:** Modularisierte ZNS-Parser eingeführt (`ZnsVereinParser`, `ZnsReiterParser`, `ZnsPferdParser`, `ZnsFunktionaerParser`) zur Verbesserung der Wartbarkeit und Unterstützung von Einzelimporten.
- **Fix:** SQL-Migrationsfehler in `V010` behoben, indem die Umbenennung der Spalte `name` in `verein_name` durch einen idempotenten `DO`-Block abgesichert wurde (behebt "Unable to resolve column 'name'").
- **Infrastructure:** Datenbank-Migration `V010` hinzugefügt, um das Schema final mit den `Exposed`-Modellen zu synchronisieren.
- **Infrastructure:** Datei-Archivierung für hochgeladene ZNS-ZIP-Dateien im `ZnsImportOrchestrator` implementiert.
- **Infrastructure:** `ZnsImportService` vollständig auf die neuen spezialisierten Parser umgestellt und als Spring-Bean im Backend registriert.
- **QA:** Umfassende Test-Suite `ZnsParserTest.kt` mit realen ZNS-Daten (Hämmerle, Neuwirth, etc.) erstellt; Korrektur der Extraktions-Logik für Mitgliedsnummern (Position 147) und Funktionär-Daten (RICHT01).
- **QA:** Neue Betriebsanleitung für ZNS-Importer Tests erstellt: `docs/07_Infrastructure/runbooks/ZNS_Importer_Test_Manual.md`.
- **Infrastructure:** `MasterdataDatabaseConfiguration` korrigiert: Expliziter Aufruf von `Database.connect()` hinzugefügt, um Abstürze beim Anwendungsstart ("No database specified") zu beheben.
- **Infrastructure:** `application.yml` im `masterdata-service` vervollständigt (DataSource-Konfiguration mit `pg-user`/`pg-password` und Flyway-Aktivierung).
- **Domain:** Legacy-Spezifikationen für ZNS-Schnittstellen (Import/Export) formalisiert:
- `docs/03_Domain/02_Reference/Legacy_Specs/OETO-2026_Meldestelle_Pflichtenheft_V2.4.md` (Basis-Satzarten A-N)
- `docs/03_Domain/02_Reference/Legacy_Specs/OETO-2026_Meldestelle_Erweiterung-Schnittstelle_2014.md` (XML-Erweiterung, LinkID-Logik)
- **QA B-2:** `OnboardingValidator`-Objekt extrahiert; `OnboardingValidatorTest.kt` (17 Unit-Tests: Pflichtfeld-Guard,
Doppelklick-Schutz, Abbrechen-Reset, rememberSaveable-Regression)
- **QA B-3:** `AbteilungsRegelServiceTest.kt` um 14 Tests erweitert: CSN-C-NEU ≤95 cm / ≥100 cm Pflicht-Teilung,
ORGANISATORISCH, SEPARATE_SIEGEREHRUNG, Caprilli-Regression, Grenzfälle 90/110 cm
- **Domain:** `AbteilungsTeilungsTypE` um `ORGANISATORISCH` und `SEPARATE_SIEGEREHRUNG` erweitert
### Behoben
- **Masterdata/Infrastructure:** Kompilierfehler in `AltersklasseRepositoryImpl` durch Vereinheitlichung der Exposed-Tabellendefinition behoben:
- `AltersklassenTable``AltersklasseTable`
- Spalte `altersklassen_code``altersklasse_code`
- Tabellenname `altersklassen``altersklasse`
- **Masterdata/API:** Fehlendes Interface-Mapping ergänzt: `RegulationRepository` enthält nun `findAllTurnierklassen()`; `ExposedRegulationRepository` implementiert die Methode und `RegulationController` kompiliert wieder.
- **ZNS-Import:** `AltersklassenExposedRepository` korrigiert (richtiger Domain-Typ `AltersklasseDefinition`, Mapping von `SparteE` und Zeitstempeln).
- **Migration V013:** Idempotent und robust gemacht. Alle `ALTER TABLE ... RENAME`-Operationen laufen nun nur, wenn die Quell-Tabelle existiert (Fix für "Unable to resolve table 'bundesland'/'turnierklasse'").
- **Lizenz-Validierung:** `LicenseMatrixServiceImpl` um Cross-Discipline-Mapping R↔RD (ÖTO-Äquivalenzen) erweitert. Damit funktionieren Fälle wie Dressur-Starts mit Spring-Lizenz (R1→RD1, R2→RD2, R3/R4→RD3) bzw. umgekehrt konsistent.
- **Domain:** Striktere Spartenlizenz-Prüfung in `Reiter.hasLizenzForSparte` implementiert (RD1..RD3 nur DRESSUR; R1..R4 nur SPRINGEN). Behebt Testfehler „isEligible verweigert Start ohne passende Spartenlizenz“ im `LicenseMatrixServiceTest`.
### Behoben
- **Backend (Entries):** Fehlschlagenden Unit-Test `berechneStartzeiten sollte Zeiten korrekt aufsummieren` korrigiert; der Test berücksichtigt nun den neuen 5-minütigen ÖTO-konformen Puffer nach der Parcoursbesichtigung (§43).
- **Frontend (Desktop):** Build-Fehler ("No matching variant") beim `funktionaer-feature` behoben; fehlendes `build.gradle.kts` mit JVM-Target und Compose/Koin-Abhängigkeiten ergänzt.
- **Frontend (Desktop):** Massive Inkonsistenzen in der Paketstruktur des `veranstalter-feature` bereinigt; alle Komponenten (ViewModel, Screens, Mocks) auf das Standardpaket `at.mocode.frontend.features.veranstalter` konsolidiert, um Redeklarationen und Import-Fehler zu beheben.
- **Frontend (Desktop):** Kompilierfehler im `VeranstalterDetailScreen` durch korrekte Paket-Referenzierung des `FakeVeranstaltungStore` gelöst.
### Dokumentation
- **Masterdata/Docs:** `REITER_LIZENZEN.md` überarbeitet:
- Strikte Sparten-Trennung dokumentiert (RD1..RD3 nur Dressur; R1..R4 nur Springen).
- Dressur-Tabelle korrigiert (R-Lizenzen entfernen; RD-Pflicht je Klasse).
- Validierungslogik ergänzt (2-stufig: Spartenlizenz → Max-Turnierklasse; R↔RD Mapping nur zur Kappung, nicht zur Eligibility).
- Vielseitigkeit (CCN/CCI) ergänzt: kumulative Anforderungen (Dressur RD* UND Springen R* je Klasse); Startkartenregel für Einsteiger.
- Fahren (CAN/CAI) ergänzt: aktueller Systemzustand ohne `F*`Lizenzen dokumentiert; Teilnahme über Startkarte/Ausschreibung, geplante EnumErweiterung vermerkt.
- §15Tabelle (kompakt) integriert und auf ÖTOReferenz verlinkt; Bedeutungen „B,C“ und „LP“ erläutert. Hinweis aufgenommen, dass `RD4` derzeit nicht als Enum vorhanden ist und wie `RD3` behandelt wird.
- Kombinationsreihen gemäß §15 ergänzt: `R1S2`, `R1S3`, `R1S4`, `R2S3`, `R2S4`, `R3S4` (neuer Unterabschnitt 2.6 mit Tabelle, identische Spalten wie 2.5).
### Behoben
- **Masterdata:** Qualifikations-Management für Funktionäre (Richter/Parcoursbauer) professionalisiert: Umstellung von unstrukturiertem Text auf offizielle ÖTO/FEI Master-Daten Referenzen (`QualifikationMasterTable`).
- **Masterdata:** Fehlende Tabelle `funktionaer_qualifikation` in der Initialisierung beider Services (`masterdata` und `zns-import`) ergänzt, um `PSQLException` während des ZNS-Imports zu beheben.
- **Infrastructure:** Start-Probleme des `masterdata-service` endgültig behoben: Port-Konflikt zwischen Spring Boot (Management/Actuator) und dem Gateway (8081) durch Umzug auf Port 8086 (gemäß Infrastruktur-Vorgaben) gelöst.
- **Infrastructure:** Port-Konflikt im `masterdata-service` durch Trennung der Bind-Adressen (Spring: 127.0.0.1, Ktor: 0.0.0.0) und Bereinigung verwaister Prozesse stabilisiert.
- **Core:** Veraltete `ZnsLegacyParsersTest.kt` entfernt; alle Tests sind nun in `ZnsParserTest.kt` konsolidiert.
- **Domain:** Fehlschlagenden `LicenseMatrixServiceTest` behoben; fehlende `reiterLizenz`-Daten in Test-Reitern ergänzt und Fallback-Logik in `LicenseMatrixServiceImpl` für spartenübergreifende Lizenzen (z.B. Springlizenz für Dressur-Basis) stabilisiert.
- **Infrastructure:** Fehlschlagenden `RegulationSeedVerificationTest` behoben; Testdaten an das neue Modell (`reiterLizenz` Feld) angepasst.
- **Infrastructure:** Kompilierfehler 'Unresolved reference lizenzKlasse' in `ReiterExposedRepository` behoben; fehlendes Feld `lizenzKlasse` zu `ReiterTable` und Datenbank-Migration `V010` hinzugefügt.
- **Onboarding:** `remember``rememberSaveable` für `geraetName`, `sharedKey`, `znsStatus` in `OnboardingScreen.kt` (
Felder gingen bei Zurück-Navigation verloren)
- **AbteilungsRegelService:** CSN-C-NEU Pflicht-Teilungslogik implementiert (≤95 cm: ohne/mit Lizenz; ≥100 cm: R1/R2+);
`SparteE`-Import ergänzt
- Desktop-Packaging konfiguriert: `.deb` (Linux), `.msi` (Windows), `.dmg` (macOS)
- Zentrale Versionsdatei `version.properties` (Single Source of Truth für SemVer)
- Automatisches Git-Tagging via CI/CD (`release.yml` Gitea Actions Workflow)
- `CHANGELOG.md` eingeführt (dieses Dokument)
---
## [1.0.6-SNAPSHOT] — 2026-04-10
### [1.0.6-SNAPSHOT] — 2026-04-10
### Hinzugefügt
- **Entries-Domain:** Strukturiertes Abteilungs-Warnungssystem gemäß ÖTO § 39 implementiert.
+5 -5
View File
@@ -13,12 +13,12 @@ Die gesamte Projektdokumentation (Architektur, Fachdomäne, Entwickler-Anleitung
**Starte hier:** [**→ docs/README.md**](./docs/README.md)
| Bereich | Inhalt |
|-----------------------------------------------|---------------------------------------------|
| Bereich | Inhalt |
|-----------------------------------------------|---------------------------------------------------|
| [01_Architecture](./docs/01_Architecture) | Master Roadmap, ADRs, C4Modelle, DesktopKonzept |
| [02_Guides](./docs/02_Guides) | Setup-Anleitungen, Entwickler-Guidelines |
| [03_Domain](./docs/03_Domain) | Fachlichkeit, Turnierregeln, Entities |
| [07_Infrastructure](./docs/07_Infrastructure) | Docker, Keycloak, CI/CD, Zora-Infrastruktur |
| [02_Guides](./docs/02_Guides) | Setup-Anleitungen, Entwickler-Guidelines |
| [03_Domain](./docs/03_Domain) | Fachlichkeit, Turnierregeln, Entities |
| [07_Infrastructure](./docs/07_Infrastructure) | Docker, Keycloak, CI/CD, Zora-Infrastruktur |
Wesentliche Architektur-Referenz: [OfflineFirst Desktop & Backend (Kurzkonzept)](./docs/01_Architecture/konzept-offline-first-desktop-backend-de.md)
File diff suppressed because it is too large Load Diff
@@ -1,72 +0,0 @@
# Tech-Stack Referenz: Kotlin 2.3.0 & Java 25 (KMP)
### 1. Kern-Spezifikationen
| Komponente | Version | Status |
| --- |----------| --- |
| **Kotlin** | `2.3.0` | Stabil (K2 Compiler standardmäßig aktiv) |
| **Java (JDK)** | `25` | LTS (Long-Term Support) |
| **Gradle** | `9.2.1` | Erforderlich für JDK 25 Support |
| **Android Plugin (AGP)** | `8.8.0+` | Empfohlen für Gradle 9.x Kompatibilität |
---
### 2. Gradle Konfiguration (`build.gradle.kts`)
Für ein **Kotlin Multiplatform (KMP)** Projekt ist die Java Toolchain-Konfiguration entscheidend, um sicherzustellen, dass der Kotlin-Compiler und die JVM-Targets Java 25 korrekt ansprechen.
```kotlin
plugins {
kotlin("multiplatform") version "2.3.0"
id("com.android.library") version "8.8.0" // Falls Android Target genutzt wird
}
kotlin {
// Globale Toolchain-Definition für alle JVM/Android Targets
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
jvm {
compilations.all {
compilerOptions.configure {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_25)
}
}
}
// Weitere Targets (Beispiel iOS)
iosArm64()
iosSimulatorArm64()
}
```
---
### 3. Gradle Wrapper Update
Damit das Projekt Java 25 erkennt, muss der Wrapper auf dem neuesten Stand sein:
**Terminal-Befehl:**
```bash
./gradlew wrapper --gradle-version 9.2.1 --distribution-type all
```
---
### 4. Wichtige Kompatibilitätshinweise für das Plugin
* **IDE-Version:** Stelle sicher, dass **IntelliJ IDEA 2025.3** (oder neuer) installiert ist, da erst diese Version die volle Unterstützung für JDK 25 Sprachfeatures und das Kotlin 2.3.0 Plugin bietet.
* **K2 Compiler:** Kotlin 2.3.0 nutzt den K2-Compiler. Falls das Google AI Pro Plugin Code-Analysen durchführt, sollte es auf dem K2-Modus basieren.
* **Bytecode:** Java 25 Bytecode wird nur generiert, wenn das `jvmTarget` explizit auf `25` gesetzt ist. Andernfalls verbleibt Kotlin standardmäßig bei einer niedrigeren Version (meist 1.8 oder 11), was die neuen JDK-Features einschränken könnte.
---
### 5. Bekannte Features in diesem Setup
* **Java 25 Features:** Unterstützung für die finalen Versionen von *Scoped Values* und *Structured Concurrency*.
* **Kotlin 2.3.0 Features:** Nutzung von `explicit backing fields` und dem verbesserten `unused return value` Checker.
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -1,63 +0,0 @@
### Analyse und Strategie zur Wiederherstellung und Strukturverbesserung
Es ist eine klassische Situation: Nach einem großen Technologie-Upgrade (Kotlin 2.1.0+, Java 25, Spring Boot 3.5.x) knirscht es oft an den Schnittstellen. Da dein `ping-service` als technischer Blueprint dient, ist er der absolut richtige Startpunkt.
Hier ist der Schlachtplan, um Ordnung zu schaffen und die hexagonale Architektur sauber zu etablieren:
### 1. Wo beginnen? Bottom-Up vs. Top-Down
Da deine Infrastruktur aktuell nicht stabil läuft, empfehle ich einen **"Core-First"** Ansatz, gefolgt vom **Backend-Durchstich**.
* **Zuerst: Core & Platform:** Ohne eine stabile Basis (`platform-bom`, `platform-dependencies`, `core-domain`) werden die anderen Module immer wieder Kompilierfehler werfen.
* **Dann: Der technische vertikale Durchstich (`ping-service`):** Sobald die Plattform steht, reparieren wir den Weg: `Infrastruktur (Docker) -> Ping-Service -> Gateway`.
* **Zuletzt: Frontend:** Das Frontend (BFF-Gedanke) wird erst dann stabil, wenn die API-Contracts des Backends wieder verlässlich geliefert werden.
### 2. Ordnung schaffen: Der "Clean Desk" im Projekt
Bevor wir Code fixen, müssen wir die Build-Umgebung aufräumen:
1. **Version Catalog Synchronität:** Deine `libs.versions.toml` nutzt bereits Java 25 und Kotlin 2.1.0. Prüfe, ob alle Gradle-Plugins (insbesondere das `compose-multiplatform` und `spring-boot` Plugin) mit Kotlin 2.1.0 kompatibel sind. Oft ist hier ein Downgrade auf die letzte stabile Version oder ein Upgrade auf Alpha/Beta-Versionen nötig, wenn man "Bleeding Edge" (Java 25) nutzt.
2. **Modul-Konsolidierung (DDD):** Wie besprochen, solltest du die "Modul-Explosion" reduzieren.
* **Vorschlag:** Statt 5 Module pro Domain (`api`, `common`, `domain`, `infrastructure`, `service`), reduziere es auf maximal zwei:
* `domain-api`: Nur DTOs und Interfaces (für KMP-Sharing mit dem Frontend).
* `domain-service`: Die gesamte Implementierung (Hexagonal strukturiert in Packages).
### 3. Hexagonale Architektur im `ping-service` umsetzen
Dein `ping-service` ist aktuell noch sehr "Spring-lastig" (Controller ruft Service mit Circuit Breaker direkt auf). Für eine echte hexagonale Vorlage strukturiere das Modul `ping-service` intern wie folgt um:
```text
at.mocode.ping.service
├── adapter
│ ├── in
│ │ └── web (PingController - Dein primärer Port-Adapter)
│ └── out
│ └── persistence (PingRepositoryAdapter - Sekundärer Port-Adapter)
├── application
│ ├── port
│ │ ├── in (PingUseCase - Das Interface für den Controller)
│ │ └── out (PingOutputPort - Interface für die Datenbank)
│ └── service (PingService - Hier liegt die Business Logik, OHNE Spring-Annotationen wo möglich)
└── domain
└── model (PingEntity/Value Objects)
```
**Der Vorteil:** Wenn du dieses Muster im `ping-service` einmal sauber hast, kopierst du diese Package-Struktur für `registry`, `events` etc.
### 4. Konkrete Schritte zur Reparatur
**Schritt 1: Infrastruktur-Check (Docker)**
Stelle sicher, dass die Basisdienste laufen. Java 25 benötigt oft neuere Container-Images.
* Check `docker-compose.yaml`: Laufen Postgres und Keycloak?
* `ping-service` application.yaml: Aktiviere die Datenbank-Verbindung (JPA), die aktuell noch auskommentiert ist, um den "echten" Test zu ermöglichen.
**Schritt 2: Backend API-Gateway Fix**
Dein Gateway ist der Wächter. Wenn die Security-Konfiguration (`SecurityConfig.kt`) wegen Bibliotheks-Änderungen in Spring Security 7/Spring Boot 3.5 hakt, ist das Priorität 1.
* Test: Kannst du den `ping-service` direkt aufrufen? Wenn ja, funktioniert das Gateway-Routing?
**Schritt 3: Frontend (BFF) Anpassung**
Nutze das Frontend als Konsument. Wenn du den `PingApiClient` im Frontend hast, sollte dieser gegen das **Gateway** (BFF-Pattern) laufen, nicht direkt gegen den Service.
### Empfehlung zur Vorgehensweise (Prioritäten):
1. **Gradle-Build stabilisieren:** Alle `:platform:*` und `:core:*` Module müssen mit `./gradlew build` fehlerfrei durchlaufen.
2. **Ping-SCS fertigstellen:** Implementiere eine minimale Datenbank-Speicherung im `ping-service`. Das ist der Beweis, dass die JPA/Hibernate-Konfiguration mit Java 25 harmoniert.
3. **Gateway-Security:** Stelle sicher, dass das JWT von Keycloak korrekt zum `ping-service` durchgereicht wird.
**Soll ich dir bei einem dieser spezifischen Schritte (z.B. der hexagonalen Package-Struktur für den Ping-Service) mit konkretem Code helfen?**
File diff suppressed because it is too large Load Diff
@@ -1,323 +0,0 @@
Meldestelle on  main [✘»!+?] via 🅶 v9.2.1 via ☕ v25.0.1 via 🅺 v2.3.0
./gradlew :backend:infrastructure:gateway:test --stacktrace
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details
Type-safe project accessors is an incubating feature.
> Task :backend:infrastructure:gateway:test
WebFluxSmokeTest > should load reactive web context and serve smoke endpoint() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
GatewayFiltersTests > should preserve existing correlation ID header() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
GatewayFiltersTests > should handle requests with X-Forwarded-For header() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayFiltersTests > should apply admin rate limit for admin users() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayFiltersTests > should add rate limiting headers() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayFiltersTests > should add correlation ID header when not present() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayFiltersTests > should apply different rate limits for auth endpoints() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayFiltersTests > should enforce rate limiting after exceeding limit() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayFiltersTests > should apply higher rate limit for authenticated users() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
KeycloakGatewayIntegrationTest > should initialize Spring context with Keycloak configuration() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
GatewayApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
FallbackControllerTests > should handle POST requests to masterdata fallback() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
FallbackControllerTests > should handle POST requests to default fallback() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should return masterdata service fallback response() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should handle POST requests to members fallback() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should handle POST requests to events fallback() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should return valid JSON structure for all fallback responses() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should handle POST requests to auth fallback() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > sollte Members Service Fallback Response zurueckgeben() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should have consistent error response structure() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > sollte Events Service Fallback Response zurueckgeben() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should return auth service fallback response() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > sollte Horses Service Fallback Response zurueckgeben() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should handle POST requests to horses fallback() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
FallbackControllerTests > should return default fallback response for unknown service() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should handle different HTTP methods allowed in CORS() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
GatewaySecurityTests > should handle complex CORS scenarios() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should handle PUT requests with CORS headers() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should allow requests from meldestelle domain() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should allow credentials in CORS requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should set max age for CORS requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should handle authorization headers in CORS requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should not duplicate CORS headers due to deduplication filter() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should handle CORS preflight requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should allow requests from localhost origins() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should maintain security headers in responses() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should handle POST requests with CORS headers() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewaySecurityTests > should handle DELETE requests with CORS headers() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayRoutingTests > should route ping service requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at ClassUtils.java:372
Caused by: java.lang.ClassNotFoundException at BuiltinClassLoader.java:642
GatewayRoutingTests > should route horses service requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayRoutingTests > should handle gateway info path request() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayRoutingTests > should route members service requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayRoutingTests > auth route is not configured anymore() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayRoutingTests > should route masterdata service requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
GatewayRoutingTests > should route events service requests() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:145
45 tests completed, 45 failed
> Task :backend:infrastructure:gateway:test FAILED
[Incubating] Problems report is available at: file:///home/stefan/WsMeldestelle/Meldestelle/build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':backend:infrastructure:gateway:test'.
> There were failing tests. See the report at: file:///home/stefan/WsMeldestelle/Meldestelle/backend/infrastructure/gateway/build/reports/tests/test/index.html
* Try:
> Run with --scan to generate a Build Scan (powered by Develocity).
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':backend:infrastructure:gateway:test'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:135)
at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:288)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:133)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:121)
at org.gradle.api.internal.tasks.execution.ProblemsTaskPathTrackingTaskExecuter.execute(ProblemsTaskPathTrackingTaskExecuter.java:41)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.DefaultNodeExecutor.executeLocalTaskNode(DefaultNodeExecutor.java:55)
at org.gradle.execution.plan.DefaultNodeExecutor.execute(DefaultNodeExecutor.java:34)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:355)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:339)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:84)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:339)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:328)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
Caused by: org.gradle.api.internal.exceptions.MarkedVerificationException: There were failing tests. See the report at: file:///home/stefan/WsMeldestelle/Meldestelle/backend/infrastructure/gateway/build/reports/tests/test/index.html
at org.gradle.api.tasks.testing.AbstractTestTask.handleTestFailures(AbstractTestTask.java:703)
at org.gradle.api.tasks.testing.AbstractTestTask.handleCollectedResults(AbstractTestTask.java:535)
at org.gradle.api.tasks.testing.AbstractTestTask.executeTests(AbstractTestTask.java:527)
at org.gradle.api.tasks.testing.Test.executeTests(Test.java:714)
at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:125)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:252)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:237)
at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:220)
at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:203)
at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:170)
at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:105)
at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:44)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:59)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:56)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:56)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44)
at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:42)
at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:75)
at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
at org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:50)
at org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:28)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:61)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:26)
at org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:69)
at org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:46)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:39)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:28)
at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:189)
at org.gradle.internal.execution.steps.BuildCacheStep.executeAndStoreInCache(BuildCacheStep.java:145)
at org.gradle.internal.execution.steps.BuildCacheStep.lambda$executeWithCache$4(BuildCacheStep.java:101)
at org.gradle.internal.execution.steps.BuildCacheStep.lambda$executeWithCache$5(BuildCacheStep.java:101)
at org.gradle.internal.Try$Success.map(Try.java:170)
at org.gradle.internal.execution.steps.BuildCacheStep.executeWithCache(BuildCacheStep.java:85)
at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$0(BuildCacheStep.java:74)
at org.gradle.internal.Either$Left.fold(Either.java:116)
at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:62)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:46)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:35)
at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:75)
at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:53)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:53)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:35)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
at org.gradle.internal.execution.steps.ResolveIncrementalCachingStateStep.executeDelegate(ResolveIncrementalCachingStateStep.java:49)
at org.gradle.internal.execution.steps.ResolveIncrementalCachingStateStep.executeDelegate(ResolveIncrementalCachingStateStep.java:27)
at org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:71)
at org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:39)
at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:64)
at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:35)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:62)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:40)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:76)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:45)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:136)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:66)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:38)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:75)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.execute(HandleStaleOutputsStep.java:41)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35)
at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:297)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:31)
at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:64)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:132)
... 30 more
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/9.2.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 23s
17 actionable tasks: 4 executed, 13 up-to-date
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
Placeholder to ensure directory exists
-109
View File
@@ -1,109 +0,0 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
jvm("desktop")
js(IR) {
// WICHTIG: Als Library kompilieren für Webpack Federation
binaries.library()
generateTypeScriptDefinitions()
browser {
commonWebpackConfig {
cssSupport {
enabled.set(true)
}
}
}
}
// Wasm vorerst deaktiviert
/*
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}
*/
sourceSets {
commonMain {
dependencies {
implementation(projects.frontend.core.domain)
// implementation(projects.frontend.core.designSystem) // REMOVED: Circular dependency
implementation(projects.frontend.core.navigation)
implementation(projects.frontend.core.network)
implementation(projects.frontend.core.localDb)
// Features - REMOVED: Circular dependency. Shared should NOT depend on features.
// implementation(projects.frontend.features.authFeature)
// implementation(projects.frontend.features.pingFeature)
// KMP Bundles
implementation(libs.bundles.kmp.common)
implementation(libs.bundles.compose.common)
// Ktor (used directly in shared/di and shared/network)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.serialization.kotlinx.json)
// Serialization
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.core)
// implementation(libs.sqldelight.coroutines) // Wird transitiv über core:localDb geladen
// Compose
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
// Koin
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
}
}
commonTest {
dependencies {
implementation(libs.kotlin.test)
}
}
val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
// implementation(libs.sqldelight.driver.sqlite) // Wird transitiv über core:localDb geladen
}
}
val jsMain by getting {
dependencies {
implementation(libs.ktor.client.js)
// implementation(libs.sqldelight.driver.web) // Wird transitiv über core:localDb geladen
// Webpack Plugin für Federation Support (falls benötigt)
implementation(devNpm("copy-webpack-plugin", "12.0.0"))
}
}
/*
val wasmJsMain by getting {
dependencies {
implementation(libs.ktor.client.js)
}
}
*/
}
}
@@ -1,12 +0,0 @@
package at.mocode.shared.core
data class AppConfig(
val gatewayUrl: String,
val isDebug: Boolean
)
// Standard-Config für Local Development
val devConfig = AppConfig(
gatewayUrl = "http://localhost:8081",
isDebug = true
)
@@ -1,29 +0,0 @@
package at.mocode.shared.core
/**
* Shared application configuration constants for clients.
* These defaults target local development environments.
*/
object AppConstants {
// Gateway base URL (reverse proxy / API gateway)
// Used by NetworkConfig via PlatformConfig
const val GATEWAY_URL: String = "http://localhost:8081"
// Keycloak configuration
const val KEYCLOAK_URL: String = "http://localhost:8180"
const val KEYCLOAK_REALM: String = "meldestelle"
// Use 'postman-client' for Desktop App Password Flow (Direct Access Grants enabled)
// 'web-app' is for Browser Flow (PKCE)
// TODO: Make this platform-dependent (Desktop vs Web)
const val KEYCLOAK_CLIENT_ID: String = "web-app"
// DEV ONLY: Client Secret for 'postman-client' (Confidential Client)
// In Production, this should NEVER be in the frontend code.
// For the Desktop App Pilot, we use this to simulate a secure client.
// For 'web-app' (Public Client), this is not needed/used if configured correctly,
// but our AuthApiClient might be sending it.
const val KEYCLOAK_CLIENT_SECRET: String = "postman-secret-123"
// Removed unused browser flow URLs (registerUrl, loginUrl, etc.) as we focus on Desktop App.
}
@@ -1,27 +0,0 @@
package at.mocode.shared.data.repository
import at.mocode.shared.domain.model.PingData
import at.mocode.shared.domain.model.Resource
import at.mocode.shared.domain.repository.PingRepository
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
class PingRepositoryImpl(
private val httpClient: HttpClient
) : PingRepository {
override suspend fun checkSystemStatus(): Resource<PingData> {
return try {
// Der HttpClient hat die BaseURL schon konfiguriert (siehe NetworkModule)
val response = httpClient.get("/api/ping/simple").body<PingData>()
Resource.Success(response)
} catch (e: Exception) {
// Hier fangen wir Netzwerkfehler ab und machen sie "hübsch" für die UI
Resource.Error(
message = "Verbindung fehlgeschlagen: ${e.message ?: "Unbekannter Fehler"}",
code = "NETWORK_ERROR"
)
}
}
}
@@ -1,50 +0,0 @@
package at.mocode.shared.di
import at.mocode.shared.core.AppConfig
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import org.koin.dsl.module
val networkModule = module {
// 1. JSON Konfiguration (Global verfügbar)
single {
Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
}
}
// 2. HttpClient (Singleton)
single {
val config = get<AppConfig>()
val jsonConfig = get<Json>()
HttpClient {
// Standard-URL setzen
defaultRequest {
url(config.gatewayUrl)
contentType(ContentType.Application.Json)
}
install(ContentNegotiation) {
json(jsonConfig)
}
install(Logging) {
level = if (config.isDebug) LogLevel.INFO else LogLevel.NONE
logger = Logger.DEFAULT
}
install(HttpTimeout) {
requestTimeoutMillis = 10000
connectTimeoutMillis = 10000
}
}
}
}
@@ -1,26 +0,0 @@
package at.mocode.shared.di
import at.mocode.shared.core.devConfig
import at.mocode.frontend.core.network.networkModule
import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration
import org.koin.dsl.module
// Das Modul für die Config
val configModule = module {
single { devConfig } // Später können wir hier PROD/DEV umschalten
}
// Basismodule, die immer geladen werden sollen (ohne Feature/Core-Cross-Imports)
val baseSharedModules = listOf(
configModule,
// Network module provides DI-only HttpClient (safe to be shared across features)
networkModule
)
// Helper zum Starten von Koin (wird von der App aufgerufen)
// Weitere Module (z. B. networkModule) können über appDeclaration hinzugefügt werden.
fun initKoin(appDeclaration: KoinAppDeclaration = {}) = startKoin {
modules(baseSharedModules)
appDeclaration()
}
@@ -1,41 +0,0 @@
package at.mocode.shared.domain.model
import kotlinx.serialization.Serializable
/**
* Generischer Wrapper für API-Antworten.
*/
@Serializable
data class ApiResponse<T>(
val success: Boolean,
val data: T? = null,
val error: ApiError? = null
)
@Serializable
data class ApiError(
val code: String,
val message: String,
val details: Map<String, String> = emptyMap()
)
/**
* Das Ergebnis eines Repository-Aufrufs.
* Die UI kennt nur das hier, keine HTTP-Exceptions!
*/
sealed class Resource<out T> {
data class Success<T>(val data: T) : Resource<T>()
data class Error(val message: String, val code: String? = null) : Resource<Nothing>()
data object Loading : Resource<Nothing>()
}
/**
* Datenmodell für den Ping.
*/
@Serializable
data class PingData(
val status: String,
val timestamp: String,
val service: String
)
@@ -1,8 +0,0 @@
package at.mocode.shared.domain.repository
import at.mocode.shared.domain.model.PingData
import at.mocode.shared.domain.model.Resource
interface PingRepository {
suspend fun checkSystemStatus(): Resource<PingData>
}
@@ -1,178 +0,0 @@
package at.mocode.shared.navigation
import at.mocode.shared.presentation.actions.AppAction
import at.mocode.shared.presentation.store.AppStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* Navigation manager for handling routing and navigation logic
*/
class NavigationManager(
private val store: AppStore
) {
/**
* Current route as a flow
*/
val currentRoute: Flow<String> = store.state.map { it.navigation.currentRoute }
/**
* Navigation history as a flow
*/
val navigationHistory: Flow<List<String>> = store.state.map { it.navigation.history }
/**
* Can go back flag as a flow
*/
val canGoBack: Flow<Boolean> = store.state.map { it.navigation.canGoBack }
/**
* Navigate to a specific route
*/
fun navigateTo(route: String) {
store.dispatch(AppAction.Navigation.NavigateTo(route))
}
/**
* Navigate back to the previous route
*/
fun navigateBack() {
store.dispatch(AppAction.Navigation.NavigateBack)
}
/**
* Replace current route without adding to history
*/
fun replaceRoute(route: String) {
store.dispatch(AppAction.Navigation.UpdateHistory(route))
}
/**
* Clear navigation history and navigate to the route
*/
fun navigateAndClearHistory(route: String) {
// First clear by replacing with the new route
store.dispatch(AppAction.Navigation.UpdateHistory(route))
}
/**
* Get current route value (non-reactive)
*/
fun getCurrentRoute(): String = store.state.value.navigation.currentRoute
/**
* Check if we can navigate back
*/
fun canNavigateBack(): Boolean = store.state.value.navigation.canGoBack
}
/**
* Route definitions for the application
*/
object Routes {
const val HOME = "/"
const val LOGIN = "/login"
const val DASHBOARD = "/dashboard"
const val PROFILE = "/profile"
const val SETTINGS = "/settings"
const val PING = "/ping"
// Auth-related routes
object Auth {
const val LOGIN = "/auth/login"
const val LOGOUT = "/auth/logout"
const val REGISTER = "/auth/register"
const val FORGOT_PASSWORD = "/auth/forgot-password"
}
// Admin routes
object Admin {
const val DASHBOARD = "/admin/dashboard"
const val USERS = "/admin/users"
const val SETTINGS = "/admin/settings"
}
// Feature routes
object Features {
const val PING = "/features/ping"
const val REPORTS = "/features/reports"
const val NOTIFICATIONS = "/features/notifications"
}
}
/**
* Route validation and utilities
*/
object RouteUtils {
/**
* Check if a route requires authentication
*/
fun requiresAuth(route: String): Boolean {
return when {
route.startsWith("/auth/") && route != Routes.Auth.LOGIN -> false
route == Routes.HOME -> false
route == Routes.LOGIN -> false
else -> true
}
}
/**
* Check if a route is for admin only
*/
fun requiresAdmin(route: String): Boolean {
return route.startsWith("/admin/")
}
/**
* Get the default route for authenticated users
*/
fun getDefaultAuthenticatedRoute(): String = Routes.DASHBOARD
/**
* Get the default route for unauthenticated users
*/
fun getDefaultUnauthenticatedRoute(): String = Routes.LOGIN
/**
* Validate route format
*/
fun isValidRoute(route: String): Boolean {
return route.startsWith("/") && route.isNotBlank()
}
/**
* Parse route parameters (simple implementation)
*/
fun parseRouteParams(route: String): Map<String, String> {
val params = mutableMapOf<String, String>()
// Simple query parameter parsing
if (route.contains("?")) {
val parts = route.split("?")
if (parts.size == 2) {
val queryParams = parts[1].split("&")
queryParams.forEach { param ->
val keyValue = param.split("=")
if (keyValue.size == 2) {
params[keyValue[0]] = keyValue[1]
}
}
}
}
return params
}
/**
* Get clean route without parameters
*/
fun getCleanRoute(route: String): String {
return if (route.contains("?")) {
route.split("?")[0]
} else {
route
}
}
}
@@ -1,75 +0,0 @@
package at.mocode.shared.navigation
import at.mocode.shared.presentation.state.NavigationState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
/**
* Interface für das Persistieren von Navigation State
*/
interface NavigationPersistence {
suspend fun saveNavigationState(state: NavigationState)
fun getNavigationState(): Flow<NavigationState?>
suspend fun clearNavigationState()
}
/**
* Default implementation ohne echte Persistierung (In-Memory)
* Platform-spezifische Implementierungen können echte Persistierung bereitstellen
*/
class DefaultNavigationPersistence : NavigationPersistence {
private var currentState: NavigationState? = null
override suspend fun saveNavigationState(state: NavigationState) {
currentState = state
}
override fun getNavigationState(): Flow<NavigationState?> {
return flowOf(currentState)
}
override suspend fun clearNavigationState() {
currentState = null
}
}
/**
* Navigation History Manager mit Persistierung
*/
class NavigationHistoryManager(
private val persistence: NavigationPersistence
) {
companion object {
private const val MAX_HISTORY_SIZE = 50
}
suspend fun saveRoute(route: String, history: List<String>) {
val state = NavigationState(
currentRoute = route,
history = history.takeLast(MAX_HISTORY_SIZE),
canGoBack = history.isNotEmpty()
)
persistence.saveNavigationState(state)
}
fun getPersistedState() = persistence.getNavigationState()
suspend fun clear() = persistence.clearNavigationState()
/**
* Optimiert die History für bessere Performance
*/
private fun optimizeHistory(history: List<String>): List<String> {
// Entfernt Duplikate in Folge und behält nur die letzten N Einträge
return history
.fold(emptyList<String>()) { acc, route ->
if (acc.lastOrNull() != route) acc + route else acc
}
.takeLast(MAX_HISTORY_SIZE)
}
suspend fun addToHistory(newRoute: String, currentHistory: List<String>) {
val optimizedHistory = optimizeHistory(currentHistory + newRoute)
saveRoute(newRoute, optimizedHistory.dropLast(1))
}
}
@@ -1,27 +0,0 @@
package at.mocode.shared.network
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
object HttpClientConfig {
fun createClient(
baseUrl: String = "http://localhost:8080"
): HttpClient = HttpClient {
// Content negotiation with JSON (based on PingApiClient pattern)
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
fun createClientWithBaseUrl(baseUrl: String): HttpClient {
return createClient(baseUrl)
}
}
@@ -1,172 +0,0 @@
package at.mocode.shared.network
import at.mocode.shared.domain.model.ApiError
import io.ktor.client.network.sockets.*
import io.ktor.client.plugins.*
import kotlinx.io.IOException
/**
* Custom exceptions for network operations
*/
sealed class NetworkException(
message: String,
cause: Throwable? = null,
val apiError: ApiError
) : Exception(message, cause) {
class ConnectionException(
message: String = "Connection failed",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "CONNECTION_ERROR",
message = message,
details = mapOf("type" to "network_connectivity")
)
)
class TimeoutException(
message: String = "Request timed out",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "TIMEOUT_ERROR",
message = message,
details = mapOf("type" to "request_timeout")
)
)
class ServerException(
statusCode: Int,
message: String = "Server error",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "SERVER_ERROR",
message = message,
details = mapOf(
"type" to "server_error",
"status_code" to statusCode.toString()
)
)
)
class ClientException(
statusCode: Int,
message: String = "Client error",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "CLIENT_ERROR",
message = message,
details = mapOf(
"type" to "client_error",
"status_code" to statusCode.toString()
)
)
)
class AuthenticationException(
message: String = "Authentication failed",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "AUTHENTICATION_ERROR",
message = message,
details = mapOf("type" to "authentication_failure")
)
)
class AuthorizationException(
message: String = "Authorization failed",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "AUTHORIZATION_ERROR",
message = message,
details = mapOf("type" to "authorization_failure")
)
)
class UnknownException(
message: String = "Unknown error occurred",
cause: Throwable? = null
) : NetworkException(
message = message,
cause = cause,
apiError = ApiError(
code = "UNKNOWN_ERROR",
message = message,
details = mapOf("type" to "unknown_error")
)
)
}
/**
* Extension function to convert various exceptions to NetworkException
*/
fun Throwable.toNetworkException(): NetworkException {
return when (this) {
is ConnectTimeoutException -> NetworkException.TimeoutException(
message = "Connection timeout: ${this.message}",
cause = this
)
is SocketTimeoutException -> NetworkException.TimeoutException(
message = "Socket timeout: ${this.message}",
cause = this
)
is ResponseException -> when (this.response.status.value) {
401 -> NetworkException.AuthenticationException(
message = "Authentication required",
cause = this
)
403 -> NetworkException.AuthorizationException(
message = "Access forbidden",
cause = this
)
in 400..499 -> NetworkException.ClientException(
statusCode = this.response.status.value,
message = "Client error: ${this.message}",
cause = this
)
in 500..599 -> NetworkException.ServerException(
statusCode = this.response.status.value,
message = "Server error: ${this.message}",
cause = this
)
else -> NetworkException.UnknownException(
message = "HTTP error: ${this.message}",
cause = this
)
}
is IOException -> NetworkException.ConnectionException(
message = "Network connection failed: ${this.message}",
cause = this
)
is NetworkException -> this
else -> NetworkException.UnknownException(
message = "Unexpected error: ${this.message}",
cause = this
)
}
}
@@ -1,220 +0,0 @@
package at.mocode.shared.network
import at.mocode.shared.domain.model.ApiError
import kotlinx.coroutines.delay
// Using platform-agnostic timestamp handling
/**
* Simple timestamp provider for multiplatform compatibility
*/
expect fun currentTimeMillis(): Long
/**
* Network utilities for handling retry logic and resilience
*/
object NetworkUtils {
/**
* Retry configuration for network operations
*/
data class RetryConfig(
val maxAttempts: Int = 3,
val initialDelayMs: Long = 1000L,
val maxDelayMs: Long = 10000L,
val backoffMultiplier: Double = 2.0,
val retryableExceptions: Set<String> = setOf(
"CONNECTION_ERROR",
"TIMEOUT_ERROR",
"SERVER_ERROR"
)
)
/**
* Execute operation with retry logic
*/
suspend fun <T> withRetry(
config: RetryConfig = RetryConfig(),
operation: suspend () -> RepositoryResult<T>
): RepositoryResult<T> {
var lastError: ApiError? = null
var currentDelay = config.initialDelayMs
repeat(config.maxAttempts) { attempt ->
try {
val result = operation()
// Return success immediately
if (result.isSuccess()) {
return result
}
// Check if the error is retryable
val error = result.getErrorOrNull()
if (error != null && shouldRetry(error, config)) {
lastError = error
// Don't delay on the last attempt
if (attempt < config.maxAttempts - 1) {
delay(currentDelay)
currentDelay = minOf(
(currentDelay * config.backoffMultiplier).toLong(),
config.maxDelayMs
)
}
} else {
// Non-retryable error, return immediately
return result
}
} catch (e: Exception) {
val networkException = e.toNetworkException()
lastError = networkException.apiError
if (shouldRetry(networkException.apiError, config)) {
if (attempt < config.maxAttempts - 1) {
delay(currentDelay)
currentDelay = minOf(
(currentDelay * config.backoffMultiplier).toLong(),
config.maxDelayMs
)
}
} else {
return RepositoryResult.Error(networkException.apiError)
}
}
}
// All attempts exhausted, return last error
return RepositoryResult.Error(
lastError ?: ApiError(
code = "MAX_RETRIES_EXCEEDED",
message = "Maximum retry attempts exceeded"
)
)
}
/**
* Check if an error should trigger a retry
*/
private fun shouldRetry(error: ApiError, config: RetryConfig): Boolean {
return config.retryableExceptions.contains(error.code)
}
/**
* Network connectivity checker (simplified for shared module)
*/
object ConnectivityChecker {
private var isOnline: Boolean = true
private var lastCheckMillis: Long = 0L
fun setOnlineStatus(online: Boolean) {
isOnline = online
lastCheckMillis = currentTimeMillis()
}
fun isOnline(): Boolean = isOnline
fun getLastCheckMillis(): Long = lastCheckMillis
/**
* Simple connectivity test by attempting a lightweight operation
*/
suspend fun checkConnectivity(testOperation: suspend () -> Boolean): Boolean {
return try {
val result = testOperation()
setOnlineStatus(result)
result
} catch (_: Exception) {
setOnlineStatus(false)
false
}
}
}
/**
* Circuit breaker pattern for network operations
*/
class CircuitBreaker(
private val failureThreshold: Int = 5,
private val recoveryTimeoutMs: Long = 60000L,
private val successThreshold: Int = 3
) {
private enum class State { CLOSED, OPEN, HALF_OPEN }
private var state = State.CLOSED
private var failureCount = 0
private var successCount = 0
private var lastFailureTime = 0L
suspend fun <T> execute(operation: suspend () -> RepositoryResult<T>): RepositoryResult<T> {
when (state) {
State.OPEN -> {
if (currentTimeMillis() - lastFailureTime >= recoveryTimeoutMs) {
state = State.HALF_OPEN
successCount = 0
} else {
return RepositoryResult.Error(
ApiError(
code = "CIRCUIT_BREAKER_OPEN",
message = "Circuit breaker is open, requests blocked"
)
)
}
}
State.HALF_OPEN -> {
// Allow limited requests to test recovery
}
State.CLOSED -> {
// Normal operation
}
}
return try {
val result = operation()
if (result.isSuccess()) {
onSuccess()
} else {
onFailure()
}
result
} catch (e: Exception) {
onFailure()
val networkException = e.toNetworkException()
RepositoryResult.Error(networkException.apiError)
}
}
private fun onSuccess() {
failureCount = 0
when (state) {
State.HALF_OPEN -> {
successCount++
if (successCount >= successThreshold) {
state = State.CLOSED
}
}
else -> {
state = State.CLOSED
}
}
}
private fun onFailure() {
failureCount++
lastFailureTime = currentTimeMillis()
if (failureCount >= failureThreshold) {
state = State.OPEN
}
}
fun getState(): String = state.name
fun getFailureCount(): Int = failureCount
}
}
@@ -1,18 +0,0 @@
package at.mocode.shared.network
import at.mocode.shared.domain.model.ApiError
/**
* Einheitlicher Ergebnis-Typ für Repository-/Netzwerkoperationen.
*/
sealed class RepositoryResult<out T> {
data class Success<T>(val value: T) : RepositoryResult<T>()
data class Error(val apiError: ApiError) : RepositoryResult<Nothing>()
}
fun <T> RepositoryResult<T>.isSuccess(): Boolean = this is RepositoryResult.Success
fun <T> RepositoryResult<T>.getErrorOrNull(): ApiError? = when (this) {
is RepositoryResult.Success -> null
is RepositoryResult.Error -> this.apiError
}
@@ -1,37 +0,0 @@
package at.mocode.shared.presentation.actions
import at.mocode.shared.presentation.state.Notification
import at.mocode.frontend.core.domain.models.User
import at.mocode.frontend.core.domain.models.AuthToken
sealed class AppAction {
// Auth Actions
sealed class Auth : AppAction() {
data class LoginStart(val username: String, val password: String) : Auth()
data class LoginSuccess(val user: User, val token: AuthToken) : Auth()
data class LoginFailure(val error: String) : Auth()
object Logout : Auth()
data class RefreshToken(val newToken: AuthToken) : Auth()
}
// Navigation Actions
sealed class Navigation : AppAction() {
data class NavigateTo(val route: String) : Navigation()
object NavigateBack : Navigation()
data class UpdateHistory(val route: String) : Navigation()
}
// UI Actions
sealed class UI : AppAction() {
object ToggleDarkMode : UI()
data class SetLoading(val isLoading: Boolean) : UI()
data class ShowNotification(val notification: Notification) : UI()
data class DismissNotification(val id: String) : UI()
}
// Network Actions
sealed class Network : AppAction() {
data class SetOnlineStatus(val isOnline: Boolean) : Network()
data class UpdateLastSync(val timestamp: String) : Network()
}
}
@@ -1,55 +0,0 @@
package at.mocode.shared.presentation.state
import kotlinx.serialization.Serializable
import at.mocode.frontend.core.domain.models.User
import at.mocode.frontend.core.domain.models.AuthToken
@Serializable
data class AppState(
val auth: AuthState = AuthState(),
val navigation: NavigationState = NavigationState(),
val ui: UiState = UiState(),
val network: NetworkState = NetworkState()
)
@Serializable
data class AuthState(
val isAuthenticated: Boolean = false,
val user: User? = null,
val token: AuthToken? = null,
val isLoading: Boolean = false,
val error: String? = null
)
@Serializable
data class NavigationState(
val currentRoute: String = "/",
val history: List<String> = emptyList(),
val canGoBack: Boolean = false
)
@Serializable
data class UiState(
val isDarkMode: Boolean = false,
val isLoading: Boolean = false,
val notifications: List<Notification> = emptyList()
)
@Serializable
data class NetworkState(
val isOnline: Boolean = true,
val lastSync: String? = null
)
@Serializable
data class Notification(
val id: String,
val title: String,
val message: String,
val type: NotificationType = NotificationType.INFO,
val timestamp: String
)
enum class NotificationType {
INFO, SUCCESS, WARNING, ERROR
}
@@ -1,156 +0,0 @@
package at.mocode.shared.presentation.store
import at.mocode.shared.presentation.state.AppState
import at.mocode.shared.presentation.actions.AppAction
import at.mocode.shared.presentation.state.AuthState
import at.mocode.shared.presentation.state.NavigationState
import at.mocode.shared.presentation.state.NetworkState
import at.mocode.shared.presentation.state.UiState
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class AppStore(
private val dispatcher: CoroutineDispatcher = Dispatchers.Main
) {
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
private val _state = MutableStateFlow(AppState())
val state: StateFlow<AppState> = _state.asStateFlow()
fun dispatch(action: AppAction) {
scope.launch {
val currentState = _state.value
val newState = reduce(currentState, action)
_state.value = newState
// Handle side effects
handleSideEffect(action, newState)
}
}
private fun reduce(currentState: AppState, action: AppAction): AppState {
return when (action) {
is AppAction.Auth -> currentState.copy(
auth = reduceAuth(currentState.auth, action)
)
is AppAction.Navigation -> currentState.copy(
navigation = reduceNavigation(currentState.navigation, action)
)
is AppAction.UI -> currentState.copy(
ui = reduceUI(currentState.ui, action)
)
is AppAction.Network -> currentState.copy(
network = reduceNetwork(currentState.network, action)
)
}
}
private fun reduceAuth(currentAuth: AuthState, action: AppAction.Auth): AuthState {
return when (action) {
is AppAction.Auth.LoginStart -> currentAuth.copy(
isLoading = true,
error = null
)
is AppAction.Auth.LoginSuccess -> currentAuth.copy(
isAuthenticated = true,
user = action.user,
token = action.token,
isLoading = false,
error = null
)
is AppAction.Auth.LoginFailure -> currentAuth.copy(
isAuthenticated = false,
user = null,
token = null,
isLoading = false,
error = action.error
)
is AppAction.Auth.Logout -> AuthState()
is AppAction.Auth.RefreshToken -> currentAuth.copy(
token = action.newToken
)
}
}
private fun reduceNavigation(currentNav: NavigationState, action: AppAction.Navigation): NavigationState {
return when (action) {
is AppAction.Navigation.NavigateTo -> currentNav.copy(
currentRoute = action.route,
history = currentNav.history + currentNav.currentRoute,
canGoBack = true
)
is AppAction.Navigation.NavigateBack -> {
val newHistory = currentNav.history.dropLast(1)
currentNav.copy(
currentRoute = newHistory.lastOrNull() ?: "/",
history = newHistory,
canGoBack = newHistory.isNotEmpty()
)
}
is AppAction.Navigation.UpdateHistory -> currentNav.copy(
currentRoute = action.route
)
}
}
private fun reduceUI(currentUI: UiState, action: AppAction.UI): UiState {
return when (action) {
is AppAction.UI.ToggleDarkMode -> currentUI.copy(
isDarkMode = !currentUI.isDarkMode
)
is AppAction.UI.SetLoading -> currentUI.copy(
isLoading = action.isLoading
)
is AppAction.UI.ShowNotification -> currentUI.copy(
notifications = currentUI.notifications + action.notification
)
is AppAction.UI.DismissNotification -> currentUI.copy(
notifications = currentUI.notifications.filter { it.id != action.id }
)
}
}
private fun reduceNetwork(currentNetwork: NetworkState, action: AppAction.Network): NetworkState {
return when (action) {
is AppAction.Network.SetOnlineStatus -> currentNetwork.copy(
isOnline = action.isOnline
)
is AppAction.Network.UpdateLastSync -> currentNetwork.copy(
lastSync = action.timestamp
)
}
}
private suspend fun handleSideEffect(action: AppAction, newState: AppState) {
when (action) {
is AppAction.Auth.LoginSuccess -> {
// Auto-save token to local storage
// TODO: Implement storage
}
is AppAction.Auth.Logout -> {
// Clear local storage
// TODO: Implement storage cleanup
}
else -> { /* No side effects */
}
}
}
fun cleanup() {
scope.cancel()
}
}
@@ -1,3 +0,0 @@
package at.mocode.shared.network
actual fun currentTimeMillis(): Long = System.currentTimeMillis()
@@ -1,5 +0,0 @@
package at.mocode.shared.network
import kotlin.js.Date
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
@@ -1,3 +0,0 @@
package at.mocode.shared.network
actual fun currentTimeMillis(): Long = System.currentTimeMillis()
@@ -11,9 +11,8 @@ import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator
import org.springframework.security.oauth2.jwt.*
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter
import org.springframework.security.web.server.SecurityWebFilterChain
@@ -38,7 +37,6 @@ class SecurityConfig(
.authorizeExchange { exchanges ->
exchanges
.pathMatchers(*securityProperties.publicPaths.toTypedArray()).permitAll()
.pathMatchers("/api/ping/**").permitAll() // TEMPORAER fuer Debugging
.pathMatchers("/api/v1/import/zns", "/api/v1/import/zns/**").permitAll() // TEMPORAER fuer Debugging
.anyExchange().authenticated()
}
@@ -67,16 +65,28 @@ class SecurityConfig(
if (delegate == null) {
if (jwkSetUri.isBlank()) {
logger.error("JWK Set URI is missing all authenticated requests will be rejected.")
return Mono.error(org.springframework.security.oauth2.jwt.BadJwtException("Identity Provider not configured"))
return Mono.error(BadJwtException("Identity Provider not configured"))
}
try {
logger.info("Attempting to initialize JWT Decoder with URI: {}", jwkSetUri)
delegate = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
logger.info("JWT Decoder successfully initialized.")
// Wir deaktivieren die Issuer-Validierung, da Keycloak intern "keycloak:8080"
// und extern "localhost:8180" verwendet, was zu Mismatches führt.
val nimbusDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
nimbusDecoder.setJwtValidator(JwtValidators.createDefault()) // Standard-Validierung (ohne Issuer-Zwang falls nicht explizit konfiguriert)
// Da createDefault() den Issuer-Check einbaut, wenn spring.security.oauth2.resourceserver.jwt.issuer-uri gesetzt ist,
// nutzen wir einen Custom Validator der den Issuer ignoriert oder flexibel ist.
val withAudience = DelegatingOAuth2TokenValidator<Jwt>(
JwtTimestampValidator(),
// Hier koennte man weitere Validatoren hinzufuegen, aber wir lassen den Issuer weg
)
nimbusDecoder.setJwtValidator(withAudience)
delegate = nimbusDecoder
logger.info("JWT Decoder successfully initialized (Issuer check disabled for environment flexibility).")
} catch (e: Exception) {
logger.warn("Could not initialize JWT Decoder: {}", e.message)
// Throw BadJwtException so Spring Security returns 401, not 500 or passthrough
return Mono.error(org.springframework.security.oauth2.jwt.BadJwtException("Identity Provider unavailable: ${e.message}"))
return Mono.error(BadJwtException("Identity Provider unavailable: ${e.message}"))
}
}
}
@@ -107,7 +117,7 @@ class SecurityConfig(
val configuration = CorsConfiguration().apply {
allowedOriginPatterns = securityProperties.cors.allowedOriginPatterns.toList()
allowedMethods = securityProperties.cors.allowedMethods.toList()
allowedHeaders = securityProperties.cors.allowedHeaders.toList()
allowedHeaders = listOf("*") // Alles erlauben fuer Postman/Frontend
exposedHeaders = securityProperties.cors.exposedHeaders.toList()
allowCredentials = securityProperties.cors.allowCredentials
maxAge = securityProperties.cors.maxAge.seconds
@@ -44,6 +44,27 @@ spring:
issuer-uri: ${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI:http://localhost:8180/realms/meldestelle}
jwk-set-uri: ${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI:http://localhost:8180/realms/meldestelle/protocol/openid-connect/certs}
gateway:
security:
cors:
allowed-origin-patterns:
- "http://localhost:*"
- "https://*.meldestelle.at"
- "https://*.mo-code.at"
- "https://*.postman.co"
- "postman://*"
allowed-methods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
- "OPTIONS"
- "PATCH"
allowed-headers:
- "*"
allow-credentials: true
max-age: 3600s
management:
endpoints:
web:
@@ -8,7 +8,7 @@
"variable": [
{
"key": "baseUrl",
"value": "http://localhost:8080",
"value": "http://localhost:8081",
"type": "string"
},
{
@@ -221,6 +221,100 @@
}
]
},
{
"name": "Connectivity Context (Ping Service)",
"item": [
{
"name": "Simple Ping",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/ping/simple",
"host": ["{{baseUrl}}"],
"path": ["api", "ping", "simple"]
}
},
"response": []
},
{
"name": "Health Check",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/ping/health",
"host": ["{{baseUrl}}"],
"path": ["api", "ping", "health"]
}
},
"response": []
},
{
"name": "Public Info",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/ping/public",
"host": ["{{baseUrl}}"],
"path": ["api", "ping", "public"]
}
},
"response": []
},
{
"name": "Enhanced Ping (Resilience)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/ping/enhanced",
"host": ["{{baseUrl}}"],
"path": ["api", "ping", "enhanced"]
}
},
"response": []
},
{
"name": "Sync Delta Diagnostic",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/ping/sync?lastSyncTimestamp=0",
"host": ["{{baseUrl}}"],
"path": ["api", "ping", "sync"],
"query": [
{
"key": "lastSyncTimestamp",
"value": "0"
}
]
}
},
"response": []
},
{
"name": "Secure Ping (Login Required)",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer {{authToken}}"
}
],
"url": {
"raw": "{{baseUrl}}/api/ping/secure",
"host": ["{{baseUrl}}"],
"path": ["api", "ping", "secure"]
}
},
"response": []
}
]
},
{
"name": "Master Data Context",
"item": [
@@ -6,9 +6,17 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.oauth2.jwt.JwtTimestampValidator
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
@Configuration
@EnableWebSecurity
@@ -19,19 +27,18 @@ class GlobalSecurityConfig {
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() } // CSRF nicht nötig für Stateless REST APIs
// WICHTIG: CORS explizit deaktivieren!
// Das API-Gateway kümmert sich um CORS. Die Microservices dürfen KEINE
// Access-Control-Allow-Origin Header setzen, sonst haben wir doppelte Header beim Client.
.cors { it.disable() }
// WICHTIG: CORS wieder aktivieren für Plan-B (Direktzugriff ohne Gateway möglich)
.cors { it.configurationSource(corsConfigurationSource()) }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.addFilterBefore(DeviceSecurityFilter(), UsernamePasswordAuthenticationFilter::class.java)
.authorizeHttpRequests { auth ->
// Explizite Freigaben (Health, Info, Public Endpoints)
// Explizite Freigaben (Health, Information, Public-Endpoints)
auth.requestMatchers("/actuator/**").permitAll()
auth.requestMatchers("/api/v1/devices/register").permitAll() // Onboarding erlauben
auth.requestMatchers("/api/mail/nennung").permitAll() // Plan-B Nennungen erlauben
auth.requestMatchers("/api/mail/nennungen").authenticated() // Liste schützen
auth.requestMatchers("/ping/public").permitAll()
auth.requestMatchers("/ping/simple").permitAll()
auth.requestMatchers("/ping/enhanced").permitAll()
auth.requestMatchers("/ping/health").permitAll()
auth.requestMatchers("/error").permitAll()
@@ -41,16 +48,48 @@ class GlobalSecurityConfig {
.oauth2ResourceServer { oauth2 ->
oauth2.jwt { jwt ->
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
// Auch hier den Issuer-Check entspannen, da der Service intern validiert
jwt.decoder(jwtDecoder())
}
}
return http.build()
}
@Bean
fun jwtDecoder(): JwtDecoder {
// 1. Suche in System-Properties (Spring injects these)
// 2. Suche in Environment Variables
// 3. Fallback auf localhost (IDE-Start) oder keycloak (Docker-Start)
val jwkSetUri = System.getProperty("spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
?: System.getenv("SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI")
?: "http://localhost:8180/realms/meldestelle/protocol/openid-connect/certs"
val decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
val validator = DelegatingOAuth2TokenValidator<Jwt>(JwtTimestampValidator())
decoder.setJwtValidator(validator)
return decoder
}
@Bean
fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
val converter = JwtAuthenticationConverter()
converter.setJwtGrantedAuthoritiesConverter(KeycloakRoleConverter())
return converter
}
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("*")
configuration.allowedOriginPatterns = listOf("*")
configuration.allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")
configuration.allowedHeaders = listOf("*")
configuration.exposedHeaders = listOf("*")
configuration.maxAge = 3600L
configuration.allowCredentials = false
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
}
@@ -47,10 +47,10 @@ class ZnsImportService(
companion object {
private val CP850 = Charset.forName("Cp850")
private const val FILE_VEREIN = "VEREIN01.DAT"
private const val FILE_LIZENZ = "LIZENZ01.DAT"
private const val FILE_PFERDE = "PFERDE01.DAT"
private const val FILE_RICHT = "RICHT01.DAT"
private const val FILE_VEREIN = "VEREIN"
private const val FILE_LIZENZ = "LIZENZ"
private const val FILE_PFERDE = "PFERDE"
private const val FILE_RICHT = "RICHT"
}
/**
@@ -65,7 +65,16 @@ class ZnsImportService(
while (entry != null) {
val fileName = entry.name.uppercase().substringAfterLast("/")
if (fileName in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) {
// Toleranter Check: Erkennt VEREIN01.DAT, VEREIN.DAT, etc.
val targetKey = when {
fileName.startsWith(FILE_VEREIN) -> FILE_VEREIN
fileName.startsWith(FILE_LIZENZ) -> FILE_LIZENZ
fileName.startsWith(FILE_PFERDE) -> FILE_PFERDE
fileName.startsWith(FILE_RICHT) -> FILE_RICHT
else -> null
}
if (targetKey != null && fileName.endsWith(".DAT")) {
// Wir lesen den Stream direkt zeilenweise mit dem korrekten Encoding
val lines = mutableListOf<String>()
val reader = zip.bufferedReader(CP850)
@@ -78,8 +87,8 @@ class ZnsImportService(
}
line = reader.readLine()
}
println("[DEBUG_LOG] Datei $fileName extrahiert: ${lines.size} Zeilen")
dateien[fileName] = lines
println("[DEBUG_LOG] Datei $fileName extrahiert als $targetKey: ${lines.size} Zeilen")
dateien[targetKey] = lines
}
zip.closeEntry()
entry = zip.nextEntry
@@ -129,20 +138,20 @@ class ZnsImportService(
var richterImportiert = 0
var richterAktualisiert = 0
when (fileName) {
FILE_VEREIN -> {
when {
fileName.startsWith(FILE_VEREIN) -> {
val (n, u) = importiereVereine(lines, fehler)
vereineImportiert = n
vereineAktualisiert = u
}
FILE_LIZENZ -> {
fileName.startsWith(FILE_LIZENZ) -> {
val (n, u) = importiereReiter(lines, fehler, warnungen)
reiterImportiert = n
reiterAktualisiert = u
}
FILE_PFERDE -> {
fileName.startsWith(FILE_PFERDE) -> {
if (mode == ZnsImportMode.FULL) {
val (n, u) = importierePferde(lines, fehler)
pferdeImportiert = n
@@ -150,7 +159,7 @@ class ZnsImportService(
}
}
FILE_RICHT -> {
fileName.startsWith(FILE_RICHT) -> {
if (mode == ZnsImportMode.FULL) {
val (n, u) = importiereFunktionaere(lines, fehler, warnungen)
richterImportiert = n
@@ -211,6 +220,14 @@ class ZnsImportService(
richterUpd = rUpd
}
// Zusätzliche Warnung wenn Dateien fehlen
if (dateien[FILE_VEREIN] == null) warnungen.add("Vereinsdaten (VEREIN*.DAT) nicht gefunden.")
if (dateien[FILE_LIZENZ] == null) warnungen.add("Reiter/Lizenzdaten (LIZENZ*.DAT) nicht gefunden.")
if (mode == ZnsImportMode.FULL) {
if (dateien[FILE_PFERDE] == null) warnungen.add("Pferdedaten (PFERDE*.DAT) nicht gefunden.")
if (dateien[FILE_RICHT] == null) warnungen.add("Funktionärsdaten (RICHT*.DAT) nicht gefunden.")
}
return ZnsImportResult(
vereineImportiert = vereineNeu,
vereineAktualisiert = vereineUpd,
@@ -9,9 +9,6 @@ plugins {
kotlin {
jvm()
js(IR) {
browser()
}
wasmJs {
browser()
}
@@ -1,10 +1,13 @@
spring:
application:
name: billing-service
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/pg-meldestelle-db}
username: ${SPRING_DATASOURCE_USERNAME:pg-user}
password: ${SPRING_DATASOURCE_PASSWORD:pg-password}
driver-class-name: org.postgresql.Driver
cloud:
consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
@@ -15,13 +18,19 @@ spring:
prefer-ip-address: true
health-check-path: /actuator/health
health-check-interval: 10s
health-check-port: 8089
# health-check-port: 8089
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
service-name: ${spring.application.name}
port: ${billing.http.port:8089}
server:
port: 8089
billing:
http:
port: 8089 # Ktor API Port (Haupt-Einstiegspunkt für REST-Anfragen)
address: 0.0.0.0 # Öffentlich erreichbar innerhalb des Netzwerks / Containers
management:
endpoints:
web:
@@ -30,3 +39,12 @@ management:
endpoint:
health:
show-details: always
probes:
enabled: true
logging:
level:
root: INFO
# at.mocode.billing: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
@@ -13,10 +13,6 @@ version = "1.0.0"
kotlin {
jvm()
js(IR) {
browser()
}
wasmJs {
browser()
}
@@ -10,10 +10,6 @@ plugins {
kotlin {
jvm()
js(IR) {
browser()
}
wasmJs {
browser()
}
@@ -28,6 +28,7 @@ dependencies {
// Common service extras
implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.mail)
implementation(libs.spring.boot.starter.actuator)
// JSON + Web: ensure Spring Web (incl. HttpMessageConverters) is on the classpath
//implementation("org.springframework.boot:spring-boot-starter-web")
implementation(libs.spring.boot.starter.web)
@@ -10,10 +10,6 @@ plugins {
kotlin {
jvm()
js(IR) {
browser()
}
wasmJs {
browser()
}
@@ -18,6 +18,9 @@ dependencies {
// Spring Boot Starters
implementation(libs.spring.boot.starter.web)
implementation(libs.spring.boot.starter.security)
implementation(libs.spring.boot.starter.oauth2.resource.server)
implementation(projects.backend.infrastructure.security)
implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.actuator)
implementation(libs.spring.boot.starter.mail)
@@ -10,9 +10,9 @@ import jakarta.mail.Session
import jakarta.mail.internet.InternetAddress
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.springframework.transaction.annotation.Transactional
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import org.springframework.mail.SimpleMailMessage
@@ -20,6 +20,7 @@ import org.springframework.mail.javamail.JavaMailSender
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.*
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -27,6 +28,7 @@ import kotlin.uuid.Uuid
@OptIn(ExperimentalUuidApi::class)
@Service
@EnableScheduling
@ConditionalOnProperty(value = ["mail.polling.enabled"], havingValue = "true", matchIfMissing = false)
class MailPollingService(
private val mailSender: JavaMailSender,
private val nennungRepository: NennungRepository,
@@ -4,22 +4,49 @@ import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.event.EventListener
import org.springframework.core.env.Environment
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@SpringBootApplication
@SpringBootApplication(scanBasePackages = ["at.mocode.mail", "at.mocode.infrastructure.security"])
class MailServiceApplication(private val env: Environment) {
private val log = LoggerFactory.getLogger(MailServiceApplication::class.java)
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(false)
}
}
}
@EventListener(ApplicationReadyEvent::class)
fun onApplicationReady() {
val springPort = env.getProperty("server.port", "8083")
val appName = env.getProperty("spring.application.name", "mail-service")
val mailHost = env.getProperty("spring.mail.host")
val mailPort = env.getProperty("spring.mail.port")
val mailUser = env.getProperty("spring.mail.username")
val mailPass = env.getProperty("spring.mail.password")?.take(3) + "***"
val connTimeout = env.getProperty("spring.mail.properties.mail.smtp.connectiontimeout")
val envHost = System.getenv("SPRING_MAIL_HOST")
val envPort = System.getenv("SPRING_MAIL_PORT")
log.info("----------------------------------------------------------")
log.info("Application '{}' is running!", appName)
log.info("Spring Management Port: {}", springPort)
log.info("SMTP Config (Resolved): host={}, port={}, user={}, pass={}, timeout={}", mailHost, mailPort, mailUser, mailPass, connTimeout)
log.info("SMTP Config (Raw Env): host={}, port={}, pass={}", envHost, envPort, System.getenv("SPRING_MAIL_PASSWORD")?.take(3) + "***")
log.info("Profiles: {}", env.activeProfiles.joinToString(", "))
log.info("----------------------------------------------------------")
}
@@ -1,34 +0,0 @@
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.mail.service
import at.mocode.mail.service.persistence.NennungEntity
import at.mocode.mail.service.persistence.NennungRepository
import org.springframework.web.bind.annotation.*
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@RestController
@RequestMapping("/api/mail/nennungen")
class NennungController(
private val nennungRepository: NennungRepository
) {
@GetMapping
fun getAllNennungen(): List<NennungEntity> {
return nennungRepository.findAll()
}
@PutMapping("/{id}/status")
fun updateStatus(
@PathVariable id: String,
@RequestBody newStatus: String
) {
nennungRepository.updateStatus(Uuid.parse(id), newStatus)
}
@PostMapping
fun createNennung(@RequestBody nennung: NennungEntity) {
nennungRepository.save(nennung)
}
}
@@ -39,7 +39,6 @@ data class NennungRequest(
@OptIn(ExperimentalUuidApi::class)
@RestController
@RequestMapping("/api/mail")
@CrossOrigin(origins = ["http://localhost:8080", "https://nennung.mo-code.at"]) // Für Wasm-Web-App (Compose HTML/Wasm)
class MailController(
private val nennungRepository: NennungRepository,
private val mailSender: JavaMailSender
@@ -50,7 +49,7 @@ class MailController(
private lateinit var baseMailAddress: String
@PostMapping("/nennung")
fun receiveNennung(@Valid @RequestBody request: NennungRequest) {
fun receiveNennung(@Valid @RequestBody request: NennungRequest): Map<String, Any> {
logger.info("Nennung via API erhalten: ${request.vorname} ${request.nachname} für Turnier ${request.turnierNr}")
val entity = NennungEntity(
@@ -71,19 +70,45 @@ class MailController(
nennungRepository.save(entity)
logger.info("Nennung ${entity.id} in Datenbank persistiert.")
// Bestätigung an Reiter senden
// --- PLAN B: Benachrichtigung an die Meldestelle (online-nennen@mo-code.at) senden ---
logger.info("Versuche Benachrichtigungs-Mail an $baseMailAddress zu senden...")
try {
val notification = SimpleMailMessage()
notification.from = baseMailAddress // Mailserver erfordert oft, dass From == Username ist
notification.setTo(baseMailAddress) // Wir senden es an uns selbst
// WICHTIG: Die Turniernummer im Betreff für das einfache Mail-Filtering!
notification.subject = "[NENNUNG] Turnier ${request.turnierNr} - ${request.vorname} ${request.nachname}"
val textBody = buildString {
appendLine("Neue Online-Nennung eingegangen!")
appendLine("----------------------------------")
appendLine("Turnier: ${request.turnierNr}")
appendLine("Reiter: ${request.vorname} ${request.nachname}")
appendLine("Lizenz: ${request.lizenz}")
appendLine("Pferd: ${request.pferdName} (Alter: ${request.pferdAlter})")
appendLine("E-Mail: ${request.email}")
appendLine("Telefon: ${request.telefon ?: "-"}")
appendLine("Bewerbe: ${request.bewerbe}")
appendLine("Bemerkungen: ${request.bemerkungen ?: "-"}")
appendLine("----------------------------------")
appendLine("System-ID: ${entity.id}")
}
notification.text = textBody
mailSender.send(notification)
logger.info("Plan-B Nennungs-Mail an die Meldestelle gesendet. Betreff: ${notification.subject}")
} catch (e: Exception) {
logger.error("KRITISCH: Fehler beim Senden der Plan-B Nennungs-Mail an die Meldestelle: ${e.message}", e)
}
// --- Ursprüngliche Bestätigung an den Reiter (optional, bleibt vorerst erhalten) ---
logger.info("Versuche Bestätigungs-Mail an ${request.email} zu senden...")
try {
val message = SimpleMailMessage()
// Dynamische Absenderadresse mit Plus-Addressing (z.B. online-nennen+26128@mo-code.at)
val dynamicFrom = try {
val (user, domain) = baseMailAddress.split("@")
"$user+${request.turnierNr}@$domain"
} catch (_: Exception) {
baseMailAddress
}
message.from = dynamicFrom
// PLAN B Fallback: Kein Plus-Addressing, da World4You es nicht unterstützt
// Wir verwenden als Absender einfach die Basis-Adresse
message.from = baseMailAddress
message.setTo(request.email)
message.subject = "Bestätigung: Ihre Online-Nennung für Turnier ${request.turnierNr}"
message.text = """
@@ -103,12 +128,57 @@ class MailController(
mailSender.send(message)
logger.info("Bestätigungs-Mail an ${request.email} gesendet.")
} catch (e: Exception) {
logger.error("Fehler beim Senden der Bestätigungs-Mail: ${e.message}")
logger.error("KRITISCH: Fehler beim Senden der Bestätigungs-Mail an ${request.email}: ${e.message}", e)
}
return mapOf(
"success" to true,
"message" to "Nennung erhalten und verarbeitet",
"id" to entity.id.toString()
)
}
@GetMapping("/nennungen")
fun getAllNennungen(): List<NennungEntity> {
return nennungRepository.findAll()
}
@PutMapping("/nennungen/{id}/status")
fun updateStatus(
@PathVariable id: String,
@RequestBody newStatus: String
) {
nennungRepository.updateStatus(Uuid.parse(id), newStatus)
}
@PostMapping("/nennungen")
fun createNennung(@RequestBody nennung: NennungEntity) {
nennungRepository.save(nennung)
}
@PostMapping("/send-reply")
fun sendReply(
@RequestParam email: String,
@RequestParam turnierNr: String,
@RequestParam vorname: String,
@RequestParam nachname: String
) {
val message = SimpleMailMessage()
// PLAN B Fallback: Kein Plus-Addressing
message.from = baseMailAddress
message.setTo(email)
message.subject = "Bestätigung: Nennung für Turnier $turnierNr manuell übernommen"
message.text = """
Sehr geehrte(r) $vorname $nachname,
Ihre Online-Nennung für das Turnier $turnierNr wurde von uns manuell in das Turniersystem übernommen.
Viel Erfolg beim Turnier!
Mit freundlichen Grüßen,
Ihre Meldestelle
""".trimIndent()
mailSender.send(message)
logger.info("Antwort-Mail an $email gesendet.")
}
}
@@ -0,0 +1,34 @@
package at.mocode.mail.service.config
import at.mocode.mail.service.persistence.NennungTable
import jakarta.annotation.PostConstruct
import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Configuration
import javax.sql.DataSource
/**
* Wires Spring's DataSource into Exposed and ensures the schema exists.
* This replaces the implicit init that previously happened in the polling service.
*/
@Configuration
class ExposedConfiguration(
private val dataSource: DataSource,
) {
private val log = LoggerFactory.getLogger(ExposedConfiguration::class.java)
@PostConstruct
fun connectAndInitSchema() {
// Bind Exposed to Spring's DataSource
Database.connect(dataSource)
// Create required tables if missing (idempotent for H2 and typical RDBMS)
transaction {
SchemaUtils.create(NennungTable)
}
log.info("Exposed connected to DataSource and schema initialized (NennungTable).")
}
}
@@ -5,9 +5,9 @@ package at.mocode.mail.service.persistence
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.insert
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.jetbrains.exposed.v1.jdbc.update
import org.springframework.stereotype.Repository
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.springframework.transaction.annotation.Transactional
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -12,23 +12,28 @@ spring:
show-sql: true
mail:
host: ${SPRING_MAIL_HOST:smtp.world4you.com}
port: ${SPRING_MAIL_PORT:587}
port: 587
username: ${SPRING_MAIL_USERNAME:online-nennen@mo-code.at}
password: ${SPRING_MAIL_PASSWORD:}
password: ${SPRING_MAIL_PASSWORD:Mogi#2reiten}
properties:
mail:
smtp:
auth: ${SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH:true}
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000
starttls:
enable: ${SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE:true}
required: ${SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_REQUIRED:true}
cloud:
consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
port: ${SPRING_CLOUD_CONSUL_PORT:8500}
enabled: ${SPRING_CLOUD_CONSUL_ENABLED:false}
discovery:
enabled: true
register: true
enabled: ${SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED:false}
register: ${SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER:false}
prefer-ip-address: true
health-check-path: /actuator/health
health-check-interval: 10s
@@ -43,4 +48,14 @@ management:
endpoints:
web:
exposure:
include: "health,info,prometheus"
include: health,info,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true
# Feature-Flags
mail:
polling:
enabled: ${MAIL_POLLING_ENABLED:false}
+2 -2
View File
@@ -92,8 +92,8 @@ USER ${APP_USER}
EXPOSE 8086 5005
HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \
CMD curl -fsS --max-time 2 http://localhost:8086/actuator/health/readiness || exit 1
HEALTHCHECK --interval=15s --timeout=5s --start-period=60s --retries=5 \
CMD curl -fsS --max-time 5 http://localhost:${SERVER_PORT:-8086}/actuator/health/readiness || exit 1
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \
@@ -62,7 +62,7 @@ class FunktionaerController(private val funktionaerRepository: FunktionaerReposi
* GET /funktionaer Alle Funktionäre (paginiert).
*/
get {
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val limit = (call.request.queryParameters["limit"]?.toIntOrNull() ?: 100).coerceAtMost(5000)
val offset = call.request.queryParameters["offset"]?.toIntOrNull() ?: 0
val results = funktionaerRepository.findAll(limit, offset)
@@ -62,11 +62,11 @@ class HorseController(private val horseRepository: HorseRepository) {
route("/horse") {
/**
* GET /horse Alle Pferde (paginiert), optional gefiltert nach jahrgang.
* GET /horse alle Pferde (paginiert), optional gefiltert nach Jahrgang.
*/
get {
val jahrgang = call.request.queryParameters["jahrgang"]?.toIntOrNull()
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val limit = (call.request.queryParameters["limit"]?.toIntOrNull() ?: 100).coerceAtMost(50000)
val offset = call.request.queryParameters["offset"]?.toIntOrNull() ?: 0
val results = when {
@@ -77,7 +77,7 @@ class HorseController(private val horseRepository: HorseRepository) {
}
/**
* GET /horse/search?q=... Sucht Pferde nach Lebensnummer.
* GET /horse/search?q=  Sucht Pferde nach Lebensnummer.
*/
get("/search") {
val query = call.request.queryParameters["q"] ?: ""
@@ -86,7 +86,7 @@ class HorseController(private val horseRepository: HorseRepository) {
}
/**
* GET /horse/{id} Ruft ein spezifisches Pferd ab.
* GET /horse/{id} ruft ein spezifisches Pferd ab.
*/
get("/{id}") {
val id = parseUuid(call.parameters["id"]) ?: return@get call.respond(HttpStatusCode.BadRequest)
@@ -104,7 +104,7 @@ class HorseController(private val horseRepository: HorseRepository) {
}
/**
* POST /horse Erstellt ein neues Pferd.
* POST /horse erstellt ein neues Pferd.
*/
post {
val req = call.receive<HorseCreateRequest>()
@@ -93,7 +93,7 @@ class ReiterController(private val reiterRepository: ReiterRepository) {
* GET /reiter Alle Reiter (paginiert).
*/
get {
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val limit = (call.request.queryParameters["limit"]?.toIntOrNull() ?: 100).coerceAtMost(50000)
val offset = call.request.queryParameters["offset"]?.toIntOrNull() ?: 0
val results = reiterRepository.findAll(limit, offset)
@@ -76,11 +76,11 @@ class VereinController(private val vereinRepository: VereinRepository) {
route("/verein") {
/**
* GET /verein Alle Vereine (paginiert), optional gefiltert nach verband/bundesland.
* GET /verein alle Vereine (paginiert), optional gefiltert nach Verband/Bundesland.
*/
get {
val verband = call.request.queryParameters["verband"]
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
val limit = (call.request.queryParameters["limit"]?.toIntOrNull() ?: 100).coerceAtMost(5000)
val offset = call.request.queryParameters["offset"]?.toIntOrNull() ?: 0
val results = if (verband != null) {
@@ -92,7 +92,7 @@ class VereinController(private val vereinRepository: VereinRepository) {
}
/**
* GET /verein/search?q=... Sucht Vereine nach Name.
* GET /verein/search?q=  Sucht Vereine nach Namen.
*/
get("/search") {
val query = call.request.queryParameters["q"] ?: ""
@@ -101,7 +101,7 @@ class VereinController(private val vereinRepository: VereinRepository) {
}
/**
* GET /verein/{id} Ruft einen spezifischen Verein ab.
* GET /verein/{id} ruft einen spezifischen Verein ab.
*/
get("/{id}") {
val id = parseUuid(call.parameters["id"]) ?: return@get call.respond(HttpStatusCode.BadRequest)
@@ -119,7 +119,7 @@ class VereinController(private val vereinRepository: VereinRepository) {
}
/**
* POST /verein Erstellt einen neuen Verein.
* POST /verein erstellt einen neuen Verein.
*/
post {
val req = call.receive<VereinCreateRequest>()
@@ -10,10 +10,6 @@ plugins {
kotlin {
jvm()
js(IR) {
browser()
}
wasmJs {
browser()
}
@@ -1,49 +1,61 @@
server:
port: ${MASTERDATA_SERVER_PORT:8086}
ktor:
port: ${MASTERDATA_KTOR_PORT:8091}
spring:
application:
name: masterdata-service
main:
banner-mode: "off"
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/pg-meldestelle-db}
username: ${SPRING_DATASOURCE_USERNAME:pg-user}
password: ${SPRING_DATASOURCE_PASSWORD:pg-password}
driver-class-name: org.postgresql.Driver
flyway:
enabled: true
baseline-on-migrate: true
cloud:
consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
port: ${SPRING_CLOUD_CONSUL_PORT:8500}
enabled: ${CONSUL_ENABLED:true}
discovery:
enabled: ${CONSUL_ENABLED:true}
register: ${CONSUL_ENABLED:true}
enabled: true
register: true
prefer-ip-address: true
health-check-path: /actuator/health
health-check-path: /actuator/health/readiness
health-check-interval: 10s
health-check-port: 8086 # Spring Boot Management Port (Actuator)
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
health-check-timeout: 5s
health-check-port: 8086
health-check-critical-timeout: 2m
deregister-critical-service-after: 5m
instance-id: ${spring.application.name}:${random.uuid}
service-name: ${spring.application.name}
port: ${masterdata.http.port:8091} # Ktor API Port registrieren (Gateway Ziel)
port: 8091
server:
port: 8086 # Spring Boot Management Port (Actuator & Tomcat)
address: 0.0.0.0 # Erreichbar für Consul Health Checks
#server:
# port: 8086 # Spring Boot Management Port (Actuator & Tomcat)
# address: 0.0.0.0 # Erreichbar für Consul Health Checks
masterdata:
http:
port: 8091 # Ktor API Port (Haupt-Einstiegspunkt für REST-Anfragen)
address: 0.0.0.0 # Öffentlich erreichbar innerhalb des Netzwerks / Containers
#masterdata:
# http:
# port: 8091 # Ktor API Port (Haupt-Einstiegspunkt für REST-Anfragen)
# address: 0.0.0.0 # Öffentlich erreichbar innerhalb des Netzwerks / Containers
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
show-components: always
probes:
enabled: true
prometheus:
@@ -1,8 +1,9 @@
plugins {
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinSpring)
alias(libs.plugins.kotlinJpa)
alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependencyManagement)
}
kotlin {
@@ -36,8 +37,7 @@ dependencies {
implementation(libs.bundles.database.complete)
// === Resilience ===
implementation(libs.resilience4j.spring.boot3)
implementation(libs.resilience4j.reactor)
implementation(libs.bundles.resilience)
implementation(libs.spring.boot.starter.aop)
// === Testing ===
@@ -2,6 +2,7 @@ package at.mocode.ping.infrastructure.web
import at.mocode.ping.api.*
import at.mocode.ping.application.PingUseCase
import at.mocode.ping.domain.Ping
import at.mocode.ping.infrastructure.PingProperties
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import org.slf4j.LoggerFactory
@@ -20,7 +21,7 @@ import kotlin.uuid.ExperimentalUuidApi
*/
@RestController
@OptIn(ExperimentalUuidApi::class)
class PingController(
open class PingController(
private val pingUseCase: PingUseCase,
private val properties: PingProperties
) : PingApi {
@@ -43,10 +44,16 @@ class PingController(
override suspend fun enhancedPing(
@RequestParam(required = false, defaultValue = "false") simulate: Boolean
): EnhancedPingResponse {
logger.info("Enhanced ping requested, simulate: {}", simulate)
val start = System.nanoTime()
if (simulate && Random.nextDouble() < 0.6) {
throw RuntimeException("Simulated service failure")
if (simulate) {
if (Random.nextDouble() < 0.6) {
logger.info("Simulating service failure now...")
throw SimulatedException("Simulated service failure")
} else {
logger.info("Simulation mode ACTIVE, but this time lucky: Request passed!")
}
}
val domainPing = pingUseCase.executePing("Enhanced Ping")
@@ -61,6 +68,8 @@ class PingController(
)
}
class SimulatedException(message: String) : RuntimeException(message)
// Neue Endpunkte
@GetMapping("/ping/public")
@@ -70,7 +79,7 @@ class PingController(
}
@GetMapping("/ping/secure")
@PreAuthorize("hasRole('MELD_USER') or hasRole('MELD_ADMIN')") // Beispiel-Rollen
@PreAuthorize("hasRole('ROLE_MELD_USER') or hasRole('ROLE_MELD_ADMIN')") // Beispiel-Rollen
override suspend fun securePing(): PingResponse {
val domainPing = pingUseCase.executePing("Secure Ping")
return createResponse(domainPing, "secure-pong")
@@ -79,7 +88,7 @@ class PingController(
@GetMapping("/ping/sync")
override suspend fun syncPings(
// Changed the parameter name to 'since' to match SyncManager convention
@RequestParam(required = false, defaultValue = "0") since: Long
@RequestParam(name = "lastSyncTimestamp", required = false, defaultValue = "0") since: Long
): List<PingEvent> {
return pingUseCase.getPingsSince(since).map {
PingEvent(
@@ -91,7 +100,7 @@ class PingController(
}
// Helper
private fun createResponse(domainPing: at.mocode.ping.domain.Ping, status: String) = PingResponse(
private fun createResponse(domainPing: Ping, status: String) = PingResponse(
status = status,
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
service = properties.serviceName
@@ -99,8 +108,8 @@ class PingController(
// Fallback
@Suppress("unused", "UNUSED_PARAMETER")
fun fallbackPing(simulate: Boolean, ex: Exception): EnhancedPingResponse {
logger.warn("Circuit breaker fallback triggered: {}", ex.message)
open fun fallbackPing(simulate: Boolean, ex: Throwable): EnhancedPingResponse {
logger.error("CIRCUIT BREAKER FALLBACK TRIGGERED! Reason: {}", ex.message, ex)
return EnhancedPingResponse(
status = "fallback",
timestamp = java.time.OffsetDateTime.now().format(formatter),
@@ -0,0 +1,32 @@
package at.mocode.ping.infrastructure.web
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ProblemDetail
import org.springframework.security.access.AccessDeniedException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
@RestControllerAdvice
class PingExceptionHandler {
private val log = LoggerFactory.getLogger(PingExceptionHandler::class.java)
@ExceptionHandler(AccessDeniedException::class)
fun handleAccessDenied(ex: AccessDeniedException): ProblemDetail {
log.warn("Zugriff verweigert: ${ex.message}")
return ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN, "Nicht berechtigt: ${ex.message}")
}
@ExceptionHandler(Exception::class)
fun handleAll(ex: Exception): ProblemDetail {
log.error("Unerwarteter Fehler: ", ex)
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.message ?: "Ein interner Fehler ist aufgetreten")
}
@ExceptionHandler(RuntimeException::class)
fun handleRuntime(ex: RuntimeException): ProblemDetail {
log.error("Interner Fehler: ", ex)
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.message ?: "Unbekannter Fehler")
}
}
@@ -43,14 +43,14 @@ spring:
consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
port: ${SPRING_CLOUD_CONSUL_PORT:8500}
enabled: ${CONSUL_ENABLED:true}
enabled: ${SPRING_CLOUD_CONSUL_ENABLED:true}
discovery:
enabled: ${CONSUL_ENABLED:true}
register: ${CONSUL_ENABLED:true}
prefer-ip-address: true
health-check-path: /actuator/health
enabled: ${SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED:true}
register: ${SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER:true}
prefer-ip-address: ${SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS:true}
health-check-path: ${SPRING_CLOUD_CONSUL_DISCOVERY_HEALTH_CHECK_PATH:/actuator/health}
health-check-interval: 10s
health-check-port: 8082
health-check-port: ${SERVER_PORT:8082}
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
service-name: ${spring.application.name}
@@ -40,12 +40,17 @@ import kotlin.uuid.ExperimentalUuidApi
controllers = [PingController::class],
properties = ["spring.aop.proxy-target-class=true"]
)
@Import(
PingControllerTest.PingControllerTestConfig::class,
io.github.resilience4j.springboot3.circuitbreaker.autoconfigure.CircuitBreakerAutoConfiguration::class,
io.github.resilience4j.springboot3.circuitbreaker.autoconfigure.CircuitBreakerMetricsAutoConfiguration::class,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration::class
)
@ContextConfiguration(classes = [TestPingServiceApplication::class])
@ActiveProfiles("test")
@Import(PingControllerTest.PingControllerTestConfig::class)
@AutoConfigureMockMvc(addFilters = false)
@OptIn(ExperimentalUuidApi::class)
class PingControllerTest {
open class PingControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@@ -125,11 +130,24 @@ class PingControllerTest {
// Then
val json = objectMapper.readTree(result.response.contentAsString)
assertThat(json["status"].asText()).isEqualTo("pong")
assertThat(json["service"].asText()).isEqualTo(properties.serviceName)
verify { pingUseCase.executePing("Enhanced Ping") }
}
@Test
fun `should return fallback when simulation failure occurs`() {
// Given
val controller = PingController(pingUseCase, properties)
// When
val response = controller.fallbackPing(simulate = true, ex = PingController.SimulatedException("test"))
// Then
assertThat(response.status).isEqualTo("fallback")
assertThat(response.service).isEqualTo(properties.serviceNameFallback)
assertThat(response.circuitBreakerState).isEqualTo("OPEN")
}
@Test
fun `should return health check response with status up`() {
// When
@@ -159,7 +177,7 @@ class PingControllerTest {
)
// When
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString()))
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("lastSyncTimestamp", timestamp.toString()))
.andExpect(request().asyncStarted())
.andReturn()
@@ -183,7 +201,7 @@ class PingControllerTest {
every { pingUseCase.getPingsSince(timestamp) } returns emptyList()
// When
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("since", timestamp.toString()))
val mvcResult: MvcResult = mockMvc.perform(get("/ping/sync").param("lastSyncTimestamp", timestamp.toString()))
.andExpect(request().asyncStarted())
.andReturn()
+44 -2
View File
@@ -3,7 +3,7 @@
metrics
}
:4000 {
:80 {
root * /usr/share/caddy
log {
output stdout
@@ -17,14 +17,56 @@
encode gzip zstd
# Same-Origin Strategy: Alle /api/* Anfragen werden intern an den Mail-Service weitergeleitet
# Dadurch sieht der Browser nur noch app.mo-code.at und CORS wird hinfällig.
handle /api/* {
reverse_proxy api-gateway:8081
reverse_proxy mail-service:8085 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
header {
Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "*"
X-Caddy-Strategy "same-origin-v32"
}
}
handle /health {
respond "healthy" 200
}
# Korrekte MIME für .wasm sicherstellen (Caddy erkennt es i. d. R. automatisch; hier explizit)
@wasm {
path *.wasm
}
header @wasm Content-Type "application/wasm"
# Caching-Strategie: Immutable Assets (hash-Dateien)
# WICHTIG: .wasm und .js werden hier gecached. Falls die Dateinamen gleich bleiben,
# wird der Browser sie NICHT neu laden.
@immutable {
path *.png *.svg *.ico *.woff2 *.map
}
header @immutable Cache-Control "public, max-age=31536000, immutable"
# Wasm und JS Dateien: Kein Cache während der aktiven Entwicklungsphase (Plan-B)
# um "Alte Seite" Probleme zu vermeiden.
@wasm_js {
path *.wasm *.js
}
header @wasm_js Cache-Control "no-store, no-cache, must-revalidate"
# Keine Cache-Header für SPA-Einstieg und Laufzeitkonfig
@nocache {
path /index.html /config.json
}
header @nocache Cache-Control "no-store"
# Static file serving mit SPA-Fallback
handle {
try_files {path} /index.html
file_server
+7 -5
View File
@@ -31,9 +31,11 @@ COPY config/docker/caddy/web-app/entrypoint.sh /entrypoint.sh
COPY config/docker/caddy/web-app/config.json /usr/share/caddy/config.json.tmpl
RUN chmod +x /entrypoint.sh
# Copy Pre-built Static Assets from Host
# NOTE: You must run `./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution -Pproduction=true` locally first!
COPY frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/ /usr/share/caddy/
# Copy Pre-built Static Assets from Host (WasmJs)
# NOTE: BUILD_DATE wird hier genutzt, um den Layer-Cache zu invalidieren,
# falls sich der Code geändert hat, aber die Dateimetadaten im Runner-Cache gleich blieben.
ARG BUILD_DATE
COPY config/docker/caddy/web-app/_site/ /usr/share/caddy/
# index.html wird als Template abgelegt; der Entrypoint erzeugt daraus zur Laufzeit die finale index.html
RUN mv /usr/share/caddy/index.html /usr/share/caddy/index.html.tmpl
@@ -41,10 +43,10 @@ RUN mv /usr/share/caddy/index.html /usr/share/caddy/index.html.tmpl
# Using the shared asset from existing config structure
COPY config/docker/nginx/web-app/favicon.svg /usr/share/caddy/favicon.svg
EXPOSE 4000
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:4000/health || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1
ENTRYPOINT ["/entrypoint.sh"]
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
+1
View File
@@ -1,4 +1,5 @@
{
"apiBaseUrl": "${API_BASE_URL}",
"mailServiceUrl": "${MAIL_SERVICE_URL}",
"keycloakUrl": "${KEYCLOAK_URL}"
}
+7 -4
View File
@@ -1,13 +1,16 @@
#!/bin/sh
set -e
# Ersetze ${API_BASE_URL} und ${KEYCLOAK_URL} in index.html und config.json zur Container-Startzeit.
# Ersetze ${API_BASE_URL}, ${MAIL_SERVICE_URL} und ${KEYCLOAK_URL} in index.html und config.json zur Container-Startzeit.
# Caddy bekommt fertige, statische Dateien — kein Template-Parsing mehr nötig.
envsubst '${API_BASE_URL} ${KEYCLOAK_URL}' \
< /usr/share/caddy/index.html.tmpl \
# Wir fügen zusätzlich einen Cache-Buster (Zeitstempel) an den Script-Tag in der index.html an
CACHE_BUSTER=$(date +%s)
envsubst '${API_BASE_URL} ${MAIL_SERVICE_URL} ${KEYCLOAK_URL}' \
< /usr/share/caddy/index.html.tmpl | \
sed "s|meldestelle-web.js|meldestelle-web.js?v=${CACHE_BUSTER}|g" \
> /usr/share/caddy/index.html
envsubst '${API_BASE_URL} ${KEYCLOAK_URL}' \
envsubst '${API_BASE_URL} ${MAIL_SERVICE_URL} ${KEYCLOAK_URL}' \
< /usr/share/caddy/config.json.tmpl \
> /usr/share/caddy/config.json
-9
View File
@@ -13,15 +13,6 @@ version = "1.0.0"
kotlin {
jvm()
js(IR) {
binaries.library()
browser {
testTask {
enabled = false
}
}
}
wasmJs {
binaries.library()
browser {
+62
View File
@@ -0,0 +1,62 @@
# =============================================================================
# Conveyor Configuration for Meldestelle Desktop App
# =============================================================================
# Dieser Build-Weg ermöglicht das Cross-Packaging für Windows (MSI) auf Linux.
# Dokumentation: https://conveyor.hydraulic.dev/
# =============================================================================
include required("https://raw.githubusercontent.com/hydraulic-software/conveyor/master/configs/jvm/extract-native-libraries.conf")
# Basis-Import der Gradle-Konfiguration (sofern das Plugin genutzt wird,
# aber wir definieren es hier explizit für maximale Kontrolle im CI/CD).
app {
# Anzeige-Name und Vendor
display-name = "Meldestelle"
rdns-name = "at.mocode.meldestelle"
vendor = "mo-code.at"
contact-email = "support@mo-code.at"
# Version aus version.properties (Conveyor kann HOCON-Variablen nutzen)
# Für diesen Task hart codiert oder via CLI-Flag --variable übergeben.
version = "1.0.0"
# Beschreibung
description = "ÖTO-konforme Turnier-Meldestelle Desktop App"
# Ziel-Plattformen
# Wir konzentrieren uns auf Windows, können aber Linux/Mac später ergänzen.
site.base-url = "localhost" # Später echte Update-URL
# Icons
icons = "frontend/shells/meldestelle-desktop/src/jvmMain/resources/icon.png"
# Einbetten der JRE (Temurin 21 wie in CI genutzt)
jvm {
gui {
main-class = "at.mocode.frontend.shell.desktop.MainKt"
}
# JVM-Argumente (analog build.gradle.kts)
jvm-options = [
"-Xms128m",
"-Xmx512m",
"-Dfile.encoding=UTF-8"
]
}
# Input-Dateien: Hier ziehen wir die Uber-JAR oder die Gradle-Outputs.
# Da wir plattformunabhängig bleiben wollen, nutzen wir das Gradle-Output-Dir.
inputs += "frontend/shells/meldestelle-desktop/build/libs/meldestelle-desktop-jvm-*.jar"
# Windows-spezifische Einstellungen
windows {
# Icon als .ico
icons = "frontend/shells/meldestelle-desktop/src/jvmMain/resources/icon.ico"
# GUID für Upgrades (muss stabil bleiben)
upgrade-uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# Menü-Eintrag
menu-group = "Meldestelle"
# Verknüpfung
desktop-shortcut = true
}
}
+4 -19
View File
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
@@ -10,17 +12,7 @@ kotlin {
}
}
js(IR) {
binaries.library()
// Re-enabled browser environment after Root NodeJs fix
browser {
testTask {
enabled = false
}
}
}
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
binaries.library()
browser()
@@ -35,17 +27,10 @@ kotlin {
commonMain.dependencies {
api(libs.kotlinx.serialization.json)
api(libs.kotlinx.datetime)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
jsMain.dependencies {
api(libs.kotlinx.coroutines.core)
}
jsTest.dependencies {
commonTest.dependencies {
implementation(libs.kotlin.test)
}
+3 -9
View File
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
@@ -5,15 +7,7 @@ plugins {
kotlin {
jvm()
js {
browser {
testTask {
enabled = false
}
}
}
// Wasm support enabled?
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
-9
View File
@@ -10,15 +10,6 @@ plugins {
kotlin {
jvm()
js(IR) {
binaries.library()
browser {
testTask {
enabled = false
}
}
}
wasmJs {
binaries.library()
browser {
+5 -1
View File
@@ -59,7 +59,7 @@ services:
# --- SERVICE URLs ---
PING_SERVICE_URL: "http://ping-service:8082"
MASTERDATA_SERVICE_URL: "http://masterdata-service:8086"
MASTERDATA_SERVICE_URL: "http://masterdata-service:8091"
EVENTS_SERVICE_URL: "http://events-service:8085"
MAIL_SERVICE_URL: "http://mail-service:8083"
ZNS_IMPORT_SERVICE_URL: "http://zns-import-service:8095"
@@ -127,6 +127,8 @@ services:
SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}"
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${PING_SERVICE_NAME:-ping-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${PING_CONSUL_PREFER_IP:-true}"
SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: "${PING_SERVICE_HOSTNAME:-ping-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_HEALTH_CHECK_PATH: "/actuator/health"
# - DATENBANK VERBINDUNG -
SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}"
@@ -202,6 +204,8 @@ services:
SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}"
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${MASTERDATA_SERVICE_NAME:-masterdata-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${MASTERDATA_CONSUL_PREFER_IP:-true}"
SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: "${MASTERDATA_SERVICE_HOSTNAME:-masterdata-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_HEALTH_CHECK_PATH: "/actuator/health"
# - DATENBANK VERBINDUNG -
SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}"
+41 -41
View File
@@ -1,48 +1,48 @@
name: "${PROJECT_NAME:-meldestelle}"
services:
# ==========================================
# 3. FRONTEND (UI)
# ==========================================
# services:
# # ==========================================
# # 3. FRONTEND (UI)
# # ==========================================
# --- WEB-APP ---
web-app:
image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/web-app:${DOCKER_TAG:-latest}"
build:
context: . # Wichtig: Root Context für Monorepo Zugriff
dockerfile: config/docker/caddy/web-app/Dockerfile
args:
# Frontend spezifisch:
CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.11-alpine}"
# Metadaten:
VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
BUILD_DATE: "${DOCKER_BUILD_DATE}"
labels:
- "org.opencontainers.image.created=${DOCKER_BUILD_DATE}"
container_name: "${PROJECT_NAME:-meldestelle}-web-app"
restart: unless-stopped
ports:
- "${WEB_APP_PORT:-4000:4000}"
environment:
# Runtime Configuration — via envsubst in entrypoint.sh in config.json & index.html injiziert.
# Muss die öffentlich erreichbare URL sein (Browser-Zugriff!), NICHT localhost.
API_BASE_URL: "${WEB_APP_API_URL:-http://localhost:8081}"
# Keycloak Public URL (muss vom Browser aus erreichbar sein)
KEYCLOAK_URL: "${WEB_APP_KEYCLOAK_URL:-http://localhost:8180}"
depends_on:
api-gateway:
condition: "service_started"
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:4000/" ]
interval: 20s
timeout: 5s
retries: 5
start_period: 20s
networks:
meldestelle-network:
aliases:
- "web-app"
profiles: [ "gui", "all" ]
# web-app:
# image: "${DOCKER_REGISTRY:-git.mo-code.at/mo-code}/web-app:${DOCKER_TAG:-latest}"
# build:
# context: . # Wichtig: Root Context für Monorepo Zugriff
# dockerfile: config/docker/caddy/web-app/Dockerfile
# args:
# # Frontend spezifisch:
# CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.11-alpine}"
# # Metadaten:
# VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
# BUILD_DATE: "${DOCKER_BUILD_DATE}"
# labels:
# - "org.opencontainers.image.created=${DOCKER_BUILD_DATE}"
# container_name: "${PROJECT_NAME:-meldestelle}-web-app"
# restart: unless-stopped
# ports:
# - "${WEB_APP_PORT:-4000:4000}"
# environment:
# # Runtime Configuration — via envsubst in entrypoint.sh in config.json & index.html injiziert.
# # Muss die öffentlich erreichbare URL sein (Browser-Zugriff!), NICHT localhost.
# API_BASE_URL: "${WEB_APP_API_URL:-http://localhost:8081}"
# # Keycloak Public URL (muss vom Browser aus erreichbar sein)
# KEYCLOAK_URL: "${WEB_APP_KEYCLOAK_URL:-http://localhost:8180}"
# depends_on:
# api-gateway:
# condition: "service_started"
# healthcheck:
# test: [ "CMD", "wget", "--spider", "-q", "http://localhost:4000/" ]
# interval: 20s
# timeout: 5s
# retries: 5
# start_period: 20s
# networks:
# meldestelle-network:
# aliases:
# - "web-app"
# profiles: [ "gui", "all" ]
networks:
meldestelle-network:
+8 -2
View File
@@ -82,13 +82,18 @@ services:
restart: unless-stopped
profiles: [ "infra", "all" ]
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: "${KC_ADMIN_USERNAME:-kc-admin}"
KC_BOOTSTRAP_ADMIN_PASSWORD: "${KC_ADMIN_PASSWORD:-kc-password}"
KC_BOOTSTRAP_ADMIN_USERNAME: "${KC_BOOTSTRAP_ADMIN_USERNAME:-kc-admin}"
KC_BOOTSTRAP_ADMIN_PASSWORD: "${KC_BOOTSTRAP_ADMIN_PASSWORD:-kc-password}"
KC_FRONTEND_URL: "${KC_FRONTEND_URL:-http://localhost:8180}"
KC_PROXY_HEADERS: "${KC_PROXY_HEADERS:-xforwarded}"
KC_DB: "${KC_DB:-postgres}"
KC_DB_SCHEMA: "${KC_DB_SCHEMA:-keycloak}"
KC_DB_URL: "jdbc:postgresql://postgres:5432/${POSTGRES_DB:-pg-meldestelle-db}"
KC_DB_USERNAME: "${POSTGRES_USER:-pg-user}"
KC_DB_PASSWORD: "${POSTGRES_PASSWORD:-pg-password}"
# Hostname-Konfiguration: Für lokale Entwicklung "localhost", auf dem Server die echte IP/Domain setzen
KC_HOSTNAME: "${KC_HOSTNAME:-localhost}"
# WICHTIG: false erlaubt Zugriff über beliebige Hostnamen (nötig für Server-Betrieb ohne TLS)
@@ -98,6 +103,7 @@ services:
KC_HTTP_ENABLED: "true"
# Admin-Interface explizit auf allen Interfaces binden (0.0.0.0)
KC_HTTP_MANAGEMENT_PORT: "9000"
KC_HEALTH_ENABLED: "true"
KC_METRICS_ENABLED: "true"
# Integration der Power-Flags
+53
View File
@@ -0,0 +1,53 @@
name: "${PROJECT_NAME:-meldestelle}"
services:
# --- Statische Web-App (WASM) ---
web-app:
image: ${REGISTRY_INTERNAL:-10.0.0.22:3000}/mocode-software/meldestelle/web-app:${DOCKER_TAG:-latest}
container_name: ${PROJECT_NAME:-meldestelle}-web-app
restart: unless-stopped
environment:
# Diese Variablen werden vom Web-Container verwendet, um die Ziel-URLs in die index.html zu injizieren
API_BASE_URL: https://api.mo-code.at
MAIL_SERVICE_URL: https://api.mo-code.at
ports:
- "${WEB_APP_PORT:-4000:4000}"
networks: [meldestelle-network]
# --- Mail-Service (Plan-B: Form -> E-Mail) ---
mail-service:
image: ${REGISTRY_INTERNAL:-10.0.0.22:3000}/mocode-software/meldestelle/mail-service:${DOCKER_TAG:-latest}
container_name: ${PROJECT_NAME:-meldestelle}-mail-service
restart: unless-stopped
environment:
# Server-Port im Container (Spring Boot)
SERVER_PORT: "8085"
# Plan-B: Zipkin-Fehler unterdrücken
MANAGEMENT_TRACING_ENABLED: "false"
SPRING_ZIPKIN_ENABLED: "false"
# SMTP (World4You - PROD)
SPRING_MAIL_HOST: "smtp.world4you.com"
SPRING_MAIL_PORT: "587"
SPRING_MAIL_USERNAME: "online-nennen@mo-code.at"
SPRING_MAIL_PASSWORD: "Mogi#2reiten"
SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: "true"
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: "true"
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_REQUIRED: "true"
# Feature-Flags / Infra-Off
MAIL_POLLING_ENABLED: ${MAIL_POLLING_ENABLED:-false}
SPRING_CLOUD_CONSUL_ENABLED: ${SPRING_CLOUD_CONSUL_ENABLED:-false}
SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED: ${SPRING_CLOUD_CONSUL_DISCOVERY_ENABLED:-false}
SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER: ${SPRING_CLOUD_CONSUL_DISCOVERY_REGISTER:-false}
# Datenbank: H2 In-Memory (Default in application.yaml) KEINE Postgres-Variablen setzen
ports:
- "8092:${SERVER_PORT:-8085}" # Extern 8092 beibehalten
networks: [meldestelle-network]
networks:
meldestelle-network:
driver: bridge
+1 -1
View File
@@ -3,7 +3,7 @@ name: "${PROJECT_NAME:-meldestelle}"
include:
- dc-infra.yaml
- dc-backend.yaml
- dc-gui.yaml
#- dc-gui.yaml
- dc-ops.yaml
# Globale Definitionen, falls nötig (meistens reichen die in den Teil-Dateien,
@@ -1,71 +0,0 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
last_update: 2026-03-15
---
# Frontend-Architektur & Modularisierungsstrategie
**Status:** ENTWURF
**Zuletzt aktualisiert:** 2026-01-19
**Kontext:** Migration zu Clean Architecture & Feature-Modulen
---
## 1. Übersicht
Die Frontend-Architektur von **Meldestelle** basiert auf **Kotlin Multiplatform (KMP)** mit **Compose Multiplatform** für die Benutzeroberfläche. Wir folgen einem strikten **Clean Architecture**-Ansatz, um Testbarkeit, Skalierbarkeit und Trennung der Zuständigkeiten sicherzustellen.
## 2. Modulstruktur
Das Projekt ist in folgende Schichten unterteilt:
### 2.1 Core-Module (`frontend/core`)
Wiederverwendbare Komponenten, die unabhängig von spezifischen Geschäftsfunktionen sind.
* `core-network`: Zentrale HTTP-Client-Konfiguration (Auth, Logging, ContentNegotiation).
* `core-sync`: Generische Synchronisierungslogik (`SyncManager`, `SyncableRepository`).
* `core-ui`: Gemeinsame UI-Komponenten und Design-System.
### 2.2 Feature-Module (`frontend/features`)
Jede Geschäftsdomäne (z.B. `ping`, `auth`, `events`) liegt in ihrem eigenen Modul.
Ein Feature-Modul MUSS die **Clean Architecture** Paketstruktur einhalten:
* `at.mocode.{feature}.feature.domain`
* **Entitäten:** Reine Datenklassen.
* **Interfaces:** Repository-Interfaces, Service-Interfaces.
* **Use Cases:** Geschäftslogik (optional, für komplexe Logik).
* `at.mocode.{feature}.feature.data`
* **Implementierungen:** Repository-Implementierungen, API-Clients.
* **DTOs:** Data Transfer Objects (wenn von Domain-Entitäten abweichend).
* `at.mocode.{feature}.feature.presentation`
* **ViewModels:** Zustandsverwaltung.
* **Screens:** Composable-Funktionen.
* `at.mocode.{feature}.feature.di`
* **Koin-Modul:** Konfiguration der Dependency Injection.
### 2.3 Shells (`frontend/shells`)
Anwendungs-Einstiegspunkte, die alles zusammenführen.
* `meldestelle-portal`: Die Haupt-Web-/Desktop-Anwendung.
## 3. Migrationsstrategie (Übergangsphase)
Wir migrieren aktuell von einer monolithischen `clients`-Paketstruktur zu modularen Feature-Modulen.
**Regeln für die Migration:**
1. **Neue Features:** Müssen direkt in `frontend/features/{name}` unter Verwendung der Clean Architecture-Struktur implementiert werden.
2. **Bestehende Features:** Werden schrittweise migriert.
3. **Koexistenz:** Während des Übergangs ist Legacy-Code in `clients/` erlaubt, aber als veraltet markiert.
4. **Dependency Injection:** Legacy-Code muss die neuen Koin-Module verwenden, sofern verfügbar.
5. **Keine Ghost-Klassen:** Klassen nicht duplizieren. Wenn eine Klasse in ein Feature-Modul verschoben wird, muss die alte in `clients/` gelöscht werden.
## 4. Wichtige Entscheidungen (ADRs)
### ADR-001: Entkopplung der Sync-Logik
* **Entscheidung:** ViewModels dürfen nicht direkt vom `SyncManager` abhängen.
* **Begründung:** Um einfacheres Testen zu ermöglichen und die Komplexität des generischen Sync-Mechanismus zu verbergen.
* **Umsetzung:** Ein Domain-Service-Interface (z.B. `PingSyncService`) einführen, das den `SyncManager`-Aufruf kapselt.
### ADR-002: Feature-Modul-Isolation
* **Entscheidung:** Feature-Module sollten nach Möglichkeit nicht direkt voneinander abhängen.
* **Kommunikation:** Gemeinsame Core-Module oder lose Kopplung über Interfaces/Events verwenden, wenn modulübergreifende Kommunikation nötig ist.
---
**Freigegeben von:** Lead Architect
@@ -1,51 +0,0 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
---
# Open-Source-Konformität & Lizenz-Checkliste
Dieses Dokument dient der Überwachung und Sicherstellung der Open-Source-Konformität des Projekts **Meldestelle**. Es wird vom Lead Architect gepflegt.
## Status der Kern-Komponenten (Stand: Januar 2026)
| Komponente | Lizenz | Status | Risiko | Maßnahme / Kommentar |
| :--- | :--- | :--- | :--- | :--- |
| **Kotlin / JVM** | Apache 2.0 / GPLv2+CE | ✅ OK | Sehr gering | Standard-Stack. |
| **Spring Boot / Cloud** | Apache 2.0 | ✅ OK | Sehr gering | |
| **PostgreSQL** | PostgreSQL (BSD-like) | ✅ OK | Sehr gering | |
| **Redis** | **RSALv2 / SSPL** | ⚠️ KRITISCH | Hoch | **Umstieg auf Valkey (BSD) geplant.** |
| **Consul** | **BSL 1.1** | ⚠️ BEOBACHTEN | Mittel | Lizenzänderung durch HashiCorp. Für interne Nutzung aktuell unkritisch. |
| **Keycloak** | Apache 2.0 | ✅ OK | Gering | |
| **SQLDelight** | Apache 2.0 | ✅ OK | Sehr gering | |
| **Redisson** | Apache 2.0 (Core) | ✅ OK | Gering | Sicherstellen, dass keine PRO-Features genutzt werden. |
---
## Checkliste für neue Abhängigkeiten
Bevor eine neue Bibliothek oder Infrastruktur-Komponente hinzugefügt wird, muss sie folgende Kriterien erfüllen:
1. **Lizenz-Typ:**
* Bevorzugt: Apache 2.0, MIT, BSD (3-Clause).
* Akzeptabel: MPL 2.0.
* Einzelfallprüfung: LGPL (nur als dynamische Bibliothek).
* **Verboten:** AGPL, SSPL, RSAL, BSL (sofern nicht explizit vom Architect freigegeben).
2. **Community & Governance:**
* Wird das Projekt von einer neutralen Foundation (Apache, CNCF, Linux Foundation) verwaltet?
* Gibt es eine "Single Vendor" Abhängigkeit (Risiko einer plötzlichen Lizenzänderung)?
3. **Transitive Abhängigkeiten:**
* Bringt die Bibliothek "versteckte" Copyleft-Lizenzen mit? (Check via Gradle License Plugin).
---
## TODOs & Strategische Entscheidungen
- [ ] **Migration Redis -> Valkey:** Umstellung der Docker-Images und Test der Kompatibilität.
- [ ] **Consul Review:** Jährliche Prüfung der BSL-Auswirkungen auf unser Deployment-Modell.
- [ ] **Automatisierung:** Integration eines License-Check-Plugins in den CI-Build (z.B. `com.github.jk1.dependency-license-report`).
---
*Dieses Dokument ist Teil der "Docs-as-Code" Strategie und muss bei jeder Änderung am Tech-Stack aktualisiert werden.*
@@ -0,0 +1,54 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
---
# Open-Source-Konformität & Lizenz-Checkliste
Dieses Dokument dient der Überwachung und Sicherstellung der Open-Source-Konformität des Projekts **Meldestelle**. Es
wird vom Lead Architect gepflegt.
## Status der Kern-Komponenten (Stand: Januar 2026)
| Komponente | Lizenz | Status | Risiko | Maßnahme / Kommentar |
|:------------------------|:----------------------|:--------------|:------------|:------------------------------------------------------------------------|
| **Kotlin / JVM** | Apache 2.0 / GPLv2+CE | ✅ OK | Sehr gering | Standard-Stack. |
| **Spring Boot / Cloud** | Apache 2.0 | ✅ OK | Sehr gering | |
| **PostgreSQL** | PostgreSQL (BSD-like) | ✅ OK | Sehr gering | |
| **Redis** | **RSALv2 / SSPL** | ⚠️ KRITISCH | Hoch | **Umstieg auf Valkey (BSD) geplant.** |
| **Consul** | **BSL 1.1** | ⚠️ BEOBACHTEN | Mittel | Lizenzänderung durch HashiCorp. Für interne Nutzung aktuell unkritisch. |
| **Keycloak** | Apache 2.0 | ✅ OK | Gering | |
| **SQLDelight** | Apache 2.0 | ✅ OK | Sehr gering | |
| **Redisson** | Apache 2.0 (Core) | ✅ OK | Gering | Sicherstellen, dass keine PRO-Features genutzt werden. |
---
## Checkliste für neue Abhängigkeiten
Bevor eine neue Bibliothek oder Infrastruktur-Komponente hinzugefügt wird, muss sie folgende Kriterien erfüllen:
1. **Lizenz-Typ:**
* Bevorzugt: Apache 2.0, MIT, BSD (3-Clause).
* Akzeptabel: MPL 2.0.
* Einzelfallprüfung: LGPL (nur als dynamische Bibliothek).
* **Verboten:** AGPL, SSPL, RSAL, BSL (sofern nicht explizit vom Architect freigegeben).
2. **Community & Governance:**
* Wird das Projekt von einer neutralen Foundation (Apache, CNCF, Linux Foundation) verwaltet?
* Gibt es eine "Single Vendor" Abhängigkeit (Risiko einer plötzlichen Lizenzänderung)?
3. **Transitive Abhängigkeiten:**
* Bringt die Bibliothek "versteckte" Copyleft-Lizenzen mit? (Check via Gradle License Plugin).
---
## TODOs & Strategische Entscheidungen
- [ ] **Migration Redis -> Valkey:** Umstellung der Docker-Images und Test der Kompatibilität.
- [ ] **Consul Review:** Jährliche Prüfung der BSL-Auswirkungen auf unser Deployment-Modell.
- [ ] **Automatisierung:** Integration eines License-Check-Plugins in den CI-Build (z.B.
`com.github.jk1.dependency-license-report`).
---
*Dieses Dokument ist Teil der "Docs-as-Code" Strategie und muss bei jeder Änderung am Tech-Stack aktualisiert werden.*
@@ -3,6 +3,7 @@
Status: April 2026
## ✅ Abgeschlossene Migrationen (Feature-Module)
- `billing-feature`: `at.mocode.frontend.features.billing` (KMP)
- `verein-feature`: `at.mocode.frontend.features.verein` (KMP)
- `nennung-feature`: `at.mocode.frontend.features.nennung` (KMP)
@@ -13,11 +14,14 @@ Status: April 2026
- `ping-feature`: `at.mocode.ping.feature` (muss noch auf `at.mocode.frontend.features.ping` vereinheitlicht werden)
## 🚧 Ausstehende Migrationen (von `at.mocode.desktop.v2` zu Features)
Die folgenden Komponenten in `meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/` basieren noch auf `StoreV2` (In-Memory Mock) und sollten in KMP-Module überführt werden:
Die folgenden Komponenten in `meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/` basieren noch auf
`StoreV2` (In-Memory Mock) und sollten in KMP-Module überführt werden:
1. **Onboarding**: `OnboardingScreen.kt` -> Design-System Integration erfolgt, KMP-Modul folgt.
## 🧹 Architektur-Cleanup
- [ ] `at.mocode.desktop.v2.StoreV2` entfernen, sobald alle Screens auf ViewModels und API-Repositories umgestellt sind.
- [ ] `at.mocode.desktop.v2.TurnierStoreV2` konsolidieren mit dem `turnier-feature`.
- [ ] Paketnamen vereinheitlichen: `at.mocode.ping.feature` -> `at.mocode.frontend.features.ping`.
@@ -26,6 +30,7 @@ Die folgenden Komponenten in `meldestelle-desktop/src/jvmMain/kotlin/at/mocode/d
- [ ] `DesktopMainLayout.kt`: Die `when`-Zweige für `v2` Screens aufräumen, sobald die Module bereit sind.
## ✅ Abgeschlossen am 11.04.2026
- Migration `pferde-feature`, `reiter-feature`, `funktionaer-feature`, `veranstalter-feature`.
- Integration in `DesktopMainLayout` und `AppScreen`.
- Bereinigung der Repository-Pakete.
@@ -9,7 +9,9 @@ last_update: 2026-04-03
## Ziel und Rahmen
Dieses Dokument definiert das Synchronisations-Konzept zwischen der Compose Desktop App (Meldestelle-Zentrale) und dem Backend in einem Offline-First Szenario. Es baut auf ADR-0021 (Tenant-Isolation) und ADR-0022 (LAN-Sync mit Lamport-Uhren) auf und erweitert sie um die WAN/Backend-Synchronisation.
Dieses Dokument definiert das Synchronisations-Konzept zwischen der Compose Desktop App (Meldestelle-Zentrale) und dem
Backend in einem Offline-First Szenario. Es baut auf ADR-0021 (Tenant-Isolation) und ADR-0022 (LAN-Sync mit
Lamport-Uhren) auf und erweitert sie um die WAN/Backend-Synchronisation.
Nicht-Ziele: Cloud-Realtime für Endnutzer, kollaboratives Editieren außerhalb des Veranstaltungsbetriebs.
@@ -18,32 +20,38 @@ Nicht-Ziele: Cloud-Realtime für Endnutzer, kollaboratives Editieren außerhalb
1. Offline-First: Die Desktop-App ist voll funktionsfähig ohne Netzwerk; Synchronisation erfolgt opportunistisch.
2. Event-isoliert: Pro Veranstaltung eigener Datenraum (gemäß ADR-0021). Keine Vermischung von Events.
3. Einheitliches Änderungsmodell: Wiederverwendung des `SyncEvent`-Logs (ADR-0022) für Desktop↔Backend.
4. Domänen-Mastership: Klare Schreibhoheiten reduzieren Konflikte, fachliche Regeln haben Vorrang vor rein technischen Timestamps.
4. Domänen-Mastership: Klare Schreibhoheiten reduzieren Konflikte, fachliche Regeln haben Vorrang vor rein technischen
Timestamps.
5. Deterministische Konfliktauflösung: Lamport-Uhren + Regel-Matrix; keine Abhängigkeit von Systemuhren.
## Topologie & Rollen
- Backend (Zentrale Plattform):
- Master für: Stammdaten (Reiter, Pferde, Vereine, Funktionäre), Identität/Rollen, Gebührenkataloge, globale Referenzen.
- Master für: Stammdaten (Reiter, Pferde, Vereine, Funktionäre), Identität/Rollen, Gebührenkataloge, globale
Referenzen.
- Aggregations-/Archiv-Quelle nach Veranstaltungsende (finale Ergebnisse, Abrechnungen).
- Desktop (Meldestelle-Zentrale):
- Master während der Veranstaltung für: Nennungen (operativ), Startreihenfolgen, Startlisten-Status, Ergebnisse/Protokolle (falls Richter nicht direkt am Backend), Kassa-Operationen vor Ort.
- Hält lokales `SyncEvent`-Log + Snapshots (vgl. ADR-0022) und synchronisiert mit Backend, sobald Konnektivität besteht.
- Master während der Veranstaltung für: Nennungen (operativ), Startreihenfolgen, Startlisten-Status,
Ergebnisse/Protokolle (falls Richter nicht direkt am Backend), Kassa-Operationen vor Ort.
- Hält lokales `SyncEvent`-Log + Snapshots (vgl. ADR-0022) und synchronisiert mit Backend, sobald Konnektivität
besteht.
Hinweis Mehrfach-Desktops: Genau eine „Zentrale“ pro Veranstaltung besitzt Schreibhoheit (Konfig-Flag `isEventAuthority=true`). Weitere Geräte sind Replikate/Clients.
Hinweis Mehrfach-Desktops: Genau eine „Zentrale“ pro Veranstaltung besitzt Schreibhoheit (Konfig-Flag
`isEventAuthority=true`). Weitere Geräte sind Replikate/Clients.
## Datenkategorien & Mastership
| Kategorie | Master | Desktop Rechte | Backend Rechte |
|--------------------------|----------------|--------------------------------|-----------------------------------|
| Stammdaten (Actor) | Backend | Lesen, lokal „provisional“ anlegen (Temp-ID) | Vollzugriff, ID-Zuteilung, Merge |
| Veranstaltungs-Stammdaten| Backend | Lesen | Vollzugriff |
| Nennungen operativ | Desktop | Vollzugriff | Lesen, Import nach Sync |
| Startreihenfolge/Status | Desktop | Vollzugriff | Lesen, Import nach Sync |
| Bewertungen/Ergebnisse | Desktop/Richter| Vollzugriff (Eventzeitraum) | Lesen, Publikation/Archiv |
| Kassa/Finanzen vor Ort | Desktop | Vollzugriff | Lesen, Abgleich Summen |
| Kategorie | Master | Desktop Rechte | Backend Rechte |
|---------------------------|-----------------|----------------------------------------------|----------------------------------|
| Stammdaten (Actor) | Backend | Lesen, lokal „provisional“ anlegen (Temp-ID) | Vollzugriff, ID-Zuteilung, Merge |
| Veranstaltungs-Stammdaten | Backend | Lesen | Vollzugriff |
| Nennungen operativ | Desktop | Vollzugriff | Lesen, Import nach Sync |
| Startreihenfolge/Status | Desktop | Vollzugriff | Lesen, Import nach Sync |
| Bewertungen/Ergebnisse | Desktop/Richter | Vollzugriff (Eventzeitraum) | Lesen, Publikation/Archiv |
| Kassa/Finanzen vor Ort | Desktop | Vollzugriff | Lesen, Abgleich Summen |
Konflikte über Kategoriegrenzen werden durch Mastership-Regeln verhindert; verbleibende Konflikte werden per Regel-Matrix gelöst.
Konflikte über Kategoriegrenzen werden durch Mastership-Regeln verhindert; verbleibende Konflikte werden per
Regel-Matrix gelöst.
## Änderungsmodell (Wiederverwendung `SyncEvent`)
@@ -71,11 +79,13 @@ data class SyncEvent(
## Lamport-Uhren & Vector-Clock (Optional)
- Primär: Lamport-Uhren wie ADR-0022. Gleichstand → lexikografisch größere `originNodeId` gewinnt (Determinismus).
- Optional für feingranulare Erkennung: Per-Aggregat Vector-Clock (`Map<nodeId, lamport>`) zur Diagnose; Entscheidungsgrundlage bleibt Lamport + Fachregeln.
- Optional für feingranulare Erkennung: Per-Aggregat Vector-Clock (`Map<nodeId, lamport>`) zur Diagnose;
Entscheidungsgrundlage bleibt Lamport + Fachregeln.
## Sync-Protokoll Desktop ↔ Backend (HTTPS)
Transport: HTTPS (HTTP/2), JSON oder Protobuf, idempotente Endpunkte. Auth: mTLS zwischen Desktop und Backend ODER OAuth2 Client Credentials + Signatur der Batch-Payload.
Transport: HTTPS (HTTP/2), JSON oder Protobuf, idempotente Endpunkte. Auth: mTLS zwischen Desktop und Backend ODER
OAuth2 Client Credentials + Signatur der Batch-Payload.
Empfohlene Endpunkte (pro `eventId`):
@@ -94,42 +104,55 @@ Idempotenz: Jeder `SyncEvent` wird durch `(eventId, originNodeId, sequenceNumber
## Konfliktauflösung
1) Strukturkonflikte (gleiches Aggregat, konkurrierende Events):
- Wenn eine Seite nicht Master ist → Event wird angenommen, aber als `PENDING_REVIEW` markiert; fachliche Entscheidung erforderlich (Backend-UI oder Desktop-Review-Queue).
- Wenn eine Seite nicht Master ist → Event wird angenommen, aber als `PENDING_REVIEW` markiert; fachliche Entscheidung
erforderlich (Backend-UI oder Desktop-Review-Queue).
- Beide Master (Sonderfälle, z. B. Ergebnisse während parallelem Backend-Fix):
- Lamport höher gewinnt.
- Gleichstand → `originNodeId`-Tiebreaker.
- Zusätzlich fachliche Heuristik optional: „Korrektur-Events“ (z. B. `ErgebnisKorrigiert`) schlagen normale `ErgebnisErfasst` bei gleichem Lamport.
- Zusätzlich fachliche Heuristik optional: „Korrektur-Events“ (z. B. `ErgebnisKorrigiert`) schlagen normale
`ErgebnisErfasst` bei gleichem Lamport.
2) Identitätskonflikte (provisionale Stammdaten):
- Desktop darf temporäre Einträge (Temp-ID `tmp-...`) erzeugen.
- Beim Push führt Backend `Upsert+Merge` aus, weist finale IDs zu und liefert `IdMapping { tmpId -> finalId }` zurück.
- Desktop ersetzt Referenzen transaktional und emittiert ein lokales `IdRemapped`-Event (kein Re-Upload nötig, außer für Diagnose).
- Desktop ersetzt Referenzen transaktional und emittiert ein lokales `IdRemapped`-Event (kein Re-Upload nötig, außer für
Diagnose).
3) Reihenfolge-/Kausalitätskonflikte:
- Bei fehlenden Vorgänger-Events antwortet Backend mit `rejected: [id]` und `requiredSinceSeq`. Desktop zieht Delta (`pull`) und wiederholt den `push`.
- Bei fehlenden Vorgänger-Events antwortet Backend mit `rejected: [id]` und `requiredSinceSeq`. Desktop zieht Delta (
`pull`) und wiederholt den `push`.
## Snapshots & Recovery
- Snapshot-Intervall: standardmäßig 100 Events pro `(aggregateType, scope)` (wie ADR-0022), für WAN-Sync zusätzlich Full-State-Snapshot pro Veranstaltung vor Event-Abschluss.
- Recovery: Desktop kann mit leerem Log starten → `snapshot/request` Full-State + `snapshotSeq` → weitere Deltas über `pull`.
- USB-Fallback (Notbetrieb): Export/Import von `sync_events` und `sync_snapshots` als verschlüsselte Archive (`.msync`). Offene Spezifikation; separater PoC.
- Snapshot-Intervall: standardmäßig 100 Events pro `(aggregateType, scope)` (wie ADR-0022), für WAN-Sync zusätzlich
Full-State-Snapshot pro Veranstaltung vor Event-Abschluss.
- Recovery: Desktop kann mit leerem Log starten → `snapshot/request` → Full-State + `snapshotSeq` → weitere Deltas über
`pull`.
- USB-Fallback (Notbetrieb): Export/Import von `sync_events` und `sync_snapshots` als verschlüsselte Archive (`.msync`).
Offene Spezifikation; separater PoC.
## Sicherheit
- Mandantentrennung: Jeder Request trägt `X-Event-Id` (ADR-0021). Backend validiert gegen `control.tenants`.
- Transport: `https` + mTLS (bevorzugt) oder `https` + OAuth2 Client Credentials. Payload-Signatur (HMAC-SHA256) empfohlen.
- Transport: `https` + mTLS (bevorzugt) oder `https` + OAuth2 Client Credentials. Payload-Signatur (HMAC-SHA256)
empfohlen.
- Integrität: `checksum` pro Event wird serverseitig geprüft; Mismatch → Reject.
- Rechte: Backend erzwingt Mastership-Regeln serverseitig; Verstöße → `PENDING_REVIEW` + Audit-Log.
## Fehlerfälle & Resilienz
- Netzwerkfehler: Exponentielles Backoff (bis 5 min), Offline-Queue unbegrenzt (bounded by disk quota), Telemetrie im UI.
- Netzwerkfehler: Exponentielles Backoff (bis 5 min), Offline-Queue unbegrenzt (bounded by disk quota), Telemetrie im
UI.
- Schema-Divergenz: `minSupportedSchema` aus `hello`; Desktop migriert vor weiterem Sync oder schaltet in Read-Only.
- Duplikate: Idempotenz-Keys verhindern Doppelverarbeitung. ACK enthält höchste verarbeitete `sequenceNumber`.
## Observability
- Metriken: `sync_push_events_total`, `sync_pull_events_total`, `sync_rejected_total`, `sync_latency_ms` (p50/p95), `offline_duration_s`.
- Metriken: `sync_push_events_total`, `sync_pull_events_total`, `sync_rejected_total`, `sync_latency_ms` (p50/p95),
`offline_duration_s`.
- Logs: pro Event `tenant`, `originNodeId`, `seq`, `aggType`, `eventType`, `result`.
- UI: Status-Anzeige (Verbunden, Getrennt, Ausstehend X), Konflikt-Review-Queue.
@@ -5,72 +5,96 @@
> **Kontext:** Phase 9 — Zeitplan & Protokollierung
## 1. Vision & Zielsetzung
Die Zeitplan-Optimierung ist das zentrale Werkzeug für die Meldestelle, um den Turnierablauf dynamisch an die Gegebenheiten vor Ort anzupassen. Ziel ist eine intuitive, visuelle Oberfläche (Kalender-Ansicht), in der Bewerbe und Abteilungen per Drag & Drop verschoben werden können, wobei das System automatisch Auswirkungen auf Folge-Bewerbe berechnet und vor Konflikten warnt.
Die Zeitplan-Optimierung ist das zentrale Werkzeug für die Meldestelle, um den Turnierablauf dynamisch an die
Gegebenheiten vor Ort anzupassen. Ziel ist eine intuitive, visuelle Oberfläche (Kalender-Ansicht), in der Bewerbe und
Abteilungen per Drag & Drop verschoben werden können, wobei das System automatisch Auswirkungen auf Folge-Bewerbe
berechnet und vor Konflikten warnt.
## 2. Fachliche Anforderungen (Use Cases)
### UC-1: Verschieben eines Bewerbs/einer Abteilung
- Ein Benutzer zieht einen Bewerb oder eine Abteilung auf eine neue Startzeit oder einen anderen Austragungsplatz.
- **System-Reaktion:**
- Neuberechnung der Endzeit basierend auf `reitdauerMinuten * starterAnzahl + besichtigungMinuten + umbauMinuten`.
- Prüfung auf Überschneidungen am selben Austragungsplatz.
- Warnung bei Konflikten (z.B. Richter-Doppelbelegung).
- Neuberechnung der Endzeit basierend auf `reitdauerMinuten * starterAnzahl + besichtigungMinuten + umbauMinuten`.
- Prüfung auf Überschneidungen am selben Austragungsplatz.
- Warnung bei Konflikten (z.B. Richter-Doppelbelegung).
### UC-2: Dynamische Zeitplan-Anpassung („Anschließend“)
- Bewerbe können als `ANSCHLIESSEND` markiert werden (`beginnZeitTyp`).
- **System-Reaktion:** Wenn der vorangehende Bewerb verschoben wird oder länger dauert, verschiebt sich der anschließende Bewerb automatisch mit.
- **System-Reaktion:** Wenn der vorangehende Bewerb verschoben wird oder länger dauert, verschiebt sich der
anschließende Bewerb automatisch mit.
### UC-3: Drag & Drop in der Startliste
- Innerhalb einer Startliste können Teilnehmer per Drag & Drop umsortiert werden.
- **System-Reaktion:** Automatische Aktualisierung der Kopfnummern (Startnummern) und Neuberechnung der individuellen Startzeiten.
- **System-Reaktion:** Automatische Aktualisierung der Kopfnummern (Startnummern) und Neuberechnung der individuellen
Startzeiten.
### UC-4: Protokollierung (Audit Log)
- Jede manuelle Änderung am Zeitplan oder der Startlisten-Reihenfolge muss protokolliert werden.
- **Grund:** Nachvollziehbarkeit bei Einsprüchen und Synchronisation zwischen verschiedenen Arbeitsplätzen (Meldestelle ↔ Richterturm).
- **Grund:** Nachvollziehbarkeit bei Einsprüchen und Synchronisation zwischen verschiedenen Arbeitsplätzen (
Meldestelle ↔ Richterturm).
## 3. Datenmodell-Erweiterungen & Logik
### 3.1 Erweiterung `Bewerb` & `Abteilung`
Das bestehende Modell in `entries-domain` deckt bereits viele Felder ab. Für die Optimierung präzisieren wir:
- **`Umbauzeit`:** Zeit zwischen zwei Abteilungen oder Bewerben.
- **`Pausen`:** Geplante Unterbrechungen (z.B. Mittagspause, Platzpflege) werden als spezielle „Blocker-Events“ im Zeitplan geführt.
- **`Pausen`:** Geplante Unterbrechungen (z.B. Mittagspause, Platzpflege) werden als spezielle „Blocker-Events“ im
Zeitplan geführt.
- **`BesichtigungsBlock` (NEU):** Eigenständiges Objekt für Parcoursbesichtigungen.
- Kann mit mehreren Bewerben/Abteilungen verknüpft werden (Cross-Competition Inspection).
- Unterstützt Typen: `ZU_FUSS` (Standard) und `ZU_PFERD` (Spezialfall für Springpferde bis 110cm gemäß ÖTO).
- Validierung: Mindestens 5 Min. Puffer vor dem ersten Start (ÖTO §43).
- Kann mit mehreren Bewerben/Abteilungen verknüpft werden (Cross-Competition Inspection).
- Unterstützt Typen: `ZU_FUSS` (Standard) und `ZU_PFERD` (Spezialfall für Springpferde bis 110cm gemäß ÖTO).
- Validierung: Mindestens 5 Min. Puffer vor dem ersten Start (ÖTO §43).
### 3.2 Zeitberechnungs-Algorithmus (Präzisierung)
Die Logik in `StartlistenService.berechneStartzeiten` wird erweitert:
1. **Basis:** `Startzeit` der Abteilung.
2. **Vorlauf:** `besichtigungMinuten`.
3. **Starter-Loop:**
- `individuelleStartzeit = aktuelleZeit`.
- `aktuelleZeit += bewerb.reitdauerMinuten`.
- *Neu:* Berücksichtigung von festen Pausen nach X Startern (z.B. 10 Min. Pause alle 20 Starter).
- `individuelleStartzeit = aktuelleZeit`.
- `aktuelleZeit += bewerb.reitdauerMinuten`.
- *Neu:* Berücksichtigung von festen Pausen nach X Startern (z.B. 10 Min. Pause alle 20 Starter).
4. **Nachlauf:** `umbauMinuten` am Ende der Abteilung/des Bewerbs.
### 3.3 Drag & Drop Logik (Frontend & Backend)
- **Frontend (Compose Desktop):**
- Implementierung eines `DraggableBewerbItem`.
- Visuelle Darstellung von Konflikten (Rote Markierung bei Überlappung).
- Implementierung eines `DraggableBewerbItem`.
- Visuelle Darstellung von Konflikten (Rote Markierung bei Überlappung).
- **Backend (API):**
- Neuer Endpunkt `PATCH /bewerbe/{id}/zeitplan` für schnelle Updates.
- Validierung der neuen Zeiten gegen den `austragungsplatzId` und `richterEinsaetze`.
- Neuer Endpunkt `PATCH /bewerbe/{id}/zeitplan` für schnelle Updates.
- Validierung der neuen Zeiten gegen den `austragungsplatzId` und `richterEinsaetze`.
## 4. Konflikt-Management
Das System arbeitet nach dem Prinzip **„Warnen statt Blockieren“** (ADR-0016):
- **Harte Fehler:** Nur bei technischer Unmöglichkeit (z.B. Datum in der Vergangenheit bei Live-Betrieb).
- **Warnungen:**
- Überlappung auf dem Platz.
- Richter hat gleichzeitig Einsatz in anderem Bewerb.
- Zeitunterschreitung für Reiter (Reiter startet in zwei kurz aufeinanderfolgenden Bewerben).
- Überlappung auf dem Platz.
- Richter hat gleichzeitig Einsatz in anderem Bewerb.
- Zeitunterschreitung für Reiter (Reiter startet in zwei kurz aufeinanderfolgenden Bewerben).
## 5. Synchronisation (Offline-First)
Änderungen am Zeitplan erzeugen `SyncEvents` (gemäß ADR-0022).
- **Lamport-Uhren:** Stellen sicher, dass bei gleichzeitigen Änderungen an zwei Laptops die zeitlich spätere Änderung (oder die mit höherer Priorität) gewinnt.
- **Echtzeit-Update:** Über WebSockets werden Zeitplan-Änderungen sofort an alle verbundenen Clients (z.B. Anzeige-Monitor, Richter-Tablet) gepusht.
- **Lamport-Uhren:** Stellen sicher, dass bei gleichzeitigen Änderungen an zwei Laptops die zeitlich spätere Änderung (
oder die mit höherer Priorität) gewinnt.
- **Echtzeit-Update:** Über WebSockets werden Zeitplan-Änderungen sofort an alle verbundenen Clients (z.B.
Anzeige-Monitor, Richter-Tablet) gepusht.
## 6. Nächste Schritte
1. **Backend:** ✅ Implementiert (BewerbService, API, Zeitberechnungs-Pausen).
2. **Frontend:** Prototyping der Kalender-Ansicht mit Compose Desktop.
3. **QA:** Test-Szenarien für komplexe Verschiebungen (Kettenreaktionen bei „Anschließend“).
@@ -5,42 +5,63 @@
> **Kontext:** Zeitplan-Optimierung & Praxis-Szenarien
## 1. Parcoursbesichtigung zu Pferd
Die Aussage des Users wurde geprüft und bestätigt.
### 1.1 Regelwerks-Referenz (ÖTO 2026)
Gemäß **ÖTO Teil B, § 43 (Abweichungen)** gilt:
- **Normalfall:** Parcoursbesichtigung erfolgt zu Fuß.
- **Springpferde- & Jungpferdeprüfungen (bis 110 cm):** Eine Besichtigung **zu Pferd im Schritt** kann von der Richtergruppe erlaubt werden.
- **Zweck:** Junge, unerfahrene Pferde sollen stressfrei an optische Reize (Fangständer, Farben, Unterbauten) herangeführt werden.
- **Springpferde- & Jungpferdeprüfungen (bis 110 cm):** Eine Besichtigung **zu Pferd im Schritt** kann von der
Richtergruppe erlaubt werden.
- **Zweck:** Junge, unerfahrene Pferde sollen stressfrei an optische Reize (Fangständer, Farben, Unterbauten)
herangeführt werden.
### 1.2 Organisatorische Implikationen
- **Dauer:** Die Besichtigung zu Pferd benötigt oft etwas mehr Zeit für die Koordination am Einlass (15-20 Min. sind praxisnah).
- **Sicherheit:** Da Pferde im Parcours sind, während andere Reiter eventuell noch draußen warten, muss der Zeitplan einen Puffer von mindestens **5 Minuten** zwischen Ende Besichtigung und erstem Start vorsehen (ÖTO Vorschrift).
- **Dauer:** Die Besichtigung zu Pferd benötigt oft etwas mehr Zeit für die Koordination am Einlass (15-20 Min. sind
praxisnah).
- **Sicherheit:** Da Pferde im Parcours sind, während andere Reiter eventuell noch draußen warten, muss der Zeitplan
einen Puffer von mindestens **5 Minuten** zwischen Ende Besichtigung und erstem Start vorsehen (ÖTO Vorschrift).
## 2. Zusammengelegte Besichtigungen (Cross-Competition Inspection)
In der Praxis üblich, wenn der Parcours für aufeinanderfolgende Bewerbe identisch bleibt.
### 2.1 Szenario
- **Bewerb A:** Springpferdeprüfung 105cm (Besichtigung zu Pferd erlaubt).
- **Bewerb B:** Standardspringprüfung 105cm (Besichtigung zu Fuß).
- **Lösung:** Eine gemeinsame Besichtigungszeit vor Bewerb A.
### 2.2 Regelwerks-Konformität
- Es gibt kein Verbot für gemeinsame Besichtigungen, solange die Teilnehmer beider Bewerbe die Möglichkeit haben, den Parcours regelkonform zu besichtigen.
- **Wichtig:** Wenn Bewerb B erst 2 Stunden später startet, muss für Teilnehmer von Bewerb B theoretisch eine zweite Besichtigungsmöglichkeit (oder ein "Refresh") angeboten werden, falls der Parcours zwischenzeitlich verändert wurde. Wenn er identisch bleibt, reicht die einmalige Bekanntgabe.
- Es gibt kein Verbot für gemeinsame Besichtigungen, solange die Teilnehmer beider Bewerbe die Möglichkeit haben, den
Parcours regelkonform zu besichtigen.
- **Wichtig:** Wenn Bewerb B erst 2 Stunden später startet, muss für Teilnehmer von Bewerb B theoretisch eine zweite
Besichtigungsmöglichkeit (oder ein "Refresh") angeboten werden, falls der Parcours zwischenzeitlich verändert wurde.
Wenn er identisch bleibt, reicht die einmalige Bekanntgabe.
## 3. Anforderungen für die Software (Zeitplan-Modul)
### 3.1 Datenmodell
- `Abteilung` oder `Bewerb` benötigt ein Flag `besichtigungZuPferdErlaubt` (Boolean).
- `BesichtigungsBlock` als eigenständiges Entitätsobjekt im Zeitplan, das mit **mehreren** Bewerben/Abteilungen verknüpft werden kann.
- `BesichtigungsBlock` als eigenständiges Entitätsobjekt im Zeitplan, das mit **mehreren** Bewerben/Abteilungen
verknüpft werden kann.
### 3.2 Validierung & Warnungen
- **Warnung:** Wenn ein Besichtigungsblock mit "zu Pferd" markiert ist, aber ein verbundener Bewerb (z.B. L-Springen) dies laut ÖTO normalerweise nicht vorsieht (Diskretionsspielraum der Richter).
- **Puffer-Check:** Automatische Prüfung, ob zwischen `Ende Besichtigung` und `Start Erster Reiter` mindestens 5 Minuten liegen.
- **Warnung:** Wenn ein Besichtigungsblock mit "zu Pferd" markiert ist, aber ein verbundener Bewerb (z.B. L-Springen)
dies laut ÖTO normalerweise nicht vorsieht (Diskretionsspielraum der Richter).
- **Puffer-Check:** Automatische Prüfung, ob zwischen `Ende Besichtigung` und `Start Erster Reiter` mindestens 5 Minuten
liegen.
## 4. Fazit für Architect & Entwickler
Die "Besichtigung zu Pferd" ist ein valider Spezialfall für Springpferdeprüfungen. Die Software muss erlauben, Besichtigungszeiten flexibel vor einen oder mehrere Bewerbe zu lagern und den Typ (Fuß/Pferd) zu kennzeichnen.
Die "Besichtigung zu Pferd" ist ein valider Spezialfall für Springpferdeprüfungen. Die Software muss erlauben,
Besichtigungszeiten flexibel vor einen oder mehrere Bewerbe zu lagern und den Typ (Fuß/Pferd) zu kennzeichnen.
---
*Geprüft durch den 📜 ÖTO/FEI Rulebook Expert am 11.04.2026*
@@ -0,0 +1,62 @@
# 🤖 Konzept: Status-Automat für Nennungen & Zeitplan-Synchronisation
Dieses Dokument spezifiziert die Logik des Status-Automaten für Nennungen (Sprint C-1) und dessen Auswirkungen auf den
dynamischen Zeitplan.
## 1. Status-Definitionen (NennStatusE)
Basierend auf `core-domain/Enums.kt`:
| Status | Bedeutung | Auswirkung auf Zeitplan |
|:-------------------|:--------------------------------------------------|:--------------------------------------------------------|
| `EINGEGANGEN` | Nennung wurde erstellt (Initialzustand) | Belegt Zeitslot (basierend auf `reitdauerMinuten`) |
| `BESTAETIGT` | Meldestelle hat Nennung geprüft | Belegt Zeitslot |
| `NACHNENNUNG` | Nennung nach Nennschluss | Belegt Zeitslot (ggf. am Ende der Liste) |
| `TRANSFERIERT` | Nennung wurde auf anderes Paar übertragen | **Inaktiv** (Original-Eintrag wird durch neuen ersetzt) |
| `ZURUECKGEZOGEN` | Reiter hat abgemeldet (vor Startlistenerstellung) | **Inaktiv** (Slot wird frei) |
| `GESTARTET` | Paar ist in die Prüfung eingeritten | Startzeitpunkt fixiert, Folgestarts ggf. anpassen |
| `NICHT_ANGETRETEN` | Paar ist zum Startzeitpunkt nicht erschienen | **Zeitslot verfällt** oder Folgestarts rücken nach |
## 2. Status-Übergänge & Validierung
### 2.1 Gültige Übergänge (Beispiele)
- `EINGEGANGEN` -> `BESTAETIGT` (Normalfall)
- `EINGEGANGEN` -> `ZURUECKGEZOGEN` (Abmeldung)
- `BESTAETIGT` -> `TRANSFERIERT` (Reitertausch)
- `BESTAETIGT` -> `GESTARTET` (Während des Turniers)
### 2.2 Side-Effects (Side-Effect-Engine)
Wenn sich der Status einer Nennung ändert, müssen folgende Systeme informiert werden:
1. **Billing-Service:** Bei `ZURUECKGEZOGEN` ggf. Stornogebühren prüfen. Bei `TRANSFERIERT` Guthaben übertragen.
2. **Startlisten-Service:** Bei `ZURUECKGEZOGEN` nach Veröffentlichung der Startliste -> Eintrag als
`istGestrichen = true` markieren.
3. **Zeitplan-Optimierung:** Bei `NICHT_ANGETRETEN` -> Alle folgenden Startzeiten rücken um `reitdauerMinuten` nach
vorne (sofern im Kalender-Modus "Dynamisch" aktiv ist).
## 3. Dynamische Zeitplan-Anpassung (C-1 Extension)
Der `StartlistenService` muss eine Methode `aktualisiereZeitplanNachStatusAenderung` erhalten:
- **Szenario A (Nicht angetreten):** Wenn Nennung X `NICHT_ANGETRETEN` wird, rücken alle folgenden Nennungen in der
Abteilung nach vorne.
- **Szenario B (Verspätung):** Wenn Nennung X `GESTARTET` wird, aber 2 Minuten später als geplant, verschieben sich alle
Folgetermine um +2 Minuten (Kettenreaktion).
### Puffer-Regel (ÖTO-Konformität)
- Eine dynamische Verschiebung nach *vorne* darf nie dazu führen, dass ein Reiter vor seiner ursprünglich kommunizierten
Startzeit (oder einem definierten Puffer-Zeitraum von z.B. 15 Minuten) starten muss, ohne dass dies explizit bestätigt
wurde.
## 4. Implementierungs-Leitfaden für Backend (C-1)
1. Erweiterung von `NennungUseCases.statusAendern` um Aufrufe der Side-Effect-Handler.
2. Implementierung des `NennungStatusListener` in `entries-service`.
3. Anbindung an den `StartlistenService` zur Zeitre-Kalkulation.
---
**Status:** Entwurf (Lead Architect)
**Datum:** 11. April 2026
+94 -297
View File
@@ -2,12 +2,12 @@
type: Roadmap
status: ACTIVE
owner: Lead Architect
last_update: 2026-04-11
last_update: 2026-05-06
---
# MASTER ROADMAP: Meldestelle-Biest
# MASTER ROADMAP: Meldestelle
🏗️ **[Lead Architect]** | 11. April 2026
🏗️ **[Lead Architect]** | 30. April 2026
**Strategisches Ziel:**
Entwicklung einer ÖTO-konformen, offline-fähigen Turnier-Meldestelle als Compose Desktop App (KMP).
@@ -17,7 +17,7 @@ Vollständige Self-Hosted Infrastruktur (Gitea, Pangolin, Zora). Datensouveräni
- Desktop-App ist der primäre Client (Compose Desktop, KMP) — „Desktop-First“ gilt für UX und Architektur.
- Offline-First Betrieb mit lokaler Persistenz und opportunistischer Synchronisation.
**Aktueller technischer Stand (30.03.2026):**
**Aktueller technischer Stand (30.04.2026):**
* **Infrastruktur:** ✅ "Zora" (MS-R1, ARM64) ist live. Gitea & Registry laufen.
* **Networking:** ✅ Pangolin Tunnel ersetzt Cloudflare.
* **CI/CD:** ✅ Gitea Actions mit ARM64-Runner (VM 102) aktiv. Docker-Publish Pipeline grün.
@@ -35,15 +35,15 @@ Vollständige Self-Hosted Infrastruktur (Gitea, Pangolin, Zora). Datensouveräni
Das System ist in **6 Self-Contained Systems (SCS)** aufgeteilt, die fachlich voneinander getrennt sind
und über definierte Schnittstellen kommunizieren.
| SCS | Kontext | Priorität | Status |
|----------------------------|---------------------------------------|-----------|----------------|
| `registration-context` | Nennungs-Workflow (Herzstück) | **P1** | ✅ Fertig |
| `actor-context` | Reiter, Pferde, Funktionäre, ZNS | **P1** | ✅ Fertig |
| `competition-context` | Bewerbe, Startlisten, Ergebnisse | **P2** | ✅ Fertig |
| `event-management-context` | Veranstaltung, Turnier, Ausschreibung | **P2** | ✅ Fertig |
| `series-context` | Cups, Serien, Meisterschaften | Phase 2+ | ✅ Fertig |
| `billing-context` | Abrechnung, Kassa, Gebühren | **P3** | 🔵 In Arbeit |
| `identity-context` | Auth, Rollen (Keycloak) | **P3** | ✅ Fertig |
| SCS | Kontext | Priorität | Status |
|----------------------------|---------------------------------------|-----------|--------------|
| `registration-context` | Nennungs-Workflow (Herzstück) | **P1** | 🔵 In Arbeit |
| `actor-context` | Reiter, Pferde, Funktionäre, ZNS | **P1** | 🟡 MVP |
| `competition-context` | Bewerbe, Startlisten, Ergebnisse | **P2** | 🔵 In Arbeit |
| `event-management-context` | Veranstaltung, Turnier, Ausschreibung | **P2** | 🔵 In Arbeit |
| `series-context` | Cups, Serien, Meisterschaften | Phase 2+ | 🔵 In Arbeit |
| `billing-context` | Abrechnung, Kassa, Gebühren | **P3** | 🔵 In Arbeit |
| `identity-context` | Auth, Rollen (Keycloak) | **P3** | 🟡 MVP |
> **Hinweis `series-context`:** Ist Phase 2+, aber die Architektur ist von Anfang an vorbereitet.
> Cups/Serien/Meisterschaften benötigen eigene, konfigurierbare Reglements (kein Hard-Coding).
@@ -51,330 +51,127 @@ und über definierte Schnittstellen kommunizieren.
---
## 1. Abgeschlossene Phasen
## 1. Abgeschlossene Phasen (Verifiziert)
### PHASE 1: Documentation Cleanup ✅ ABGESCHLOSSEN
*Ziel: Eine einzige, vertrauenswürdige Quelle der Wahrheit (SSOT) schaffen.*
### PHASE 1: Domain-Design & Ubiquitous Language ✅
#### 🧹 Agent: Curator
* [x] **Archivierung:** Veraltete Docs (Cloudflare, GitHub-Workflows, alte Roadmaps) nach `docs/_archive/` verschoben.
* [x] **Konsolidierung:** `Zora_System_Architektur.md` als zentrale Infrastruktur-Doku etablieren.
* [x] **Struktur:** `docs/` Ordner aufräumen (unnötige Root-Files in Unterordner).
* [x] **Index:** `README.md` im Root als Einstiegspunkt aktualisieren.
*Status: Fachliche Grundlage vorhanden.*
### PHASE 2: Infrastructure Hardening ✅ ABGESCHLOSSEN
*Ziel: Stabilisierung der neuen Self-Hosted Umgebung.*
* [x] **DDD-Analyse:** 6 Bounded Contexts (SCS-Architektur) definiert.
* [x] **Terminologie:** `Veranstaltung``Turnier` gemäß ÖTO § 2 Abs. 1 festgelegt.
* [x] **Ubiquitous Language:** Offizielle Domänen-Terminologie erstellt.
#### 🐧 Agent: DevOps Engineer
### PHASE 2: Infrastruktur & ZNS-Importer (Prototyp) ✅
* [x] **Keycloak Fix:** Verbindungsprobleme innerhalb des Docker-Netzwerks behoben (Alias `auth.mo-code.at`).
* [x] **Backup Strategy:** Automatisierte Backups für Gitea & Datenbanken auf Zora (`config/scripts/backup.sh`).
* [x] **Monitoring:** Prometheus/Grafana Dashboard für Zora finalisiert (`dc-ops.yaml`).
* [x] **Deployment:** Git-basiertes Deployment-Skript (`config/scripts/deploy.sh`) erstellt.
*Status: Grundgerüst steht, Performance-Probleme bekannt.*
### PHASE 3: Domain-Design & Ubiquitous Language ✅ ABGESCHLOSSEN
*Ziel: Fachliche Grundlage für die Implementierung schaffen.*
#### 🏗️ Agent: Lead Architect
* [x] **DDD-Analyse:** 6 Bounded Contexts (SCS-Architektur) definiert und priorisiert.
* [x] **Terminologie:** `Veranstaltung``Turnier` gemäß ÖTO § 2 Abs. 1 festgelegt (ADR).
* [x] **Design-Baseline:** Vision_03 (Figma) als offizieller Design-Baseline festgelegt.
* [x] **Technologie:** Desktop-First-Strategie mit KMP/Compose Desktop beschlossen.
#### 📜 Agent: ÖTO/FEI Rulebook Expert
* [x] **Ubiquitous Language:** Offizielle Domänen-Terminologie mit ÖTO-Referenzen erstellt.
* [x] **Abteilungs-Schwellenwerte:** Alle Trennungs-Schwellenwerte (§ 39 + spartenspezifisch) dokumentiert.
`docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md`
#### 👷 Agent: Backend Developer
* [x] **Enums:** `SparteE`, `TurnierkategorieE`, `VeranstaltungsTypE`, `LizenzKlasseE`, `NennungsStatusE`,
`StartwunschE` ÖTO-konform.
* [x] **Domain-Modelle:** `DomReiter` (actor-context), `DomNennung`, `DomNennungsTransfer` (registration-context).
* [x] **Modul:** `entries-domain` als KMP-Modul aufgesetzt und in `settings.gradle.kts` registriert.
* [x] **ZNS-Parser:** Grundsätzliche Fähigkeit zum Parsen von ZNS-Daten (Reiter, Pferde, Vereine, Funktionäre).
* [x] **UI-Gerüst:** Navigation und Dialog-Hüllen in Compose Desktop vorhanden.
* [x] **Plan-B:** Rudimentärer Mail-Service für Nennungs-Versand (funktional).
---
## 2. Aktuelle Phase
## 2. Der neue Weg: Fachliche Realität (Roadmap 2026)
### PHASE 4: MVP-Implementierung ✅ ABGESCHLOSSEN
Fokus: Physische Implementierung der Turnier-Hierarchie und technisches Onboarding.
*Ziel: Lauffähiger MVP für `registration-context` und `actor-context` (P1-Contexts).*
### MEILENSTEIN 0: Technische Geräte-Initialisierung (Prio 1) 🚧 IN ARBEIT (STABILISIERUNG)
#### 🧐 Agent: QA Specialist
*Ziel: Ein stabiles, offline-fähiges technisches Fundament für die Desktop-App.*
* [x] **Actor Context Stabilization:** Funktionär-Datenmodell (Richter/Parcoursbauer) auf professionelle Master-Daten-Referenzierung umgestellt und mit Reiter-Daten verknüpft (Import-Idempotenz via `satzNummer`). Redundante Kontakt-/Adressdaten entfernt. Alle ZNS-Import-Tests (9/9) stabilisiert und verwaiste Parser-Reste entfernt.
* [x] **Masterdata Standardization:** Bereinigung und Standardisierung der Masterdaten-Tabellen (Mehrzahl-Konvention: `bundeslaender`, `funktionaers_qualifikationen`, `reit_lizenzen`, `turnier_klassen`).
* [x] **ÖTO Data Seeding:** Umfassende Seeder für Funktionärs-Qualifikationen, Turnierklassen und Turnier-Sparten gemäß ÖTO implementiert. Einführung der Tabelle `turnier_sparten`.
* [x] **ZNS-Import Optimization:** Automatische Verknüpfung von Funktionären mit Reitern (Reihenfolge: VEREIN -> LIZENZ -> PFERDE -> RICHT). `ZnsImportService` für sequentielle Imports in Tests gehärtet.
* [x] **Service Stability:** Port-Konflikt des `masterdata-service` (Spring Management Port 8081 vs. Gateway) durch Umzug auf Port 8086 und explizite Bind-Adressen (Spring: 127.0.0.1, Ktor: 0.0.0.0) dauerhaft gelöst.
* [x] **Documentation:** `CHANGELOG.md` aktualisiert und Port-Konfiguration in `application.yml` dokumentiert.
→ Note: `IdempotencyApiIntegrationTest` bleibt vorerst @Disabled, da das Hochfahren des Spring-Contexts in der
Testumgebung blockiert (unabhängig vom Plugin).
→ Task: Integration-Test Umgebung (Port-Binding/Server-Lifecycle) für `masterdata-service` untersuchen.
* [x] **App-Icons (PNG/ICO):** Implementiert (Fix für Build-Fehler).
* [x] **Docker-Fix:** "services must be a mapping" behoben (dc-gui.yaml).
* [x] **Chat-Funktion (Desktop):** MVP implementiert (Navigation & UI).
* [x] **Geführte Discovery ("Zero-Config"):** Master-Namen statt IPs, "Wait-State" für Clients.
* [x] **Native FileDialogs:** Stabile Pfad-Auswahl für Plan-USB auf allen Systemen.
* [x] **Handshake-Feedback:** Visuelle Signalisierung des Verbindungsstatus (Grün/Rot).
* [x] **Client-Konfiguration:** Master kann nun Clients in der UI hinzufügen und bearbeiten.
* [x] **Master-UX:** Konfiguration beim Start nicht mehr zwangsgesperrt.
* [x] **Cross-Packaging (Conveyor):** Windows-Build auf Linux-CI ermöglicht (x64-Abhängigkeit identifiziert).
* [ ] **PoC Verifikation:** 🔴 **BLOCKIERT** (Log 483: ARM64-Runner inkompatibel mit Conveyor-Binary; Workflow auf
manuell gesetzt).
#### 🏗️ Agent: Lead Architect
### MEILENSTEIN 1: Die Basis-Hierarchie (Prio 1) ⚪ GEPLANT
* [x] **ADRs vervollständigen:** Bounded Context Mapping und Context Map dokumentieren.
`docs/01_Architecture/adr/0014-bounded-context-mapping-de.md`
`docs/01_Architecture/adr/0015-context-map-de.md`
* [x] **API-Design:** Schnittstellen zwischen den Contexts definieren (Anti-Corruption Layer).
`docs/01_Architecture/adr/0016-api-design-acl-de.md`
* [x] **ÖTO-Validation-Seeds:** Seed-Daten für Lizenz-Matrix und Altersklassen finalisiert (V008).
*Ziel: Veranstaltung -> Turnier -> Bewerb/Abteilung physisch anlegen und speichern.*
#### 👷 Agent: Backend Developer
* [ ] **Persistenz-Layer:** Implementierung der lokalen Speicherung für alle Ebenen der Hierarchie.
* [ ] **Turnier-Wizard (Echt):** Umstellung des Wizards von `println`-Dummies auf echte Datenbank-Transaktionen.
* [ ] **Validierung:** Sicherstellen, dass Pflichtfelder (Turniernummer, Sparte) korrekt erfasst werden.
* [x] **ZNS-Importer:** Support für Richter-Import (RICHT01.DAT) und Reiter-Refactoring (LIZENZ01.DAT) vervollständigt. ✓
* [x] **Masterdata:** Qualifikations-System und Personen-Referenzen (Vereine, Bundesländer, Nationen) stabilisiert (Consul & MasterdataSeeder). ✓
* [x] **Infrastruktur:** Service-Discovery (Consul) für alle Microservices (incl. Masterdata) aktiviert.
* [x] **Infrastruktur:** Bean-Definitionen und Dependency-Injection im `zns-import-service` bereinigt.
* [x] **Database:** Initialisierung der Funktionärs-Tabellen stabilisiert (PSQLException Fix).
* [x] **`actor-context`:** Domain-Modelle für `Pferd`, `Funktionaer`, `Verein` implementiert.
* [x] **`registration-context`:** `DomBewerb`, `DomAbteilung`, `DomStartliste` implementiert.
* [x] **`event-management-context`:** `DomVeranstaltung`, `DomTurnier`, `DomAusschreibung` implementiert.
* [x] **Persistenz:** Repository-Interfaces und erste DB-Migrationen (Flyway/Liquibase).
* [x] **API:** REST-Endpunkte für Nennungs-Workflow (Kern-Use-Cases).
* [x] **Infrastruktur-Stabilisierung:** Kompilierfehler in `masterdata-infrastructure` behoben.
* [x] **Identity-Schnittstellen:** Endpunkte für ZNS-Linking über `identity-service` bereitgestellt.
### MEILENSTEIN 2: ZNS-Performance & Daten-Qualität (Prio 2)
#### 🎨 Agent: Frontend Expert
*Ziel: Import der ZNS.zip in < 2 Minuten statt 30+ Minuten.*
* [x] **KMP/Compose Desktop:** Projektstruktur aufgesetzt (`frontend/shells/meldestelle-desktop`).
* [x] **Navigation:** Sidebar-Navigation gemäß Vision_03 implementiert (Veranstaltungen, Reiter, Pferde, Funktionäre,
Meisterschaften, Cups).
* [x] **Nennungs-Screen:** `TurnierDetailScreen` integriert `NennungsMaske` aus `nennung-feature` (Bewerbe-Tab ⭐).
* [ ] **Batch-Processing:** Umstellung des `ZnsImportService` auf Batch-Inserts (JDBC/Exposed).
* [ ] **Fortschritts-Anzeige:** Reale Rückmeldung über den Import-Status in der UI.
#### 📜 Agent: ÖTO/FEI Rulebook Expert
### MEILENSTEIN 3: Richtverfahren & Bewerbs-Konfiguration (Prio 3)
* [x] **Voltigieren (CVN):** Abteilungs-Trennungsregeln aus B-Teil § 400 ff. ausgewertet (offene Frage #3 teilweise
geklärt).
→ B IV enthält keine eigenen Regeln verweist auf OEPS-Reglement CVN. § 39 Abs. 1 gilt nicht für CVN. § 39 Abs.
2 (> 80 Starter) gilt als Fallback.
`docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md` (Abschnitt 2.6)
* [x] **Fahren (CAN):** Starter-Schwellenwerte jenseits der Reitertreffen-Regel geklärt (offene Frage #4 teilweise
geklärt).
→ B VII enthält keine eigenen Regeln verweist auf OEPS-Reglement „Turnierordnung für Gespanne". § 39 Abs. 1 gilt
nicht für CAN. § 39 Abs. 2 (> 80 Starter) gilt als Fallback. Einzige gesicherte Lizenzregel: § 850 Abs. 9 (F1+ bei
Fahrertreffen).
`docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md` (Abschnitt 2.5)
* [x] **Warn-Logik:** Spezifikation der `competition-context` Warn-Logik für Abteilungs-Schwellenwerte.
`docs/03_Domain/02_Reference/OETO_Regelwerk/Warn-Logik-Spezifikation-competition-context.md`
*Ziel: Bewerbe mit fachlich korrekten Regeln (ÖTO) anlegen.*
#### 🧹 Agent: Curator & Lead Architect (ZNS-Importer)
* [x] **ZNS-Importer (MVP) Phase 1 & 2:** `core:zns-parser` (KMP), `ZnsLegacyParsers` (alle 4 Dateitypen, CP850),
`ZnsImportService` (Orchestrator, ZIP in-memory, Upsert), Unit-Tests grün.
→ Detaillierte Planung: `docs/01_Architecture/Roadmap_ZNS_Importer.md`
* [x] Backend-Infrastruktur & CP850 Parser (Phase 1 Parser/Modul)
* [x] Domain-Mapping & Upsert in DB (Phase 2)
* [x] REST-API & Job-Management (Phase 1 Controller/Job-Registry)
* [x] Frontend-Integration mit File-Picker & Status-Polling (Phase 3)
* [ ] **Richtverfahren-Engine:** Validierungslogik für verschiedene Richtverfahren (Fehler/Zeit, Wertnoten).
* [ ] **Abteilungs-Logik:** Automatische Teilungsvorschläge gemäß ÖTO § 39.
---
## 3. Aktuelle Phase
## 3. Zukünftige Module (Status: Geplant / Vision)
### PHASE 5: P2-Contexts & Integration ✅ ABGESCHLOSSEN
*Diese Module existieren derzeit nur als Konzepte oder leere Hüllen.*
*Ziel: `competition-context` und `event-management-context` implementieren.*
* [x] **`competition-context`:** Bewerbe, Startlisten, Ergebnisse, Abteilungs-Warn-Logik.
* [x] **`event-management-context`:** Veranstaltungs- und Turnier-Verwaltung, Ausschreibungs-Generator.
* [x] **ZNS-Integration:** Schnittstelle zum Zentralen Nennungs-System (A-Satz / B-Satz).
* [x] **Offline-Sync:** Offline-First-Strategie für Desktop-App implementieren.
### PHASE 6: P3-Contexts & Billing ✅ ABGESCHLOSSEN
*Ziel: `billing-context` und `identity-context` implementieren.*
* [x] **`billing-context`:** Gebührenberechnung, Kassa, Abrechnung.
* [x] **`identity-context`:** Rollen-Modell (TBA, Veranstalter, Richter etc.) mit Keycloak.
* [x] **Reporting:** Startlisten- und Ergebnislisten-Druck (PDF).
* **Nennungs-Management:** Erfassung von Nennungen gegen den ZNS-Datenbestand.
* **Zeitplan:** Dynamische Zeitplanung (Drag & Drop).
* **Ergebnisse:** Erfassung von Wertungen und Platzierungsberechnung.
* **Abrechnung:** Vollständiger `billing-context`.
---
## 4. Geplante Phasen
## 4. Archiv der fiktiven Meilensteine (Reality-Check am 28.04.2026)
### PHASE 7: Desktop-Vernetzung & Zentrale Verwaltung ✅ ABGESCHLOSSEN
*Die folgenden Einträge wurden in früheren Sitzungen als "Abgeschlossen" markiert, entsprachen aber nicht dem realen
Code-Stand.*
*Ziel: LAN-Kommunikation Vorbereitung und Etablierung der "Veranstaltung-Verwaltung" als zentrale Schaltstelle.*
<details>
<summary>Frühere (fiktive) Meilensteine anzeigen</summary>
* [x] **Zentrale Verwaltung:** Etablierung der `Veranstaltung-Verwaltung` (Zentrale) als administratives Cockpit.
* [x] **Navigation:** Implementierung eines Back-Stack-Systems für intelligente "Zurück"-Navigation.
* [x] **Domänen-Synchronisation:** Anpassung der Frontend-Stores an die Backend-Masterdata-Modelle (Reiter, Pferde,
Vereine, Funktionäre).
* [x] **ZNS-Integration (Frontend):** ZNS-Importer in die Zentrale integriert; Konzept "Globaler Pool -> Lokale
Synchronisation" gefestigt.
* [x] **Terminologie:** UI-weit Umstellung von "Event" auf "Veranstaltung" (ÖTO-konform).
* [x] **Konzept:** LAN-Discovery (mDNS) und Echtzeit-Sync (WebSockets) entworfen.
* [x] **ADR:** ADR-0020 (Lokale Netzwerk-Kommunikation) erstellt.
### PHASE 4: MVP-Implementierung (Früherer Status: Abgeschlossen)
### PHASE 8: Bewerbe-Management & Startlisten ✅ ABGESCHLOSSEN
* [ ] ZNS-Import Optimierung (Batching) -> In MEILENSTEIN 2 verschoben.
* [ ] `event-management-context` Implementierung -> In MEILENSTEIN 1 verschoben.
*Ziel: Fachliche Tiefe in den Turnieren (Import, Generierung, Zeitberechnung).*
### PHASE 5-13 (Früherer Status: Abgeschlossen/In Arbeit)
* [x] **Konzept/ADR:** LANSync (ADR0022) und OfflineFirst Desktop↔Backend Konzept definiert und verlinkt.
* [x] **Bewerbe-Import:** Implementierung der Merge-Logik (ZNS-XML -> BewerbUiModel). ✓
* [x] **Startlisten-Automatisierung:** Generierung und Zeitberechnung (Pausen, Umbauzeiten). ✓
* [x] **Discovery:** Implementierung des mDNS-Service (JmDNS) für die Geräte-Suche. ✓
* [x] **Transport:** Aufbau der WebSocket-Infrastruktur für P2P-Sync (Ktor WebSockets, SyncManager). ✓
* [x] **Offline-First Desktop↔Backend:** Umsetzung gemäß Konzept „Offline-First Synchronisation (Desktop ↔ Backend)“ → `docs/01_Architecture/konzept-offline-first-desktop-backend-de.md`. ✓
* [x] **Regelwerks-Validierung:** Implementierung des strukturierten Abteilungs-Warnungssystems gemäß ÖTO § 39 inkl. UI-Integration. ✓
* [ ] `competition-context` (Bewerbe, Startlisten) -> Fachlogik fehlt weitgehend.
* [ ] `billing-context` (Abrechnung) -> Nur Infrastruktur-Hülle vorhanden.
* [ ] `results-service` -> Geschäftslogik fehlt.
### PHASE 9: Zeitplan-Optimierung & Protokollierung ✅ ABGESCHLOSSEN
*Ziel: Dynamische Zeitplan-Anpassungen, Protokollierung von Änderungen und Export-Funktionen.*
* [x] **Billing-Service:** Initialisierung, Teilnehmer-Konten & Buchungs-Logik (v1). ✓
* [x] **Entries-Integration:** Automatische Buchung von Nenngeldern bei Nennungs-Abgabe. ✓
* [x] **ZNS-Importer:** Hardening & Integrationstests für Funktionärs-Updates. ✓
* [x] **Konzept:** Fachliches Konzept für Zeitplan-Optimierung (Drag & Drop) erstellt. ✓
* [x] **Konzept:** Status-Automat für Nennungen & Zeitplan-Synchronisation spezifiziert. ✓
* [x] **Frontend-Standardisierung:** `nennung-feature` refactored und in Desktop-Shell integriert. ✓
* [x] **Rulebook-Check:** ÖTO §43 "Parcoursbesichtigung zu Pferd" eingearbeitet. ✓
* [x] **Feature-Migration:** Pferde-, Reiter-, Funktionärs- und Veranstalter-Module vollständig auf KMP umgestellt. ✓
* [x] **Cleanup:** `FRONTEND_CLEANUP_TODO.md` für Migration von `v2` Screens weitestgehend abgeschlossen. ✓
* [x] **Zeitplan:** Dynamische Verschiebung von Bewerben (Drag & Drop im Kalender). ✓
* [x] **Protokoll:** Implementierung eines Event-Logs für manuelle Eingriffe in Startlisten (Audit-Log). ✓
* [x] **Export:** Startlisten-Export für ZNS (XML-B-Satz). ✓
### PHASE 10: Series-Context & Stammdaten ✅ ABGESCHLOSSEN
*Ziel: Stammdaten-Integration (Reiter, Pferde, Funktionäre) und Series-Context (Cups).*
* [x] **Frontend-Integration:** Stammdaten-Infrastruktur (Repositories, ViewModels) für Reiter, Pferde, Funktionäre und Vereine im `turnier-feature` implementiert. ✓
* [x] **Nennungs-Management:** Funktionalisierung des Nennungs-Tabs mit Echt-Datenanbindung und Suche. ✓
* [x] **`series-context`:** Pluggable Berechnungsmodell (Streichresultate, Alles zählt), konfigurierbare Paar-Bindung (Reiter+Pferd vs. Einzelwertung) implementiert. ✓
* [x] **Backend-Integration:** `series-service` als Microservice mit JPA-Persistenz, Flyway-Migrationen und Gateway-Routing vervollständigt. ✓
## 4. Geplante Phasen
### PHASE 11: Ergebniserfassung & Platzierung ✅ ABGESCHLOSSEN
*Ziel: Vollständige Ergebniserfassung und automatisierte Platzierungsberechnung.*
* [x] **Backend (Results):** Implementierung der Geschäftslogik für Wertnoten, Zeitfehler und ÖTO-konforme Platzierungen. ✓
* [x] **Backend-Infrastruktur:** `results-service` in Docker-Compose und API-Gateway integriert; Service-Discovery via Consul aktiviert. ✓
* [x] **Frontend UI:** `ErgebnisEditDialog` zur schnellen Ergebniserfassung und `TurnierErgebnislistenTab` zur Anzeige realer Ergebnisse. ✓
* [x] **PDF-Export:** Generierung von PDF-Ergebnislisten (Bewerbe und Serien). ✓
### PHASE 12: Abrechnung & Billing 🔵 IN ARBEIT
*Ziel: Vollständige Kassa-Funktionalität und Turnier-Abrechnung.*
* [x] **Backend-Infrastruktur:** `billing-service` initialisiert, Docker-Integration und Gateway-Routing (Port 8087) konfiguriert. ✓
* [x] **Frontend-Anbindung:** `BillingRepository` (Ktor) und `BillingViewModel` auf reale API-Kommunikation umgestellt. ✓
* [x] **Buchungs-Logik:** Implementierung von Soll/Haben-Buchungen (Startgebühren, Nenngelder, Boxen). ✓
* [x] **Offene Posten:** Liste aller unbezahlten Beträge pro Teilnehmer/Pferd. ✓
* [x] **Rechnungserstellung:** Generierung von PDF-Rechnungen und Zahlungsbestätigungen. ✓
* [x] **Kassa-Management:** Tagesabschluss, Storno-Logik und verschiedene Zahlungsarten. ✓
</details>
---
## 4. Geplante Phasen
### PHASE 13: Export & ZNS-Rückmeldung
*Ziel: Finalisierung der Turnier-Daten und Rückübermittlung an den OEPS.*
* [x] **Mail-Service Integration:** Online-Nennungen via REST/Mail empfangen und persistieren. ✓ (April 2026)
* [ ] **XML-Export:** Vollständiger B-Satz Export (inkl. Ergebnisse und Platzierungen).
* [ ] **ZNS-Portal:** Upload-Integration in das OEPS-ZNS.
* [ ] **Archivierung:** Langzeit-Archivierung abgeschlossener Turniere.
---
## 5. Wichtige Architektur-Entscheidungen (ADRs)
| # | Entscheidung | Status | Dokument |
|----|--------------------------------------------------------------|--------|------------------------------|
| 1 | Vision_03 = Design-Baseline | ✅ | Session Log 2026-03-24 |
| 2 | Desktop-First mit KMP/Compose Desktop | ✅ | ADR-0009 |
| 3 | `Veranstaltung``Turnier` (ÖTO § 2 Abs. 1) | ✅ | Ubiquitous Language |
| 4 | 6 Bounded Contexts als SCS-Architektur | ✅ | Session Log 2026-03-24 |
| 5 | `series-context` ist Phase 2+ (Architektur vorbereitet) | ✅ | Session Log 2026-03-24 |
| 6 | Cups/Serien benötigen konfigurierbare Reglements | ✅ | Session Log 2026-03-24 |
| 7 | Warn-Logik statt harter Fehler (Override-Event) | ✅ | Abteilungs-Schwellenwerte.md |
| 8 | 6 Bounded Contexts: Mapping & Aggregate Roots | ✅ | ADR-0014 |
| 9 | Context Map: Integration Patterns & ACL-Strategie | ✅ | ADR-0015 |
| 10 | API-Design & ACL: Ports, DTOs, REST-Endpunkte, Domain Events | ✅ | ADR-0016 |
| 11 | Masterdata: Importer-Einbettung als Worker | ✅ | ADR-0017 |
| 12 | Masterdata: Rule-Versionierung (Regulation-as-Data) | ✅ | ADR-0018 |
| 13 | Masterdata: API-Schichten (REST vs. Ingestion) | ✅ | ADR-0019 |
| 14 | Lokale Netzwerk-Kommunikation und Daten-Isolierung | ✅ | ADR-0020 |
| 15 | Masterdata: Observability & Operations | ✅ | masterdata-ops.md, CHANGELOG |
| 16 | Tenant-Resolution: Schema-per-Tenant | ✅ | ADR-0021 |
| 17 | LAN-Sync-Protokoll (Lamport-Uhren, Event-Sourcing Light) | ✅ | ADR-0022 |
---
## 5. Wichtige Referenzen
| Dokument | Pfad |
|---------------------------|----------------------------------------------------------------------------------------------|
| Ubiquitous Language | `docs/03_Domain/01_Glossary/Ubiquitous_Language.md` |
| Abteilungs-Schwellenwerte | `docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md` |
| Warn-Logik-Spezifikation | `docs/03_Domain/02_Reference/OETO_Regelwerk/Warn-Logik-Spezifikation-competition-context.md` |
| Session Log (DDD) | `docs/99_Journal/2026-03-24_Session_Log_DDD_Ubiquitous_Language.md` |
| Infrastruktur | `docs/07_Infrastructure/Zora_System_Architektur.md` |
| Deployment Guide | `docs/07_Infrastructure/Guides/Setup_Git_Deployment_Zora.md` |
| Backup Guide | `docs/07_Infrastructure/Guides/Setup_Backup_Zora.md` |
| CI/CD | `.gitea/workflows/docker-publish.yaml` |
| Agent Playbooks | `docs/04_Agents/Playbooks/` |
| ADR-Verzeichnis | `docs/01_Architecture/adr/` |
| ZNS-Importer Roadmap | `docs/01_Architecture/Roadmap_ZNS_Importer.md` |
| Masterdata Roadmap | `backend/services/masterdata/docs/ROADMAP.md` |
| Masterdata Changelog | `backend/services/masterdata/docs/CHANGELOG.md` |
| Masterdata Operations | `backend/services/masterdata/docs/runbooks/masterdata-ops.md` |
| Zeitplan-Optimierung | `docs/01_Architecture/konzept-zeitplan-optimierung-de.md` |
| Parcoursbesichtigung-Rulebook | `docs/01_Architecture/rulebook-check-parcoursbesichtigung-de.md` |
| Status-Automat-Nennungen | `docs/01_Architecture/status-automat-nennungen-de.md` |
---
## 3. Zukünftige Phase (April 2026)
### PHASE 5: Web-App & Neumarkt-Vorbereitung 🔵 IN ARBEIT (Start 13. April 2026)
*Ziel: Fertigstellung der Web-App für Online-Nennungen und Vorbereitung des Neumarkt-Turniers (24. April).*
#### 🎨 Agent: Frontend Expert
* [x] **Web-App Shell:** Modul `frontend:shells:meldestelle-web` (Compose WasmJS) initialisiert.
* [x] **UI-Komponenten:** `VeranstaltungsCard` und `TurnierCard` für Web implementiert (mit PDF- & Nenn-Button).
* [x] **Workflow:** `NennungWebFormular` Prototyp erstellt (mit simuliertem Mail-Versand).
#### 👷 Agent: Backend Developer
* [x] **Daten-Seeding:** Desktop-Stores mit echten Daten für Neumarkt (April 2026) vorbefüllt.
* [ ] **Mail-Service:** Integration eines E-Mail-Dienstes für eingehende Nennungen.
#### 🧐 Agent: QA Specialist
* [x] **Verifikation:** Desktop-Screens (Veranstalter, Turnier, Bewerbe) mit echten Daten geprüft.
* [ ] **End-to-End Test:** Online-Nennung (Web) -> E-Mail -> Desktop-Verarbeitung.
---
### PHASE 5: Desktop-Zentrale & Synchronisation 🔵 IN ARBEIT
*Ziel: Ein einsatzbereiter Desktop-Client für das Neumarkt-Turnier.*
#### 🎨 Agent: Frontend Expert
* [x] **Onboarding UI:** Implementierung des Onboarding-Screens (Name, Key, Backup, Rolle, Sync, Drucker) mit
validierten Eingaben.
* [x] **Navigation:** Navigations-Rail mit Hover-Tooltips und dedizierten Icons für "Setup" und "Sync".
* [x] **Settings:** Persistente Speicherung der Onboarding-Daten in `settings.json`.
#### 👷 Agent: Backend Developer
* [x] **Device Management:** Domain-Modell (`Device`), Tabelle (`identity_devices`) und Repository zur
Geräteverwaltung implementiert.
* [x] **Security Key Auth:** Implementierung des `DeviceSecurityFilter` zur Authentifizierung via `X-Security-Key`
Header.
* [x] **Onboarding API:** REST-Endpunkte zur Registrierung und Abfrage von Desktop-Instanzen erstellt.
#### 🧹 Agent: Curator
* [x] **Dokumentation:** Erstellung der Architektur-Doku für das Onboarding-Backend.
| Dokument | Pfad |
|-------------------------------|----------------------------------------------------------------------------------------------|
| Ubiquitous Language | `docs/03_Domain/01_Glossary/Ubiquitous_Language.md` |
| Abteilungs-Schwellenwerte | `docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md` |
| Warn-Logik-Spezifikation | `docs/03_Domain/02_Reference/OETO_Regelwerk/Warn-Logik-Spezifikation-competition-context.md` |
| Session Log (DDD) | `docs/99_Journal/2026-03-24_Session_Log_DDD_Ubiquitous_Language.md` |
| Infrastruktur | `docs/07_Infrastructure/Zora_System_Architektur.md` |
| Deployment Guide | `docs/07_Infrastructure/Guides/Setup_Git_Deployment_Zora.md` |
| Backup Guide | `docs/07_Infrastructure/Guides/Setup_Backup_Zora.md` |
| CI/CD | `.gitea/workflows/docker-publish.yaml` |
| Agent Playbooks | `docs/04_Agents/Playbooks/` |
| ADR-Verzeichnis | `docs/01_Architecture/adr/` |
| ADR-0025: Plan-USB | `docs/01_Architecture/adr/0025-plan-usb-offline-integritaet.md` |
| ADR-0026: Lizenzierung | `docs/01_Architecture/adr/0026-offline-lizenzierung-pay-per-event.md` |
| ADR-0027: Discovery | `docs/01_Architecture/adr/0027-netzwerk-discovery-interface-binding.md` |
| Docker-Fix: dc-gui.yaml | `dc-gui.yaml` |
| ZNS-Importer Roadmap | `docs/01_Architecture/Roadmaps/Roadmap_ZNS_Importer.md` |
| Masterdata Roadmap | `backend/services/masterdata/docs/ROADMAP.md` |
| Masterdata Changelog | `backend/services/masterdata/docs/CHANGELOG.md` |
| Masterdata Operations | `backend/services/masterdata/docs/runbooks/masterdata-ops.md` |
| Zeitplan-Optimierung | `docs/01_Architecture/Concepts/konzept-zeitplan-optimierung-de.md` |
| Parcoursbesichtigung-Rulebook | `docs/01_Architecture/Concepts/rulebook-check-parcoursbesichtigung-de.md` |
| Status-Automat-Nennungen | `docs/01_Architecture/Concepts/status-automat-nennungen-de.md` |
@@ -1,358 +0,0 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
date: 2026-03-07
---
# Meldestelle — Tech-Stack Zusammenfassung
> **Zweck:** Vollständige Referenz des eingesetzten Tech-Stacks im Projekt "Meldestelle".
> Dient als Basis für Recherchen zu Self-Hosted AI (Codegenerierung, RAG, Agenten).
> **Stand:** 07. März 2026
---
## 1. Überblick
Das Projekt "Meldestelle" ist eine **Kotlin-first, Cloud-native Microservices-Plattform** für die Verwaltung von Reitsport-Meldungen (FEI / ÖTO). Es kombiniert ein **Kotlin Multiplatform (KMP) Frontend** mit einem **Spring Boot Microservices Backend** auf einer vollständig self-hosted Infrastruktur.
```
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (KMP) │ Backend (Spring Boot / Kotlin JVM) │
│ Kotlin 2.3 / Compose │ Java 25 / Spring Boot 3.5.9 │
│ JS + WASM (geplant) │ Microservices + API-Gateway │
├─────────────────────────────────────────────────────────────────┤
│ Infrastruktur (Self-Hosted auf Zora / Proxmox) │
│ Keycloak · Consul · Valkey · PostgreSQL · Zipkin │
│ Prometheus · Grafana · Caddy · Pangolin-Tunnel │
└─────────────────────────────────────────────────────────────────┘
```
---
## 2. Sprachen & Laufzeiten
| Komponente | Sprache / Runtime | Version |
|:------------------|:-------------------------|:------------|
| Backend | Kotlin (JVM) | 2.3.0 |
| Frontend | Kotlin (KMP / JS) | 2.3.0 |
| JVM | Java (Eclipse Temurin) | 25 (EA) |
| Build | Gradle (Kotlin DSL) | 9.3.1 |
| Plattform | ARM64 (AArch64) | Linux |
---
## 3. Frontend — Kotlin Multiplatform (KMP)
### 3.1 Core-Framework
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| Kotlin Multiplatform | 2.3.0 | Cross-Platform-Basis (JS + WASM) |
| Compose Multiplatform | 1.10.0 | UI-Framework (Deklarativ) |
| Compose Hot Reload | 1.0.0 | Live-Reload im Dev-Modus |
| Koin (DI) | 4.1.1 | Dependency Injection |
| Koin Compose | 4.1.1 | DI-Integration für Compose |
| Ktor Client | 3.4.0 | HTTP-Client (Multiplatform) |
| Kotlin Serialization | 2.3.0 | JSON-Serialisierung |
### 3.2 Persistenz (Offline-First)
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| SQLDelight | 2.2.1 | Cross-Platform SQL-Datenbank |
| SQLite (WASM) | 3.51.1 | SQLite für Browser/WASM |
| SQLite (Native) | 2.6.2 | SQLite für JVM/Desktop |
### 3.3 Build-Targets
| Target | Status | Anmerkung |
|:--------------|:------------|:-----------------------------------|
| KotlinJS | ✅ Aktiv | Primäres Build-Target |
| WASM | ⏳ Geplant | Warten auf Alpha-Version |
| Desktop (JVM) | ⚙️ Möglich | uiDesktop 1.7.0 vorhanden |
### 3.4 Modul-Struktur
```
frontend/
├── core/
│ ├── core-network/ # Ktor HTTP-Client, Auth-Interceptor
│ ├── core-ui/ # Design-System, Tokens, Komponenten
│ ├── core-domain/ # Shared Domain-Models
│ └── core-data/ # Repository-Interfaces
├── features/
│ ├── ping-feature/ # ✅ Blueprint: MVVM + Repository + DI + Tests
│ └── members-feature/ # ⏳ Auskommentiert (nächste Phase)
└── shells/
└── meldestelle-portal/ # Web-App Shell (Caddy-served)
```
---
## 4. Backend — Spring Boot Microservices
### 4.1 Core-Framework
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| Spring Boot | 3.5.9 | Microservices-Framework |
| Spring Cloud | 2025.0.1 | Service Discovery, Config |
| Spring Security (OAuth2) | (Boot) | JWT-Validierung, Resource Server |
| Spring Data JPA | (Boot) | ORM-Layer |
| Spring Data Valkey | 0.2.0 | Cache-Integration (Valkey/Redis) |
| Spring WebFlux | (Boot) | Reaktive API (Gateway) |
| Kotlin Coroutines | 2.3.0 | Async/Non-blocking |
### 4.2 Persistenz
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| PostgreSQL Driver | 42.7.8 | JDBC-Treiber |
| Exposed (JetBrains) | 1.0.0 | Kotlin-native SQL DSL |
| Flyway | 11.19.1 | Datenbank-Migrationen |
| HikariCP | 7.0.2 | Connection Pool |
> **Strategie (ADR-001):** Hybrid — JPA für einfache CRUD-Entities, Exposed für komplexe Queries und Domain-Logik.
### 4.3 Caching & Messaging
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| Lettuce | 6.6.0 | Valkey/Redis-Client (reaktiv) |
| Redisson | 4.0.0 | Distributed Locks, Pub/Sub |
| Caffeine | 3.2.3 | In-Memory Cache (L1) |
| Reactor Kafka | 1.3.23 | Kafka-Client (Phase 3 / Outbox) |
### 4.4 Observability & Tracing
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| Micrometer | 1.16.1 | Metriken (Prometheus-Export) |
| Micrometer Tracing | 1.6.1 | Distributed Tracing |
| Zipkin Reporter | 3.5.1 | Trace-Export zu Zipkin |
| Logback | 1.5.25 | Logging |
### 4.5 Security
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| Keycloak Admin Client | 26.0.7 | Keycloak-API-Integration |
| Spring Security OAuth2 | (Boot) | JWT Resource Server |
| Jackson (Kotlin) | 3.0.3 | JSON-Serialisierung |
### 4.6 API & Dokumentation
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| Springdoc OpenAPI | 3.0.0 | Swagger / OpenAPI 3.1 |
| Jakarta Annotation | 3.0.0 | Jakarta EE Annotations |
### 4.7 Modul-Struktur
```
backend/
├── infrastructure/
│ ├── gateway/ # ✅ API-Gateway (Spring Cloud Gateway)
│ ├── security/ # ✅ Zentrales OAuth2/JWT-Modul (DRY)
│ ├── cache/
│ │ ├── cache-api/ # Interface-Abstraktion
│ │ └── valkey-impl/ # Valkey-Implementierung
│ ├── event-store/
│ │ ├── event-store-api/ # Interface-Abstraktion
│ │ └── valkey-impl/ # Valkey-Implementierung
│ ├── persistence/ # Hybrid JPA + Exposed
│ └── messaging/ # Kafka (Phase 3 / Outbox-Pattern)
└── services/
├── ping/ # ✅ Deployed — Test/Blueprint-Service
├── entries/ # ⚙️ Registriert, noch nicht deployed
├── events/ # 👻 Ghost Service (nicht registriert)
├── horses/ # 👻 Ghost Service (nicht registriert)
├── masterdata/ # 👻 Ghost Service (nicht registriert)
├── registry/ # 👻 Ghost Service (nicht registriert)
├── results/ # 👻 Ghost Service (nicht registriert)
└── scheduling/ # 👻 Ghost Service (nicht registriert)
```
---
## 5. Infrastruktur-Services (Docker)
### 5.1 Laufende Services
| Service | Image / Version | Port(s) | Zweck |
|:---------------|:--------------------------|:--------------|:-----------------------------|
| PostgreSQL | postgres:16-alpine | 5432 | Primäre Datenbank |
| Keycloak | keycloak:26.4 (custom) | 8180, 9000 | IAM / OAuth2 / OIDC |
| Valkey | valkey:8-alpine | 6379 | Cache + Event-Store |
| Consul | consul:1.21 | 8500, 8600 | Service Discovery + Config |
| Zipkin | openzipkin/zipkin:3.5 | 9411 | Distributed Tracing |
| Prometheus | prom/prometheus:v3.4 | 9090 | Metriken-Sammlung |
| Grafana | grafana/grafana:11.6 | 3000 | Dashboards / Visualisierung |
| Caddy | caddy:2.10-alpine | 4000 | Web-App Serving (Frontend) |
| API-Gateway | (custom Spring Boot) | 8081 | Zentraler Eintrittspunkt |
| Ping-Service | (custom Spring Boot) | 8082 | Test/Blueprint-Service |
### 5.2 CI/CD & DevOps
| Tool | Version / Details | Zweck |
|:----------------|:--------------------------|:-----------------------------|
| Gitea | Self-Hosted (CT 101) | Git-Repository + Registry |
| Gitea Actions | (Runner VM 102) | CI/CD-Pipeline |
| Docker Buildx | ARM64 (linux/arm64) | Multi-Arch Image Build |
| Pangolin | Self-Hosted Tunnel | Reverse Proxy / Extern-Zugang|
### 5.3 Netzwerk & Routing
| Subdomain | Intern (Zora) | Zweck |
|:-----------------------|:--------------------|:-------------------------|
| `git.mo-code.at` | `10.0.0.22:3000` | Gitea |
| `api.mo-code.at` | `10.0.0.50:8081` | API-Gateway |
| `auth.mo-code.at` | `10.0.0.50:8180` | Keycloak |
| `app.mo-code.at` | `10.0.0.50:4000` | Web-App |
| `photos.mo-code.at` | `10.0.0.24:2283` | Immich (Fotos) |
---
## 6. Code-Qualität & Build-Tools
| Tool | Version | Zweck |
|:------------------|:----------|:-----------------------------------|
| Detekt | 1.23.6 | Kotlin Static Analysis |
| ktlint | 12.1.1 | Kotlin Code Formatter |
| ArchUnit | 1.4.1 | Architektur-Tests (Layer-Regeln) |
| Dokka | 2.1.0 | API-Dokumentation (KDoc) |
| Ben-Manes Versions| 0.51.0 | Dependency-Update-Check |
| KSP | 2.3.4 | Kotlin Symbol Processing |
### Build-Konfiguration
```properties
# gradle.properties
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.workers.max=8
org.gradle.configuration-cache=false # wegen JS-Test Serialisierungsproblem
org.gradle.dependency.verification=strict
```
---
## 7. Test-Stack
| Bibliothek | Version | Zweck |
|:------------------------------|:----------|:-----------------------------------|
| JUnit Jupiter | 5.11.3 | Unit-Tests |
| JUnit Platform | 1.11.3 | Test-Runner |
| MockK | 1.14.7 | Kotlin Mocking |
| AssertJ | 3.27.7 | Fluent Assertions |
| Testcontainers | 2.0.3 | Integration-Tests (Docker) |
| Testcontainers Keycloak | 4.0.1 | Keycloak-Integration-Tests |
| Testcontainers PostgreSQL | 1.21.4 | DB-Integration-Tests |
| Testcontainers Kafka | 1.21.4 | Kafka-Integration-Tests |
| ArchUnit | 1.4.1 | Architektur-Compliance-Tests |
---
## 8. Architektur-Prinzipien (ADRs)
| ADR | Entscheidung | Status |
|:------|:--------------------------------------------------|:---------|
| 0001 | Modulare Architektur (DDD, Clean Architecture) | ✅ Aktiv |
| 0003 | Microservices-Architektur | ✅ Aktiv |
| 0004 | Event-Driven Communication (Kafka Phase 3) | ✅ Aktiv |
| 001 | Backend-Infrastruktur: Hybrid JPA+Exposed, Valkey | ✅ Aktiv |
| 0013 | Tech-Stack-Stabilisierung 2026 (Versionen) | ✅ Aktiv |
**Kern-Prinzipien:**
- **Offline-First:** SQLDelight als Cross-Platform-DB, Sync-Mechanismus
- **Docs-as-Code:** `/docs` als Single Source of Truth
- **DRY-Infrastruktur:** Shared Security/Cache/EventStore-Module
- **ARM64-Native:** Alle Images für `linux/arm64` gebaut
---
## 9. Relevanz für Self-Hosted AI
### 9.1 Welche AI-Aufgaben entstehen im Projekt?
| Aufgabe | Häufigkeit | Komplexität |
|:---------------------------------|:-----------|:------------|
| Kotlin/Spring Boot Code-Completion| Täglich | Mittel |
| Compose Multiplatform UI-Code | Täglich | Mittel |
| SQL / Exposed DSL Queries | Häufig | Mittel |
| Gradle Kotlin DSL Build-Skripte | Gelegentlich| Niedrig |
| Docker / YAML Konfigurationen | Gelegentlich| Niedrig |
| Architektur-Entscheidungen (ADR) | Selten | Hoch |
| Fachlogik FEI/ÖTO Regelwerk | Selten | Sehr hoch |
### 9.2 Anforderungen an das AI-Modell
```
MUSS:
✅ Kotlin (JVM + Multiplatform) — sehr gute Unterstützung
✅ Spring Boot / Spring Cloud — sehr gute Unterstützung
✅ Compose Multiplatform — gute Unterstützung (neuere Modelle)
✅ SQL / PostgreSQL — sehr gute Unterstützung
✅ Docker / YAML — sehr gute Unterstützung
✅ Deutsch (Fachsprache Reitsport) — für RAG-Dokumente
NICE-TO-HAVE:
⭐ Gradle Kotlin DSL
⭐ Keycloak / OAuth2 / OIDC
⭐ Microservices-Architektur-Patterns
```
### 9.3 Empfohlene Modelle für diesen Stack
| Modell | Größe | Stärke | RAM-Bedarf |
|:------------------------|:-------|:------------------------------------|:-----------|
| `qwen2.5-coder:14b` | 14B | Code (Kotlin, Java, SQL) — Top | ~10 GB |
| `deepseek-coder-v2:16b` | 16B | Code-Completion, Refactoring | ~12 GB |
| `llama3.1:8b` | 8B | Allgemein + Deutsch, schnell | ~6 GB |
| `qwen2.5:32b` | 32B | Architektur, Planung, Fachlogik | ~22 GB |
| `codellama:13b` | 13B | Code-Completion (IntelliJ-Plugin) | ~9 GB |
> **Empfehlung für Zora (64 GB RAM):**
> - **Primär:** `qwen2.5-coder:14b` — bester Kotlin/Spring-Support
> - **Sekundär:** `qwen2.5:32b` — für Architektur und Fachlogik
> - **IntelliJ-Integration:** `codellama:13b` oder `qwen2.5-coder:14b`
### 9.4 RAG-Dokumente (Priorität)
Folgende Projekt-Dokumente sind besonders wertvoll als RAG-Kontext:
```
Hohe Priorität:
├── docs/01_Architecture/MASTER_ROADMAP_2026_Q1.md
├── docs/01_Architecture/adr/ (alle ADRs)
├── docs/04_Agents/Playbooks/ (Agenten-Rollen)
├── gradle/libs.versions.toml (Versions-SSoT)
└── docs/07_Infrastructure/ (Infrastruktur-Referenz)
Mittlere Priorität:
├── docs/05_Backend/ (Backend-Guides)
├── docs/06_Frontend/ (Frontend-Guides)
└── docs/03_Domain/ (Fachlogik-Konzepte)
```
---
## 10. Zusammenfassung
```
TECH-STACK KOMPLEXITÄT
──────────────────────────────────────────────────────
Sprachen: Kotlin (JVM + KMP/JS)
Build: Gradle 9.3.1 + Kotlin DSL + libs.versions.toml
Frontend: Compose Multiplatform 1.10 + SQLDelight 2.2 + Koin 4.1
Backend: Spring Boot 3.5.9 + Spring Cloud 2025.0.1
Persistenz: PostgreSQL 16 + Exposed 1.0 + Flyway 11 + HikariCP 7
Cache: Valkey 8 + Lettuce 6.6 + Caffeine 3.2
Security: Keycloak 26.4 + Spring Security OAuth2 + JWT
Observability: Micrometer 1.16 + Zipkin 3.5 + Prometheus + Grafana 11.6
Service Mesh: Consul 1.21 + Spring Cloud Gateway
Messaging: Kafka (Phase 3) + Reactor Kafka 1.3
CI/CD: Gitea Actions + Docker Buildx (ARM64)
Hosting: Proxmox VE 8.4 + Docker Compose + Pangolin-Tunnel
```
@@ -0,0 +1,107 @@
---
type: Reference
status: ACTIVE
owner: Lead Architect
date: 2026-04-21
---
# WizardDSL & Orchestrator Referenz
## Ziel
Deklarative Beschreibung von WizardFlows als Graph (Steps, Guards, Transitions) mit klaren SideEffects und OfflineDraftUnterstützung.
## KernInterfaces (Skizze)
```kotlin
interface StepId
data class WizardContext(
val origin: AppScreen,
val role: String?,
val isOnline: Boolean,
val stats: MasterdataStats?
)
data class WizardState<S: StepId, A>(
val current: S,
val history: List<S> = emptyList(),
val acc: A,
val errors: List<String> = emptyList()
)
typealias Guard<S, A> = (WizardContext, A) -> Boolean
data class Transition<S: StepId>(val target: S, val guard: Guard<S, *>? = null, val id: String)
interface StepEffects<S: StepId, A> {
suspend fun onEnter(ctx: WizardContext, state: WizardState<S, A>) {}
suspend fun onLeave(ctx: WizardContext, state: WizardState<S, A>) {}
suspend fun onComplete(ctx: WizardContext, state: WizardState<S, A>) {}
}
```
## DSL (Skizze)
```kotlin
class FlowBuilder<S: StepId, A> {
fun step(id: S, block: StepBuilder<S, A>.() -> Unit) { /* … */ }
}
class StepBuilder<S: StepId, A> {
fun onEnter(block: suspend (WizardContext, WizardState<S, A>) -> Unit) { /* … */ }
fun whenGuard(id: String, g: Guard<S, A>, go: S) { /* … */ }
fun otherwise(go: S) { /* … */ }
}
fun <S: StepId, A> flow(start: S, build: FlowBuilder<S, A>.() -> Unit): WizardRuntime<S, A> { /* … */ }
```
## Beispiel EventFlow (Auszug)
```kotlin
sealed interface EventStep: StepId {
data object ZnsCheck: EventStep
data object VeranstalterSelection: EventStep
data object AnsprechpersonMapping: EventStep
data object MetaData: EventStep
data object TurnierAnlage: EventStep
data object Summary: EventStep
}
data class EventAcc(
val veranstalterId: Uuid? = null,
val veranstalterNr: String = "",
val veranstalterName: String = "",
val ansprechpersonSatznr: String = "",
val name: String = "",
val ort: String = "",
val start: LocalDate? = null,
val end: LocalDate? = null,
val turniere: List<TurnierEntry> = emptyList()
)
object EventGuards {
val hasZns: Guard<EventStep, EventAcc> = { ctx, _ -> (ctx.stats?.vereinCount ?: 0) > 0 }
val needsContactPerson: Guard<EventStep, EventAcc> = { _, acc -> acc.veranstalterId == null || acc.veranstalterNr.startsWith("ORG-") }
}
val EventFlow = flow<EventStep, EventAcc>(start = EventStep.ZnsCheck) {
step(EventStep.ZnsCheck) {
onEnter { ctx, _ -> /* prefetch stats */ }
whenGuard("hasZns", EventGuards.hasZns, go = EventStep.VeranstalterSelection)
otherwise(go = EventStep.VeranstalterSelection)
}
step(EventStep.VeranstalterSelection) {
whenGuard("needsContact", EventGuards.needsContactPerson, go = EventStep.AnsprechpersonMapping)
otherwise(go = EventStep.MetaData)
}
}
```
## DevTools
- Strukturierte Logs je Transition (from, to, guard-id, result, duration).
- GraphExport (DOT/PlantUML) aus der DSL für Doku & Reviews.
## Tests (Empfehlungen)
- Unit: Guards (100% BranchAbdeckung), RuntimeHistory.
- Property: ResumeDeterminismus (Draft → korrekter Step).
- Snapshot: ComposePanels mit Beispielkontexten.
## Verweise
- ADR0025 Orchestrator · ADR0026 ValidationPolicy · ADR0027 DraftDomain & DeltaSync
- Roadmap: `docs/01_Architecture/MASTER_ROADMAP.md#3-initiative-wizard-orchestrator--offline-drafts-q2q3-2026`
@@ -3,22 +3,26 @@
## 🏗️ [Lead Architect] | 14. April 2026
Dieses Dokument beschreibt die Umsetzung der Online-Nennung für das Turnier in Neumarkt (24. April 2026).
Ziel ist ein schlankes Web-Formular, das strukturierte E-Mails an den `Mail-Service` sendet, welcher diese verarbeitet und in der Desktop-Zentrale zur manuellen Übernahme bereitstellt.
Ziel ist ein schlankes Web-Formular, das strukturierte E-Mails an den `Mail-Service` sendet, welcher diese verarbeitet
und in der Desktop-Zentrale zur manuellen Übernahme bereitstellt.
---
### Phase 1: E-Mail-Infrastruktur (Vorbereitung) ✅
* [x] Definition des Adress-Schemas: `meldestelle-[Turnier-Nr]@mo-code.at`.
* [x] Konfiguration der World4You SMTP/IMAP Zugangsdaten.
* [x] Mailpit Integration für lokale Tests (bereits in `dc-ops.yaml`).
### Phase 2: Das Web-Formular (WasmJS Frontend) ✅
* [x] **Basis-UI:** Erstellung des Formulars gemäß Spezifikation (Reiter, Pferd, Lizenz, Bewerbe).
* [x] **Validierung:** Implementierung der Pflichtfeld-Prüfung (Buttonsperre bis alles ok).
* [x] **Mail-Versand:** Integration des API-Calls an das Backend (`mail-service`), um die Nennung zu speichern.
* [x] **DSGVO:** Checkbox und Hinweistext eingebaut.
### Phase 3: Mail-Service (Backend-Verarbeitung) 🏗️
* [x] **Endpoint:** POST-Endpunkt für direkte Nennungen aus dem Web-Formular implementiert.
* [ ] **Polling:** Implementierung des IMAP-Pollers (imap.world4you.com).
* [ ] **Parsing:** Extraktion der Turnier-Nummer aus dem `To`-Header und Mapping auf das Datenbank-Schema (Tenant).
@@ -26,12 +30,14 @@ Ziel ist ein schlankes Web-Formular, das strukturierte E-Mails an den `Mail-Serv
* [x] **Persistence:** Speichern der eingegangenen "Nennungs-Mails" in einer temporären Tabelle.
### Phase 4: Desktop-Zentrale Integration ✅
* [x] **UI-Tab:** Neuer Reiter "Online-Eingang" in der Turnierverwaltung (`TurnierDetailScreen`).
* [x] **Vorschau:** Anzeige der eingegangenen Nennungen mit Details (`OnlineNennungEingangTabContent`).
* [x] **Übernahme:** "Übernehmen"-Button, der Reiter/Pferd in die Nennung vorausfüllt (`NennungViewModel`).
* [ ] **Abschluss:** Manueller "Bestätigen"-Button zum Versenden der finalen Bestätigungsmail.
### Phase 5: End-to-End Test & Deployment 🚀 (Deadline: 21.04.2026)
* [ ] Test-Nennung über Web-Formular (Mailpit).
* [ ] Verifikation der Schema-Zuordnung im Backend.
* [ ] Live-Test mit `online-nennen@mo-code.at`.
@@ -40,6 +46,7 @@ Ziel ist ein schlankes Web-Formular, das strukturierte E-Mails an den `Mail-Serv
---
### Meilensteine
1. **16.04.:** Web-Formular ist funktionsfähig (Senden möglich).
2. **18.04.:** Mail-Service verarbeitet Mails und sendet Auto-Antworten.
3. **20.04.:** Desktop-UI zur Übernahme ist fertig.
@@ -10,7 +10,9 @@ last_update: 2026-03-25
10. **🏗️ [Lead Architect]** | 5. April 2026
**Kontext:**
Der ZNS-Importer wurde für die Verarbeitung der Datei `RICHT01.dat` optimiert. Es wurde klargestellt, dass Richter (SatzID 'X') und Parcoursbauer (SatzID 'Y') gemeinsam in dieser Datei geliefert werden. Eine separate `PARCO01.dat` existiert nicht.
Der ZNS-Importer wurde für die Verarbeitung der Datei `RICHT01.dat` optimiert. Es wurde klargestellt, dass Richter (
SatzID 'X') und Parcoursbauer (SatzID 'Y') gemeinsam in dieser Datei geliefert werden. Eine separate `PARCO01.dat`
existiert nicht.
Um den `registration-context` und `actor-context` unter realistischen Bedingungen testen zu können, benötigen wir echte
Stammdaten (Reiter, Pferde, Vereine, Funktionäre). Diese Daten werden vom ÖPS (Österreichischer Pferdesportverband) in
Form einer `ZNS.zip` bereitgestellt.
@@ -51,7 +53,8 @@ gesteuert wird und die Daten persistent im Backend (`actor-context`) ablegt.
* ZIP-Entpackung in-memory implementiert (`ZnsImportService`).
* [x] **Legacy-Parser (CP850 Fixed-Width):**
* `ZnsLegacyParsers` in `core:zns-parser` (KMP-Modul) implementiert.
* Alle 4 Dateitypen (VEREIN01, LIZENZ01, PFERDE01, RICHT01) bytegenau gemappt. RICHT01 verarbeitet Richter ('X') und Parcoursbauer ('Y'). ✅
* Alle 4 Dateitypen (VEREIN01, LIZENZ01, PFERDE01, RICHT01) bytegenau gemappt. RICHT01 verarbeitet Richter ('X') und
Parcoursbauer ('Y'). ✅
### Phase 2: Domain-Mapping & Persistenz (👷 Backend Developer)

Some files were not shown because too many files have changed in this diff Show More