263 Commits

Author SHA1 Message Date
stefan 1690da3fab chore/ai-guardrails-centralization
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-06-02 13:32:00 +02:00
stefan cb6e0103e7 chore(ai): centralize guardrail scripts under .ai; add resolve_repo_root; add shims for .junie/.nolik; fix gitea contexts in release.yml
Co-authored-by: Junie <junie@jetbrains.com>
2026-06-02 13:27:23 +02:00
stefan 98d0bf0c7b docs: Turnier-Dokumente für 2026 hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-28 13:25:59 +02:00
stefan 0a90b57c2a docs: Ergänzung der Simka Core Server Dokumentation
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-22 13:13:24 +02:00
stefan 0ab62a2752 docs: README-Testbeschreibung aktualisiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-22 11:36:34 +02:00
stefan 6070709bf2 docs: README-Testbeschreibung aktualisiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-22 11:34:17 +02:00
stefan 763c2a9157 Test 2. Versuch zu committen
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-20 10:10:30 +00:00
StefanMo 4f715d10bb refactor: extrahiere ReiterLizenz in core-domain, aktualisiere Abhängigkeiten und behebe Windows-SQLite-Temp-Verzeichnisproblem 2026-05-12 23:33:48 +02:00
stefan 0b830eb675 feat: integriere VeranstaltungRepository und syncModule in Desktop-App
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-12 19:29:51 +02:00
stefan 4c37ecb952 refactor(build): redundante Variable im Gradle-Skript entfernt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-12 15:27:02 +02:00
stefan c25ef17a4a refactor(build): Typen in Gradle-Skript explizit hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-12 15:23:52 +02:00
stefan e5e3b4cfec refactor(build): Plugin-Anwendung in Gradle-Konfiguration vereinfacht
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-12 15:17:47 +02:00
stefan 7d064853e5 feat: optimiere Gradle-Konfiguration für bessere Build-Performance (JVM, Worker, Cache) und dokumentiere Änderungen
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-11 21:39:46 +02:00
stefan 387180c12c chore: entferne index.html
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-11 20:44:35 +02:00
stefan 49393d3eac feat: verbessere Build-Performance durch Standard-Deaktivierung von WASM und aktualisiere Dokumentation
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-05-11 20:44:24 +02:00
stefan e389fe9bce feat(desktop, network): Chat-Funktion hinzugefügt und P2P-Sync verbessert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-11 13:57:53 +02:00
stefan 1a4753cd73 refactor(frontend): HTML-Styles aufgeräumt und Konsistenz verbessert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-09 17:23:17 +02:00
stefan ece3f8bf78 feat(frontend): Grundlegendes HTML-Template für Website hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-09 17:09:51 +02:00
stefan 8d176ce955 refactor(gradle, desktop): Build-Konfiguration bereinigt, Ports optimiert und UI-Logik konsolidiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-09 14:27:22 +02:00
stefan 280db663c7 chore(build, docs): Gradle auf 9.5.0 und Kotlin auf 2.3.21 aktualisiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-09 10:20:02 +02:00
stefan 74ef6424b7 docs(journal): Session-Log zu P2P-Guards, FilePicker-Fixes und Tests hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-08 15:08:20 +02:00
stefan 3959168695 feat(core, network): Port-Guards für Mehrfachstarts von P2P-Server integriert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-08 12:45:13 +02:00
stefan 04a435df1d refactor(core, desktop): Fehlertexte präzisiert und Verzeichnisauswahl für JFileChooser optimiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-08 12:25:58 +02:00
stefan 3aaf5cc59c feat(desktop, network): Fehlerhandling verbessert, Tools-Menü erweitert und mDNS-Discovery optimiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-07 17:18:17 +02:00
stefan a2d94bbc7e refactor(desktop, core): Exception-Handling optimiert und Divider-Komponente angepasst
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-07 15:44:08 +02:00
stefan 95a130c72e feat(desktop, device-initialization): Tools-Menü mit Backup-Option und Reset-Funktion ergänzt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-07 15:42:12 +02:00
stefan 223bf77776 feat(core, network): lokale Chat-Kommunikation und WebSocket-Server hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-05-07 10:58:31 +02:00
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
stefan 4b6a242372 feat: ZNS-Cloud-Sync und manuellen Veranstalter-Button im Wizard hinzugefügt
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 59s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m6s
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 1m13s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 1m51s
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-17 00:31:40 +02:00
stefan a1194adeac feat: unterstütze Einzeldatei-Import, verbessere Fortschrittsanzeige und Logging im ZNS-Import
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 23:45:23 +02:00
stefan 26b3b193ca feat: Health-Check-Ports und Service-URLs konsolidiert, Consul-Best-Practices umgesetzt
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 21:26:58 +02:00
stefan dd76ad6d14 feat: konsistente Consul-Discovery- und Healthcheck-Konfiguration für alle Dienste implementiert
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 19:34:28 +02:00
stefan cfc412878f feat: füge Wasm/JS-Feature-Toggle hinzu, optimiere Gradle-Build-Zeit durch bedingte Task-Deaktivierung
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 19:18:58 +02:00
stefan 0426d4ee9a feat: vereinheitliche Startup-Logs in allen Backend-Services, verbessere Konsistenz und Diagnosemöglichkeiten
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 18:47:35 +02:00
stefan 8f45544fe1 feat: refactor Health-Check-Probes und Connectivity-Logik, stabilisiere Docker-Services
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 18:47:21 +02:00
stefan edd33c34dc docs(journal): Eintrag zu ZNS-First Enrollment und Onboarding-Evolution ergänzt
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: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 16:59:30 +02:00
stefan b8bd2744ac feat(onboarding): Netzwerkrollen und automatisches Discovery im Onboarding hinzugefügt
- Unterstützung für Master- und Client-Rollen mit angepasster Konfiguration.
- Automatische Dienstsuche (Discovery) für Clients implementiert.
- Erweiterte UI für Drucker-, Backup- und Rollenspezifische Einstellungen.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 16:57:20 +02:00
stefan b2e6c2427b refactor(core, veranstaltung): Exception-Handling vereinfacht und Delay-Angabe optimiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 16:16:52 +02:00
stefan 3b7abc55a4 feat(zns-import): DAT-Dateisupport hinzugefügt, Fehlerbehebung und UI-Anpassungen
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 16:12:53 +02:00
stefan 29c35c524b feat(zns-import): Healthchecks optimiert und Konsul-Discovery erweitert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 14:57:34 +02:00
stefan f3d5651ab7 refactor(veranstaltung): VeranstaltungVerwaltungV2 in VeranstaltungVerwaltung umbenannt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 14:31:52 +02:00
stefan ba812e230d feat(veranstaltung): ZNS-Import-Assistent hinzugefügt und Workflow verbessert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 14:05:46 +02:00
stefan cb4f2f855c feat(veranstaltung): Wizard für neue Veranstaltung implementiert und ZNS-Light-Integration hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 13:26:48 +02:00
stefan 10f9e82718 docs(adr): ZNS-First Enrollment Pattern und ZNS-Light Strategie dokumentiert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 12:41:12 +02:00
stefan eb0fac5989 feat(veranstaltung): UI-Refactoring und Validierung für Veranstaltungsverwaltung hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 12:37:49 +02:00
stefan 82a4a13505 feat(onboarding): Explicit Device Enrollment für Master-Geräte hinzugefügt
- Master-Geräte können erwartete Clients inkl. Name & Rolle definieren.
- Neue Rollen (`RICHTER`, `ZEITNEHMER` etc.) integriert.
- Backend- und Frontend-Validierung erweitert, UI-Komponente für Client-Verwaltung.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-16 10:24:52 +02:00
stefan f98a9075ae feat: erweitere Changelog um Onboarding, UX-Verbesserungen und Fehlerbehebungen, aktualisiere Settings-Datei
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 6m3s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 5m59s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 3m27s
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-16 00:45:53 +02:00
stefan 7581f15dfb feat: füge ConnectivityTracker hinzu, erweitere networkModule, aktualisiere DesktopFooterBar mit Gerätestatus und mDNS-Discovery
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-16 00:00:11 +02:00
stefan 67d7b38d79 feat: integriere Live-Daten in NennungsEingangScreen, erweitere NennungRemoteRepository um holeNennungen und markiereAlsGelesen, aktualisiere Port-Konfiguration
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-15 22:59:24 +02:00
stefan 6d631acce6 refactor: entferne toJavaInstant und passe DeviceRepository sowie verwandte Modelle an
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-15 22:28:41 +02:00
stefan 1cefc26be9 feat: Mail-Service-Ports aktualisiert, Consul- und Zipkin-Konfiguration hinzugefügt, neue Felder in BewerbService eingefügt
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
2026-04-15 21:45:20 +02:00
stefan 18e41a90b6 Refactor and rename NennungViewModel to TurnierNennungViewModel, implement online registration workflow with new UI state, ViewModel logic, and API integration, and update dependencies and documentation accordingly. 2026-04-15 20:55:05 +02:00
stefan d026e7f83c Remove unused background import from TurnierOnlineNennungenTab to clean up code. 2026-04-15 20:55:05 +02:00
stefan 26ac3007b9 Implement online registration (Nennung) workflow: add API integration, ViewModel logic, UI updates, backend endpoint, and roadmap adjustments. 2026-04-15 20:55:00 +02:00
stefan a6fcb81594 feat(desktop-onboarding): neue Onboarding-UI implementiert, Backup- und Rollenmanagement hinzugefügt
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Failing after 3m10s
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Successful in 6m37s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Successful in 5m59s
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 (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
- Einbindung eines komplett überarbeiteten Onboarding-Screens mit validierten Eingaben für Gerätename, Sicherheitsschlüssel und Backup-Pfad.
- `SettingsManager` eingeführt zur Speicherung der Onboarding-Daten in `settings.json`.
- Navigation verbessert: Onboarding-Workflow startet, wenn Konfiguration fehlt; neues "Setup"-Icon in der Navigationsleiste hinzugefügt.
- Backend: Geräte-API und `DeviceSecurityFilter` für Authentifizierung per Sicherheitsschlüssel implementiert.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-15 15:49:01 +02:00
stefan a5f5e7a24b feat(mail-service): Port-Konflikt behoben, SMTP-Konfig optimiert und dynamisches Plus-Addressing eingefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-15 13:44:34 +02:00
stefan d0b756694b feat(frontend): Struktur und Kommentare verfeinert, Mail-Service-Konfiguration erweitert
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-15 11:49:31 +02:00
stefan 8c804832d8 feat(billing): add automatic booking for Sportförderbeitrag in compliance with § 16 ÖTO
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-04-15 11:17:35 +02:00
stefan c542094196 feat(online-nennung): integrate online nomination workflow via REST and mail service
- Enabled web-to-backend nominations with `MailController` and REST endpoint (`/api/mail/nennung`).
- Added `NennungRemoteRepository` for frontend API integration using Ktor.
- Linked `WebMainScreen` to backend API for nomination handling and confirmation display.
- Implemented automated confirmation emails for received nominations.
- Updated `MASTER_ROADMAP` to reflect progress on Phase 13 milestones.
- Improved Nennung UI, backend persistence, and QA tracking for Neumarkt tournament.
2026-04-15 10:37:12 +02:00
stefan b4c400efea docs(agents): expand playbooks and refine agent collaboration protocols
- Added `Bounded Context Awareness` section to the Architect playbook, emphasizing adherence to SCS boundaries.
- Refined agent definitions and responsibilities, highlighting domain-driven principles and offline-first focus.
- Introduced strategic project goals in AGENTS.md, clarifying the operational scope of Meldestelle-Biest.
- Enhanced workflow and session protocols for better alignment with the MASTER_ROADMAP and DDD principles.
- Updated role descriptions to emphasize tools, technologies, and accountability.
2026-04-15 09:06:09 +02:00
stefan 03f0c3a90b chore(frontend): remove unused imports and update delay syntax in OnlineNennungFormular and NennungsEingangScreen
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-14 17:01:40 +02:00
stefan da3b57a91d feat(mail-service): introduce persistence and REST support for Nennungen
- Added `NennungRepository` with methods for saving, updating status, and retrieving entries.
- Created `NennungController` to expose REST endpoints for Nennungen.
- Defined `NennungTable` schema with relevant fields and indices.
- Extended `MailPollingService` to parse incoming emails into `NennungEntity` and persist them.
- Updated `build.gradle.kts` with database dependencies and H2 configuration for local dev.
- Refined frontend layout in `OnlineNennungFormular` for improved usability and responsiveness.
2026-04-14 16:50:34 +02:00
stefan 4de44623c2 feat(desktop): add NennungsEingang screen and integrate into navigation
- Introduced `NennungsEingangScreen` for managing online nomination entries.
- Added `NennungsEingang` to `AppScreen` with corresponding route configuration.
- Updated `DesktopMainLayout` to include navigation and UI components for `NennungsEingang`.
- Adjusted `PreviewMain` for screen integration and testing.
2026-04-14 15:27:08 +02:00
stefan adfa97978e feat(mail-service): initialize Mail-Service and integrate online nomination workflow
- Created `MailServiceApplication` with Spring Boot setup.
- Added `MailPollingService` for IMAP polling, `TurnierNr` extraction, and auto-reply functionality.
- Implemented structured email sending for online nominations via `OnlineNennungFormular`.
- Updated frontend with `Erfolgsscreen` for nomination confirmation and fallback handling.
- Added build configurations for Mail-Service and frontend nomination module.
- Documented phase-based roadmap for Online-Nennung and Mail-Service rollout.
2026-04-14 14:59:15 +02:00
stefan 5f87eed86a chore(billing-service): remove unused Duration.hours import from TagesabschlussService 2026-04-14 13:11:46 +02:00
stefan cfe12e4dd0 feat(billing): implement support for Tagesabschluss and Buchung cancellations
- Added `Tagesabschluss` entity and repository to handle daily cash closing logic.
- Introduced cancellation logic for `Buchung`, enabling creation of offsetting entries.
- Extended schema definitions with `TagesabschlussTable` and nullable `storniertBuchungId` in `BuchungTable`.
- Updated services to support `Tagesabschluss` creation and `Buchung` cancellation.
- Implemented tests for `TagesabschlussService` and cancellation functionality.
- Updated documentation to reflect completed roadmap items related to cash management.
2026-04-14 13:10:57 +02:00
stefan 2a1508c6a5 chore(tests): standardize schema usage with constants and resolve IDE warnings
- Replaced hardcoded schema names with constants (`TEST_SCHEMA`, `CONTROL_SCHEMA`) across multiple tests.
- Resolved IDE warnings by removing unused variables (`result`), suppressing `SqlResolve`, and using ASCII-compliant strings.
- Corrected typos in test data (`testdb` -> `test_db`, `Produktions` -> `Production`).
- Improved readability and maintainability in migration and tenant registry tests by introducing companion object constants.
2026-04-14 12:53:33 +02:00
stefan a15cc5971f chore(tests+config): enhance EntriesIsolationIntegrationTest and add missing Spring metadata
- Improved schema isolation logic with constants for tenant schemas and search path management in PostgreSQL.
- Added `withTenant` utility in `TenantContextHolder` to simplify tenant context usage.
- Removed unused imports, variables, and helper functions (`random()` and redundant `NennungRepository` references).
- Included missing `multitenancy.*` configuration keys in `additional-spring-configuration-metadata.json` to address IDE warnings.
2026-04-14 12:39:56 +02:00
stefan f961b6e771 chore(docs+tests): reactivate EntriesIsolationIntegrationTest and resolve tenant data isolation issues
- Fixed schema isolation handling in Exposed by switching table creation to JDBC and explicitly setting `search_path` in PostgreSQL.
- Removed redundant `runBlocking` calls, unused variables, and IDE warnings in the test.
- Added `JwtDecoder` mock in `@TestConfiguration` to prevent application context loading errors.
- Verified that writes in one tenant schema are no longer visible in another.

chore(config): add `application-test.yaml` for better test environment setup

- Configured H2 as an in-memory database for tests.
- Disabled Flyway and Consul to avoid unnecessary dependencies during testing.
2026-04-14 12:25:27 +02:00
stefan 7e3a5aa49e Set static health-check-port to 8086 in application.yml for consistent configuration.
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
2026-04-13 23:30:45 +02:00
stefan bef09791ae Update series-service startup configuration: change default port to 8090, improve Docker stability with prefer-ip-address, and document fixes in curator log. 2026-04-13 23:27:57 +02:00
stefan 2ee9ccf8e9 Add startup fix for scheduling-service: configure application.yml, set service port to 8089, include spring-boot-starter-actuator for health checks, and document changes. 2026-04-13 23:25:35 +02:00
stefan d4509d6c5a Make health-check-port configurable using SERVER_PORT environment variable in application.yml. 2026-04-13 23:19:53 +02:00
stefan 19934e2a96 Add service discovery and health fixes: configure Consul registration, update health-check paths and ports, expand scanBasePackages, and ensure consistent service startup across modules. 2026-04-13 23:18:08 +02:00
stefan 8e40d13954 Add missing spring.datasource configuration to entries-service, comment out conflicting index creation in Flyway migration script, and update log documentation for startup fix. 2026-04-13 22:47:22 +02:00
stefan 43a98ec9ef Set SPRING_APPLICATION_NAME for billing-service in Docker Compose to fix Consul registration issues and add stability log documentation. 2026-04-13 22:29:00 +02:00
stefan 8d0d8898cb Organize imports and clean up exceptions: remove unused imports across multiple modules, replace exception variable usage with wildcard, and improve code readability. 2026-04-13 22:03:46 +02:00
stefan fb1c1ee4ce Remove domain models and services related to Abteilung, AbteilungsRegelService, and Bewerb: cleanup unnecessary entities, validation logic, and tests across backend modules. 2026-04-13 21:58:25 +02:00
stefan 76d7019d30 Add PDF invoice generation: implement backend API, introduce PdfService, update frontend repository and UI with download logic, and mark roadmap task complete. 2026-04-13 17:18:50 +02:00
796 changed files with 32545 additions and 22275 deletions
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/lib/common.sh"
REPO_ROOT="$(resolve_repo_root)"
cd "$REPO_ROOT"
# check-docs-drift.sh
# Zweck: sehr schlanke Drift-Checks gegen die neue Doku-Struktur.
# - Kein Guidelines-System mehr.
# - Single Source of Truth: `docs/`
err=0
has() { grep -q "$2" "$1" || { echo "[DRIFT] '$2' fehlt in $1"; err=1; }; }
miss() { grep -q "$2" "$1" && { echo "[DRIFT] Veralteter Begriff '$2' in $1"; err=1; }; }
# Harte Altlast-Pfade dürfen nicht mehr vorkommen
if git grep -n "docs/00_Domain/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/00_Domain/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/adr/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/adr/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/c4/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/c4/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/how-to/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/how-to/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/reference/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/reference/' in docs/* gefunden"
err=1
fi
# Quelle der Wahrheit: Gateway-Technologie (sollte in Architektur/ADRs/C4 konsistent sein)
has docs/01_Architecture/ARCHITECTURE.md "Spring Cloud Gateway"
has docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Spring Cloud Gateway"
miss docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Ktor"
has docs/01_Architecture/c4/02-container-de.puml "Spring Cloud Gateway"
miss docs/01_Architecture/c4/02-container-de.puml "Ktor"
exit $err
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
# Common helpers for AI guardrail scripts
# Robustly resolve the repository root directory.
# Strategy: prefer Git; fallback to marker search upwards; last resort: current dir.
resolve_repo_root() {
local start
start="${1:-$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)}"
if command -v git >/dev/null 2>&1; then
if git -C "$start" rev-parse --show-toplevel >/dev/null 2>&1; then
git -C "$start" rev-parse --show-toplevel
return 0
fi
fi
local dir
dir="$(cd "$start" && pwd)"
while [ "$dir" != "/" ]; do
if [ -f "$dir/gradlew" ] || [ -f "$dir/settings.gradle.kts" ] || [ -d "$dir/.git" ]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
pwd
}
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/lib/common.sh"
REPO_ROOT="$(resolve_repo_root)"
cd "$REPO_ROOT"
mkdir -p build/diagrams
shopt -s nullglob
for f in docs/architecture/c4/*.puml; do
docker run --rm -v "$PWD":/data plantuml/plantuml -tsvg "/data/$f" -o "/data/build/diagrams"
echo "Rendered build/diagrams/$(basename "${f%.puml}").svg"
done
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/lib/common.sh"
REPO_ROOT="$(resolve_repo_root)"
cd "$REPO_ROOT"
QUICK_MODE=false
while [[ $# -gt 0 ]]; do
case $1 in
--quick)
QUICK_MODE=true
shift
;;
--help|-h)
cat << 'EOF'
Docs Link-Validierung
USAGE:
./.ai/scripts/validate-links.sh [--quick]
BESCHREIBUNG:
Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade.
Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...).
OPTIONEN:
--quick Führt nur eine Teilmenge der Prüfungen durch (aktuell nicht implementiert).
EOF
exit 0
;;
*)
echo "[ERROR] Unbekannter Parameter: $1" >&2
exit 2
;;
esac
done
python3 - <<'PY'
import re
import sys
from pathlib import Path
from urllib.parse import unquote
root = Path.cwd()
docs_dir = root / "docs"
if not docs_dir.is_dir():
print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr)
sys.exit(2)
# Veraltete Pfad-Prüfungen wurden entfernt; Fokus auf Link-Integrität.
FORBIDDEN_SUBSTRINGS = []
md_files = sorted(docs_dir.rglob("*.md"))
link_pattern = re.compile(r"\]\(([^)]+)\)")
errors = 0
def is_external(target: str) -> bool:
t = target.lower()
return t.startswith("http://") or t.startswith("https://") or t.startswith("mailto:")
def strip_fragment_and_query(target: str) -> str:
target = target.split("#", 1)[0]
target = target.split("?", 1)[0]
return target
for f in md_files:
text = f.read_text(encoding="utf-8", errors="replace")
for forbidden in FORBIDDEN_SUBSTRINGS:
if forbidden in text:
print(f"[ERROR] Veralteter Pfad '{forbidden}' in {f}")
errors += 1
for match in link_pattern.finditer(text):
target = match.group(1).strip()
if not target:
continue
if is_external(target):
continue
if target.startswith("#"):
continue
if target.startswith("<") and target.endswith(">"):
target = target[1:-1]
target = unquote(strip_fragment_and_query(target))
if target.startswith("/"):
continue
if ":" in target.split("/", 1)[0]:
# z.B. "vscode:..."
continue
resolved = (f.parent / target).resolve()
try:
resolved.relative_to(root.resolve())
except ValueError:
print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}")
errors += 1
continue
if resolved.is_dir():
if not (resolved / "README.md").is_file():
print(f"[ERROR] Verlinktes Verzeichnis ohne README.md: {f} -> {target}")
errors += 1
continue
if not resolved.exists():
print(f"[ERROR] Broken link: {f} -> {target}")
errors += 1
if errors:
print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler")
sys.exit(1)
print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft")
PY
+1 -1
View File
@@ -193,7 +193,7 @@ secrets/
# ===================================================================
TODO*.md
NOTES*.md
**/.junie/
.junie/
# ===================================================================
# Keep essential files (override exclusions)
+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
+87
View File
@@ -120,6 +120,13 @@ MAILPIT_IMAGE=axllent/mailpit:v1.29
MAILPIT_WEB_PORT=8025:8025
MAILPIT_SMTP_PORT=1025:1025
# --- SPRING MAIL CONFIG (Lokal / Mailpit) ---
# Für lokale Entwicklung mit Mailpit (Docker Compose)
SPRING_MAIL_HOST=mailpit
SPRING_MAIL_PORT=1025
SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=false
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=false
# --- PGADMIN ---
PGADMIN_IMAGE=dpage/pgadmin4:8
PGADMIN_EMAIL=meldestelle@mo-code.at
@@ -149,6 +156,8 @@ 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
@@ -159,6 +168,84 @@ 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_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=<DEIN_WORLD4YOU_PASSWORT>
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true
# --- MASTERDATA-SERVICE ---
MASTERDATA_PORT=8086:8086
MASTERDATA_DEBUG_PORT=5007:5007
MASTERDATA_SERVER_PORT=8086
MASTERDATA_SPRING_PROFILES_ACTIVE=docker
MASTERDATA_DEBUG=true
MASTERDATA_SERVICE_NAME=masterdata-service
MASTERDATA_CONSUL_PREFER_IP=true
# --- 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 ---
WEB_APP_PORT=4000:4000
# URL für API-Zugriffe vom Browser (Public URL via Pangolin)
+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(gitea.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
+40 -49
View File
@@ -1,9 +1,11 @@
name: Build and Publish Docker Images
run-name: Build & Publish by @${{ github.actor }}
run-name: Build & Publish by @${{ gitea.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=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long
type=ref,event=tag
type=raw,value=latest,enable=${{ gitea.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ gitea.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')
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 }}
BUILD_DATE=${{ gitea.event.head_commit.timestamp || 'unknown' }}
VERSION=${{ gitea.sha }}
+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" >> $GITEA_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(gitea.event.head_commit.message, '[planb]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+19 -13
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(gitea.event.head_commit.message, '[planb]') }}
runs-on: ubuntu-latest
outputs:
version: ${{ steps.read-version.outputs.version }}
@@ -62,7 +64,7 @@ jobs:
fi
- name: Git-Tag erstellen & pushen
if: steps.check-tag.outputs.already_tagged == 'false' && github.event.inputs.dry_run != 'true'
if: steps.check-tag.outputs.already_tagged == 'false' && gitea.event.inputs.dry_run != 'true'
run: |
TAG="${{ steps.read-version.outputs.tag }}"
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(gitea.event.head_commit.message, '[planb]') }}
runs-on: ubuntu-latest
needs: tag-release
@@ -84,11 +88,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK 21 (Temurin)
- name: Setup JDK 25 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
java-version: '25'
- name: Gradle cache
uses: actions/cache@v4
@@ -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(gitea.event.head_commit.message, '[planb]') }}
runs-on: windows-latest
needs: tag-release
@@ -130,11 +136,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK 21 (Temurin)
- name: Setup JDK 25 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
java-version: '25'
- name: Gradle cache
uses: actions/cache@v4
@@ -173,11 +179,11 @@ jobs:
steps:
- name: Summary ausgeben
run: |
echo "## 🚀 Release ${{ needs.tag-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Artefakt | Status |" >> $GITHUB_STEP_SUMMARY
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Linux .deb | ${{ needs.package-linux.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Windows .msi | ${{ needs.package-windows.result }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Git-Tag:** \`${{ needs.tag-release.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "## 🚀 Release ${{ needs.tag-release.outputs.version }}" >> $GITEA_STEP_SUMMARY
echo "" >> $GITEA_STEP_SUMMARY
echo "| Artefakt | Status |" >> $GITEA_STEP_SUMMARY
echo "|----------|--------|" >> $GITEA_STEP_SUMMARY
echo "| Linux .deb | ${{ needs.package-linux.result }} |" >> $GITEA_STEP_SUMMARY
echo "| Windows .msi | ${{ needs.package-windows.result }} |" >> $GITEA_STEP_SUMMARY
echo "" >> $GITEA_STEP_SUMMARY
echo "**Git-Tag:** \`${{ needs.tag-release.outputs.tag }}\`" >> $GITEA_STEP_SUMMARY
+48 -60
View File
@@ -1,74 +1,62 @@
# --- General ---
.gradle/
**/build/
**/out/
.kotlin/
kotlin-js-store/
# 🐧 [DevOps Engineer] Optimierte .gitignore für Meldestelle (KMP / Gradle / Docker)
# --- Environments ---
#.env
config/env/.env.local
.env.development.local
.env.test.local
.env.production.local
.env.local
# --- IDEs ---
# IntelliJ
# --- IDE & Editor ---
.idea/
*.iml
*.ipr
*.iws
*.ipr
out/
.vscode/
.history/
.shelf/
# VS Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/snippets
# --- Gradle ---
.gradle/
build/
!**/src/**/build/
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache
bin/
# Fleet
.fleet/
!.fleet/receipt.json
# --- Kotlin / KMP ---
.kotlin/
kotlin-js-store/
.jetbrains/
# --- Dependencies & Build ---
# --- Android (falls relevant) ---
*.ap_
*.apk
*.dex
local.properties
# --- Node / JS (Compose Web / KMP JS) ---
node_modules/
**/node_modules/
package-lock.json
yarn.lock
pnpm-lock.yaml
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm/
# --- OS Files ---
# --- Docker & Infrastructure ---
.docker/
*.log
logs/
.env
!.env.example
.data/
postgres-data/
# --- OS Specific ---
.DS_Store
Thumbs.db
*.swp
*~
.nfs*
desktop.ini
# --- Logs ---
_backup/logs/
**/*.log
*.log.gz
# --- Project Specific ---
docs/temp/
docs/Bin/
docs/_archive/
# --- Languages & Runtimes ---
# Java/Kotlin
*.class
.attach_pid*
# Python
.venv/
venv/
ENV/
*.pyc
__pycache__/
# --- Quality & Documentation ---
build/diagrams/
.eslintcache
.stylelintcache
.phpunit.result.cache
.dataSources/
dataSources.local.xml
/_backup/
.env
# Conveyor
conveyor.rootkey
output/
+4 -40
View File
@@ -1,43 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# check-docs-drift.sh
# Zweck: sehr schlanke Drift-Checks gegen die neue Doku-Struktur.
# - Kein Guidelines-System mehr.
# - Single Source of Truth: `docs/`
err=0
has() { grep -q "$2" "$1" || { echo "[DRIFT] '$2' fehlt in $1"; err=1; }; }
miss() { grep -q "$2" "$1" && { echo "[DRIFT] Veralteter Begriff '$2' in $1"; err=1; }; }
# Harte Altlast-Pfade dürfen nicht mehr vorkommen
if git grep -n "docs/00_Domain/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/00_Domain/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/adr/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/adr/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/c4/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/c4/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/how-to/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/how-to/' in docs/* gefunden"
err=1
fi
if git grep -n "docs/reference/" -- docs >/dev/null 2>&1; then
echo "[DRIFT] Veralteter Pfad 'docs/reference/' in docs/* gefunden"
err=1
fi
# Quelle der Wahrheit: Gateway-Technologie (sollte in Architektur/ADRs/C4 konsistent sein)
has docs/01_Architecture/ARCHITECTURE.md "Spring Cloud Gateway"
has docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Spring Cloud Gateway"
miss docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Ktor"
has docs/01_Architecture/c4/02-container-de.puml "Spring Cloud Gateway"
miss docs/01_Architecture/c4/02-container-de.puml "Ktor"
exit $err
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
exec "$ROOT_DIR/.ai/scripts/check-docs-drift.sh" "$@"
+4 -6
View File
@@ -1,9 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
mkdir -p build/diagrams
shopt -s nullglob
for f in docs/architecture/c4/*.puml; do
docker run --rm -v "$PWD":/data plantuml/plantuml -tsvg "/data/$f" -o "/data/build/diagrams"
echo "Rendered build/diagrams/$(basename "${f%.puml}").svg"
done
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
exec "$ROOT_DIR/.ai/scripts/render-plantuml.sh" "$@"
+4 -133
View File
@@ -1,136 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# validate-links.sh - Link-Validierung für Projektdokumentation (`docs/**`).
# Zweck: Guardrail für die "Docs-as-Code"-Strategie.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
QUICK_MODE=false
while [[ $# -gt 0 ]]; do
case $1 in
--quick)
QUICK_MODE=true
shift
;;
--help|-h)
cat << 'EOF'
Docs Link-Validierung
USAGE:
./.junie/scripts/validate-links.sh [--quick]
BESCHREIBUNG:
Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade.
Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...).
OPTIONEN:
--quick Führt nur eine Teilmenge der Prüfungen durch (aktuell nicht implementiert).
EOF
exit 0
;;
*)
echo "[ERROR] Unbekannter Parameter: $1" >&2
exit 2
;;
esac
done
python3 - <<'PY'
import os
import re
import sys
from pathlib import Path
from urllib.parse import unquote
root = Path.cwd()
docs_dir = root / "docs"
if not docs_dir.is_dir():
print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr)
sys.exit(2)
# Veraltete Pfad-Prüfungen wurden entfernt, da sie zu wartungsintensiv waren.
# Das Skript konzentriert sich nun auf die Validierung der Link-Integrität.
FORBIDDEN_SUBSTRINGS = []
md_files = sorted(docs_dir.rglob("*.md"))
link_pattern = re.compile(r"\]\(([^)]+)\)")
errors = 0
def is_external(target: str) -> bool:
t = target.lower()
return t.startswith("http://") or t.startswith("https://") or t.startswith("mailto:")
def strip_fragment_and_query(target: str) -> str:
# remove fragment and query parts
target = target.split("#", 1)[0]
target = target.split("?", 1)[0]
return target
for f in md_files:
text = f.read_text(encoding="utf-8", errors="replace")
for forbidden in FORBIDDEN_SUBSTRINGS:
if forbidden in text:
print(f"[ERROR] Veralteter Pfad '{forbidden}' in {f}")
errors += 1
for match in link_pattern.finditer(text):
target = match.group(1).strip()
if not target:
continue
if is_external(target):
continue
if target.startswith("#"):
continue
# drop angle brackets <...> used in markdown for urls with spaces
if target.startswith("<") and target.endswith(">"):
target = target[1:-1]
target = unquote(strip_fragment_and_query(target))
# ignore absolute paths in the repo (we treat them as doc-style links; validate only if relative)
if target.startswith("/"):
continue
# ignore non-file targets (e.g. empty or protocol-less anchors)
if ":" in target.split("/", 1)[0]:
# things like "vscode:..." etc.
continue
# treat as file path relative to markdown file
resolved = (f.parent / target).resolve()
# keep validation within repo
try:
resolved.relative_to(root.resolve())
except ValueError:
print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}")
errors += 1
continue
# allow directories if they contain README.md
if resolved.is_dir():
if not (resolved / "README.md").is_file():
print(f"[ERROR] Verlinktes Verzeichnis ohne README.md: {f} -> {target}")
errors += 1
continue
if not resolved.exists():
print(f"[ERROR] Broken link: {f} -> {target}")
errors += 1
if errors:
print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler")
sys.exit(1)
print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft")
PY
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
exec "$ROOT_DIR/.ai/scripts/validate-links.sh" "$@"
+21
View File
@@ -0,0 +1,21 @@
# .aiignore - Verhindert Token-Waste für Nolik
# Abhängigkeiten & Binaries
build/
.gradle/
*.jar
*.deb
*.msi
# Sensible Daten (auch lokal!)
.env
.env.*
config/docker/certs/
*.pem
*.jks
postgres-data/
valkey-data/
# Doku-Builds (Nolik soll die Source-Files in docs/ lesen, nicht die HTML-Exporte)
build/dokka/
docs/Neumarkt2026/*.pdf
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
exec "$ROOT_DIR/.ai/scripts/check-docs-drift.sh" "$@"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
exec "$ROOT_DIR/.ai/scripts/validate-links.sh" "$@"
+37 -24
View File
@@ -1,35 +1,48 @@
# 🤖 Project Agents & Protocol
# 🤖 Projekt Agenten & Protokoll (Meldestelle)
Dieses Dokument definiert die Zusammenarbeit zwischen dem User (Owner) und den KI-Agenten.
Es dient als "System Prompt" für neue Chat-Sessions.
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.
## 1. Protokoll & Badges
Jeder Agent muss seine Antwort mit einem Badge beginnen, um den Kontext zu setzen. Detaillierte Anweisungen finden sich in den jeweiligen Playbooks.
## 🚀 Strategische Ausrichtung (Reality-Reset 28.04.2026)
* **🏗️ [Lead Architect]**: Strategie, Planung, Entscheidungen, Master Roadmap.
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.
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.
* **🏗️ [Lead Architect]**: Hüter der **MASTER_ROADMAP**. Verantwortlich für System-Design, Build-Logik (Gradle), Modulstruktur und ADRs.
* [Playbook](docs/04_Agents/Playbooks/Architect.md)
* **🧹 [Curator]**: Dokumentation, Logs, Reports, Aufräumen.
* [Playbook](docs/04_Agents/Playbooks/Curator.md)
* **👷 [Backend Developer]**: Spring Boot, Kotlin, SQL, API-Design.
* **📜 [Rulebook Expert]**: Wächter über **ÖTO & FEI**. Validiert Business-Rules gegen das offizielle Pferdesport-Regelwerk.
* [Playbook](docs/04_Agents/Playbooks/RulebookExpert.md)
* **👷 [Backend Developer]**: Kotlin & Spring Boot Experte. Fokus auf DDD, Persistenz (Postgres) und **Delta-Sync APIs**.
* [Playbook](docs/04_Agents/Playbooks/BackendDeveloper.md)
* **🎨 [Frontend Expert]**: KMP, Compose, State-Management, Auth.
* **🎨 [Frontend Expert]**: KMP & Compose Desktop Spezialist. Implementiert State-Management und High-Performance UI.
* [Playbook](docs/04_Agents/Playbooks/FrontendExpert.md)
* **🖌️ [UI/UX Designer]**: High-Density Design, Wireframes, Usability.
* **🖌️ [UI/UX Designer]**: "Toolsmith" für High-Density Enterprise-UIs. Fokus auf Tastatur-Bedienbarkeit und Effizienz.
* [Playbook](docs/04_Agents/Playbooks/UIUXDesigner.md)
* **🐧 [DevOps Engineer]**: Docker, CI/CD, Gradle, Security.
* **🐧 [DevOps Engineer]**: Infrastruktur-Automatisierung (Docker, Gitea-Actions). Fokus auf Stabilität und lokale Dev-Umgebung.
* [Playbook](docs/04_Agents/Playbooks/DevOpsEngineer.md)
* **🧐 [QA Specialist]**: Test-Strategie, Edge-Cases.
* **🧐 [QA Specialist]**: Test-Stratege (Shift-Left). Fokus auf Unit-, Integration- und Edge-Case-Tests (Testing Pyramid).
* [Playbook](docs/04_Agents/Playbooks/QASpecialist.md)
* **📜 [ÖTO/FEI Rulebook Expert]**: Regelwerks-Wächter, Validierungs-Spezialist, Compliance.
* [Playbook](docs/04_Agents/Playbooks/RulebookExpert.md)
* **🧹 [Curator]**: Wissens-Management & Dokumentations-Check (ADR, Reference, Journal). Beendet jede Session.
* [Playbook](docs/04_Agents/Playbooks/Curator.md)
## 2. Workflow
1. **Kontext:** Lies immer zuerst die `MASTER_ROADMAP` in `docs/01_Architecture/`.
2. **Fokus:** Bearbeite immer nur EINE Aufgabe zur Zeit.
3. **Doku:** Jede Session endet mit einem Eintrag durch den **Curator**.
4. **Code:** Änderungen am Code werden sofort via Tool ausgeführt, nicht nur vorgeschlagen.
## 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
* **Startup-Mode:** Wir bauen ein echtes Produkt. Code-Qualität und Geschwindigkeit sind gleich wichtig.
* **Docs-as-Code:** Die Dokumentation ist die Single Source of Truth.
* **Offline-First:** Das System muss ohne Internet funktionieren (Sync).
## 🚫 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 -181
View File
@@ -16,191 +16,20 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/).
### [Unreleased]
### 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).
- **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).
### 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.
### Reality-Reset (28.04.2026)
### 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.
- **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 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.
+12 -6
View File
@@ -13,14 +13,14 @@ 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)
Wesentliche Architektur-Referenz: [OfflineFirst Desktop & Backend (Kurzkonzept)](./docs/01_Architecture/Concepts/konzept-offline-first-desktop-backend-de.md)
---
@@ -113,3 +113,9 @@ Beiträge sind willkommen. Bitte lies zunächst die Entwickler-Guides unter [`do
## 📜 Lizenz
Dieses Projekt steht unter der [MIT License](LICENSE).
---
## Test
Das ist der 2. Versuch über Remote zu Committen
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()
@@ -8,7 +8,7 @@ import io.valkey.springframework.data.valkey.core.ValkeyTemplate
import io.valkey.springframework.data.valkey.serializer.StringValkeySerializer
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.testcontainers.containers.GenericContainer
@@ -70,7 +70,7 @@ class ValkeyDistributedCachePerformanceTest {
}
@Test
fun `test cache performance with high concurrent access`() = runTest {
fun `test cache performance with high concurrent access`() = runBlocking {
logger.info { "Starting concurrent access test" }
val numberOfCoroutines = 100
val operationsPerCoroutine = 50
+2 -2
View File
@@ -5,8 +5,8 @@
# ===================================================================
# === CENTRALIZED BUILD ARGUMENTS ===
ARG GRADLE_VERSION=9.4.1
ARG JAVA_VERSION=25
ARG GRADLE_VERSION=9.5.0
ARG JAVA_VERSION=25.0.2
ARG BUILD_DATE
ARG VERSION=1.0.0-SNAPSHOT
@@ -1,25 +1,30 @@
package at.mocode.infrastructure.gateway
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.getBean
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.boot.runApplication
import org.springframework.context.event.EventListener
import org.springframework.core.env.Environment
@SpringBootApplication
class GatewayApplication
class GatewayApplication(private val env: Environment) {
private val log = LoggerFactory.getLogger(GatewayApplication::class.java)
@EventListener(ApplicationReadyEvent::class)
fun onApplicationReady() {
val springPort = env.getProperty("server.port", "8081")
val appName = env.getProperty("spring.application.name", "gateway")
log.info("----------------------------------------------------------")
log.info("Application '{}' is running!", appName)
log.info("Spring Management Port: {}", springPort)
log.info("Profiles: {}", env.activeProfiles.joinToString(", "))
log.info("----------------------------------------------------------")
}
}
fun main(args: Array<String>) {
val context = runApplication<GatewayApplication>(*args)
val logger = LoggerFactory.getLogger(GatewayApplication::class.java)
val env = context.getBean<Environment>()
val port = env.getProperty("server.port") ?: "8081"
logger.info("""
----------------------------------------------------------
Application 'Gateway' is running!
Port: $port
Profiles: ${env.activeProfiles.joinToString(", ").ifEmpty { "default" }}
----------------------------------------------------------
""".trimIndent())
runApplication<GatewayApplication>(*args)
}
@@ -1,6 +1,5 @@
package at.mocode.infrastructure.gateway.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.cloud.gateway.route.RouteLocator
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
import org.springframework.cloud.gateway.route.builder.filters
@@ -9,15 +8,7 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class GatewayConfig(
@Value("\${ping.service.url:http://localhost:8082}") private val pingServiceUrl: String,
@Value("\${masterdata.service.url:http://localhost:8086}") private val masterdataServiceUrl: String,
@Value("\${events.service.url:http://localhost:8085}") private val eventsServiceUrl: String,
@Value("\${zns.import.service.url:http://localhost:8095}") private val znsImportServiceUrl: String,
@Value("\${results.service.url:http://localhost:8088}") private val resultsServiceUrl: String,
@Value("\${series.service.url:http://localhost:8089}") private val seriesServiceUrl: String,
@Value("\${billing.service.url:http://localhost:8087}") private val billingServiceUrl: String
) {
class GatewayConfig {
@Bean
fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
@@ -31,31 +22,31 @@ class GatewayConfig(
it.fallbackUri = java.net.URI.create("forward:/fallback/ping")
}
}
uri(pingServiceUrl)
uri("lb://ping-service")
}
route(id = "masterdata-service") {
path("/api/v1/masterdata/**")
uri(masterdataServiceUrl)
uri("lb://masterdata-service")
}
route(id = "events-service") {
path("/api/v1/events/**")
uri(eventsServiceUrl)
uri("lb://events-service")
}
route(id = "zns-import-service") {
path("/api/v1/import/zns/**", "/api/v1/import/zns")
uri(znsImportServiceUrl)
uri("lb://zns-import-service")
}
route(id = "results-service") {
path("/api/v1/results/**")
uri(resultsServiceUrl)
uri("lb://results-service")
}
route(id = "series-service") {
path("/api/v1/series/**")
uri(seriesServiceUrl)
uri("lb://series-service")
}
route(id = "billing-service") {
path("/api/v1/billing/**")
uri(billingServiceUrl)
uri("lb://billing-service")
}
}
}
@@ -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
@@ -20,14 +20,18 @@ spring:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
port: ${SPRING_CLOUD_CONSUL_PORT:8500}
discovery:
enabled: true
register: true
prefer-ip-address: true
health-check-path: /actuator/health
health-check-interval: 10s
health-check-port: 8081
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
service-name: ${spring.application.name}
# Bei lokalem Start (Gradle) wollen wir nicht die Docker-IP registrieren, sondern localhost oder die Host-IP.
# Aber für den Anfang reicht es, wenn wir Consul finden.
gateway:
httpclient: { }
# Routen sind in GatewayConfig.kt definiert
# Routen sind in GatewayConfig.kt via Service-Discovery (lb://) definiert
# --- SECURITY (OAuth2 Resource Server) ---
security:
@@ -40,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:
@@ -62,9 +87,3 @@ management:
# Lokal: Zipkin auf Port 9411. In Docker via ENV MANAGEMENT_ZIPKIN_TRACING_ENDPOINT überschrieben.
endpoint: ${MANAGEMENT_ZIPKIN_TRACING_ENDPOINT:http://localhost:9411/api/v2/spans}
# --- Custom Service URLs ---
# Default: Localhost (für Entwicklung ohne Docker)
# Im Docker-Compose überschreiben wir das mit dem Service-Namen
ping:
service:
url: ${PING_SERVICE_URL:http://localhost:8082}
@@ -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": [
@@ -25,6 +25,7 @@ dependencies {
// Web (for CORS config)
implementation(libs.spring.web)
implementation(libs.spring.boot.starter.web)
// Testing
testImplementation(projects.platform.platformTesting)
@@ -0,0 +1,49 @@
package at.mocode.infrastructure.security
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.filter.OncePerRequestFilter
/**
* Filter zur Authentifizierung von Desktop-Clients via Security Key.
* Dieser Filter ist für die Offline-First-Synchronisation gedacht.
*
* Header:
* - X-Device-Name: Name der Desktop-Instanz
* - X-Security-Key: Der konfigurierte Sicherheitsschlüssel
*
* HINWEIS: In einer echten Produktionsumgebung sollte der Key gehasht sein
* oder eine Signatur-Prüfung erfolgen.
*/
class DeviceSecurityFilter : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val deviceName = request.getHeader("X-Device-Name")
val securityKey = request.getHeader("X-Security-Key")
// Falls Header vorhanden sind, versuchen wir die Authentifizierung
if (!deviceName.isNullOrBlank() && !securityKey.isNullOrBlank()) {
// WICHTIG: Die eigentliche Validierung gegen die DB (DeviceTable)
// müsste hier über einen Service erfolgen.
// Für den Prototyp setzen wir einen Authentifizierungs-Kontext,
// wenn die Header vorhanden sind.
val auth = UsernamePasswordAuthenticationToken(
deviceName,
null,
listOf(SimpleGrantedAuthority("ROLE_DEVICE"))
)
SecurityContextHolder.getContext().authentication = auth
}
filterChain.doFilter(request, response)
}
}
@@ -6,8 +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
@@ -18,17 +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()
@@ -38,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
}
}
@@ -0,0 +1,12 @@
package at.mocode.zns.importer
/**
* Der Modus des ZNS-Imports.
*
* [FULL] - Alle Dateien (Vereine, Reiter, Pferde, Funktionäre) werden importiert.
* [LIGHT] - Nur Stammdaten (Vereine, Reiter) werden importiert (Performance-Optimiert).
*/
enum class ZnsImportMode {
FULL,
LIGHT
}
@@ -2,6 +2,7 @@
package at.mocode.zns.importer
import at.mocode.core.domain.model.ReiterLizenz
import at.mocode.masterdata.domain.repository.*
import at.mocode.zns.parser.ZnsFunktionaerParser
import at.mocode.zns.parser.ZnsPferdParser
@@ -19,10 +20,10 @@ import java.util.zip.ZipInputStream
* Domänenobjekte über die jeweiligen Repositories (Upsert-Logik).
*
* Die Verarbeitungsreihenfolge ist fix:
* 1. VEREIN01.DAT Verein (via VereinRepository)
* 2. LIZENZ01.DAT Reiter (via ReiterRepository)
* 3. PFERDE01.DAT Pferd (via HorseRepository)
* 4. RICHT01.DAT Funktionaer (via FunktionaerRepository)
* 1. VEREIN01.DAT Verein (via VereinRepository)
* 2. LIZENZ01.DAT Reiter (via ReiterRepository)
* 3. PFERDE01.DAT Pferd (via HorseRepository)
* 4. RICHT01.DAT Funktionär (via FunktionaerRepository)
*
* Dieser Service hat **keine** Spring-Abhängigkeit und kann daher sowohl
* im Backend (REST-Upload) als auch in der Compose Desktop App (Offline-Import)
@@ -47,14 +48,15 @@ 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"
}
/**
* Extrahiert die relevanten Dateien aus dem ZIP-Archiv.
* Optimiert: Nutzt BufferedReader für zeilenweises Einlesen, ohne das gesamte File in den RAM zu laden.
*/
fun extrahiereDateien(zipInputStream: InputStream): Map<String, List<String>> {
val dateien = mutableMapOf<String, List<String>>()
@@ -64,47 +66,168 @@ class ZnsImportService(
while (entry != null) {
val fileName = entry.name.uppercase().substringAfterLast("/")
if (fileName in setOf(FILE_VEREIN, FILE_LIZENZ, FILE_PFERDE, FILE_RICHT)) {
val outputStream = java.io.ByteArrayOutputStream()
val buffer = ByteArray(4096)
var len: Int
while (zip.read(buffer).also { len = it } > 0) {
outputStream.write(buffer, 0, len)
// 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)
// WICHTIG: Wir dürfen den Reader NICHT schließen (use), da sonst der ZipInputStream geschlossen wird!
var line = reader.readLine()
while (line != null) {
if (line.isNotBlank()) {
lines.add(line)
}
line = reader.readLine()
}
val content = outputStream.toString(CP850)
val lines = content.split(Regex("\\r?\\n|\\r")).filter { it.isNotBlank() }
dateien[fileName] = lines
println("[DEBUG_LOG] Datei $fileName extrahiert als $targetKey: ${lines.size} Zeilen")
dateien[targetKey] = lines
}
zip.closeEntry()
entry = zip.nextEntry
}
} finally {
// Wir schließen den ZipInputStream NICHT mit use,
// um den zugrunde liegenden zipInputStream nicht vorzeitig zu schließen.
// Falls der Aufrufer den Stream schließen will, soll er das tun.
// Aber wir müssen sicherstellen, dass wir alle Entries gelesen haben.
} catch (e: Exception) {
println("[DEBUG_LOG] Fehler beim Extrahieren der ZIP (eventuell keine ZIP-Datei?): ${e.message}")
}
return dateien
}
/**
* Importiert ZNS-Daten aus einem Stream. Erkennt automatisch, ob es eine ZIP oder eine DAT ist.
*/
suspend fun importiereStream(
inputStream: InputStream,
fileName: String,
mode: ZnsImportMode = ZnsImportMode.FULL
): ZnsImportResult {
val upperName = fileName.uppercase()
return if (upperName.endsWith(".ZIP")) {
importiereZip(inputStream, mode)
} else if (upperName.endsWith(".DAT")) {
importiereEinzelDatei(inputStream, upperName, mode)
} else {
ZnsImportResult(fehler = listOf("Dateiformat nicht unterstützt: $fileName"))
}
}
private suspend fun importiereEinzelDatei(
inputStream: InputStream,
fileName: String,
mode: ZnsImportMode
): ZnsImportResult {
println("[DEBUG_LOG] Importiere Einzeldatei: $fileName")
val lines = inputStream.bufferedReader(CP850).readLines().filter { it.isNotBlank() }
println("[DEBUG_LOG] Einzeldatei $fileName hat ${lines.size} Zeilen")
val fehler = mutableListOf<String>()
val warnungen = mutableListOf<String>()
var vereineImportiert = 0
var vereineAktualisiert = 0
var reiterImportiert = 0
var reiterAktualisiert = 0
var pferdeImportiert = 0
var pferdeAktualisiert = 0
var richterImportiert = 0
var richterAktualisiert = 0
when {
fileName.startsWith(FILE_VEREIN) -> {
val (n, u) = importiereVereine(lines, fehler)
vereineImportiert = n
vereineAktualisiert = u
}
fileName.startsWith(FILE_LIZENZ) -> {
val (n, u) = importiereReiter(lines, fehler, warnungen)
reiterImportiert = n
reiterAktualisiert = u
}
fileName.startsWith(FILE_PFERDE) -> {
if (mode == ZnsImportMode.FULL) {
val (n, u) = importierePferde(lines, fehler)
pferdeImportiert = n
pferdeAktualisiert = u
}
}
fileName.startsWith(FILE_RICHT) -> {
if (mode == ZnsImportMode.FULL) {
val (n, u) = importiereFunktionaere(lines, fehler, warnungen)
richterImportiert = n
richterAktualisiert = u
}
}
else -> fehler.add("Unbekannte DAT-Datei: $fileName")
}
return ZnsImportResult(
vereineImportiert = vereineImportiert,
vereineAktualisiert = vereineAktualisiert,
reiterImportiert = reiterImportiert,
reiterAktualisiert = reiterAktualisiert,
pferdeImportiert = pferdeImportiert,
pferdeAktualisiert = pferdeAktualisiert,
richterImportiert = richterImportiert,
richterAktualisiert = richterAktualisiert,
fehler = fehler,
warnungen = warnungen
)
}
/**
* Importiert eine ZNS-ZIP-Datei aus einem [InputStream].
*
* @param zipInputStream Der InputStream der ZIP-Datei.
* @param mode Der [ZnsImportMode] (Standard: [ZnsImportMode.FULL]).
* @return [ZnsImportResult] mit Statistiken und eventuellen Fehlern.
*/
suspend fun importiereZip(zipInputStream: InputStream): ZnsImportResult {
suspend fun importiereZip(
zipInputStream: InputStream,
mode: ZnsImportMode = ZnsImportMode.FULL
): ZnsImportResult {
val dateien = extrahiereDateien(zipInputStream)
// println("[DEBUG_LOG] Gefundene Dateien: ${dateien.keys}")
// dateien.forEach { (name, lines) -> println("[DEBUG_LOG] Datei $name hat ${lines.size} Zeilen") }
println("[DEBUG_LOG] Gefundene Dateien im ZIP: ${dateien.keys}")
dateien.forEach { (name, lines) -> println("[DEBUG_LOG] Datei $name hat ${lines.size} Zeilen") }
val fehler = mutableListOf<String>()
val warnungen = mutableListOf<String>()
val (vereineNeu, vereineUpd) = importiereVereine(dateien[FILE_VEREIN] ?: emptyList(), fehler)
val (reiterNeu, reiterUpd) = importiereReiter(dateien[FILE_LIZENZ] ?: emptyList(), fehler, warnungen)
val (pferdeNeu, pferdeUpd) = importierePferde(dateien[FILE_PFERDE] ?: emptyList(), fehler)
val (richterNeu, richterUpd) = importiereFunktionaere(dateien[FILE_RICHT] ?: emptyList(), fehler, warnungen)
var pferdeNeu = 0
var pferdeUpd = 0
var richterNeu = 0
var richterUpd = 0
if (mode == ZnsImportMode.FULL) {
val (pNeu, pUpd) = importierePferde(dateien[FILE_PFERDE] ?: emptyList(), fehler)
pferdeNeu = pNeu
pferdeUpd = pUpd
val (rNeu, rUpd) = importiereFunktionaere(dateien[FILE_RICHT] ?: emptyList(), fehler, warnungen)
richterNeu = rNeu
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,
@@ -132,7 +255,11 @@ class ZnsImportService(
var aktualisiert = 0
zeilen.forEachIndexed { index, zeile ->
runCatching {
val verein = ZnsVereinParser.parse(zeile) ?: return@forEachIndexed
val verein = ZnsVereinParser.parse(zeile)
if (verein == null) {
if (index < 5) println("[DEBUG_LOG] Parser lieferte null für Zeile ${index + 1}: '$zeile'")
return@forEachIndexed
}
val vorhanden = vereinRepository.findByVereinsNummer(verein.vereinsNummer)
if (vorhanden == null) {
vereinRepository.save(verein)
@@ -167,7 +294,11 @@ class ZnsImportService(
var aktualisiert = 0
zeilen.forEachIndexed { index, zeile ->
runCatching {
val parsed = ZnsReiterParser.parse(zeile) ?: return@forEachIndexed
val parsed = ZnsReiterParser.parse(zeile)
if (parsed == null) {
if (index < 5) println("[DEBUG_LOG] Reiter-Parser lieferte null für Zeile ${index + 1}: '$zeile'")
return@forEachIndexed
}
// Relationen auflösen
val verein = parsed.vereinsName?.let { vereinRepository.findByExactName(it) }
@@ -1,3 +1,7 @@
@file:OptIn(ExperimentalWasmDsl::class)
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
@@ -5,22 +9,24 @@ plugins {
kotlin {
jvm()
js(IR) {
wasmJs {
browser()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(libs.kotlinx.datetime)
}
commonMain.dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(libs.kotlinx.datetime)
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
commonTest.dependencies {
implementation(kotlin("test"))
}
jvmTest.dependencies {
implementation(projects.platform.platformTesting)
}
}
}
@@ -36,7 +36,24 @@ data class Buchung constructor(
val typ: BuchungsTyp,
val verwendungszweck: String,
@Serializable(with = InstantSerializer::class)
val gebuchtAm: Instant = Clock.System.now()
val gebuchtAm: Instant = Clock.System.now(),
val storniertBuchungId: Uuid? = null // Referenz auf die ursprüngliche Buchung, falls dies ein Storno ist
)
/**
* Repräsentiert einen Kassa-Tagesabschluss.
*/
@Serializable
data class Tagesabschluss(
val tagesabschlussId: Uuid = Uuid.random(),
val veranstaltungId: Uuid,
val abgeschlossenAm: Instant = Clock.System.now(),
val abgeschlossenVon: String,
val summeBarCent: Long,
val summeKarteCent: Long,
val summeGutschriftCent: Long,
val anzahlBuchungen: Int,
val bemerkungen: String? = null
)
@Serializable
@@ -46,6 +63,7 @@ enum class BuchungsTyp {
NACHNENNGEBUEHR,
STARTGEBUEHR,
BOXENGEBUEHR,
SPORTFOERDERBEITRAG,
ZAHLUNG_BAR,
ZAHLUNG_KARTE,
GUTSCHRIFT,
@@ -3,7 +3,9 @@
package at.mocode.billing.domain.repository
import at.mocode.billing.domain.model.Buchung
import at.mocode.billing.domain.model.Tagesabschluss
import at.mocode.billing.domain.model.TeilnehmerKonto
import kotlin.time.Instant
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -14,6 +16,7 @@ interface TeilnehmerKontoRepository {
fun findByVeranstaltungAndPerson(veranstaltungId: Uuid, personId: Uuid): TeilnehmerKonto?
fun findById(kontoId: Uuid): TeilnehmerKonto?
fun findByVeranstaltung(veranstaltungId: Uuid): List<TeilnehmerKonto>
fun findOffenePosten(veranstaltungId: Uuid): List<TeilnehmerKonto>
fun save(konto: TeilnehmerKonto): TeilnehmerKonto
fun updateSaldo(kontoId: Uuid, saldoCent: Long): Long
}
@@ -23,5 +26,19 @@ interface TeilnehmerKontoRepository {
*/
interface BuchungRepository {
fun findByKonto(kontoId: Uuid): List<Buchung>
fun findById(buchungId: Uuid): Buchung?
fun findByVeranstaltungAndZeitraum(
veranstaltungId: Uuid,
von: Instant,
bis: Instant
): List<Buchung>
fun save(buchung: Buchung): Buchung
}
/**
* Repository für den Zugriff auf Tagesabschlüsse.
*/
interface TagesabschlussRepository {
fun findByVeranstaltung(veranstaltungId: Uuid): List<Tagesabschluss>
fun save(abschluss: Tagesabschluss): Tagesabschluss
}
@@ -5,8 +5,8 @@
# ===================================================================
# === CENTRALIZED BUILD ARGUMENTS ===
ARG GRADLE_VERSION=9.4.1
ARG JAVA_VERSION=25
ARG GRADLE_VERSION=9.5.0
ARG JAVA_VERSION=25.0.2
ARG BUILD_DATE
ARG VERSION=1.0.0-SNAPSHOT
@@ -21,6 +21,7 @@ dependencies {
implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.actuator)
implementation(libs.jackson.module.kotlin)
implementation(libs.openpdf)
implementation(libs.spring.cloud.starter.consul.discovery)
implementation(libs.micrometer.tracing.bridge.brave)
implementation(libs.zipkin.reporter.brave)
@@ -5,11 +5,14 @@ package at.mocode.billing.api.rest
import at.mocode.billing.domain.model.Buchung
import at.mocode.billing.domain.model.BuchungsTyp
import at.mocode.billing.domain.model.TeilnehmerKonto
import at.mocode.billing.service.PdfService
import at.mocode.billing.service.TeilnehmerKontoService
import at.mocode.core.domain.serialization.InstantSerializer
import jakarta.validation.Valid
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import kotlin.time.Instant
@@ -20,7 +23,8 @@ import kotlinx.serialization.Serializable
@RestController
@RequestMapping("/api/billing")
class BillingController(
private val kontoService: TeilnehmerKontoService
private val kontoService: TeilnehmerKontoService,
private val pdfService: PdfService
) {
data class KontoDto(
@@ -106,6 +110,33 @@ class BillingController(
return ResponseEntity.ok(konto.toDto())
}
@GetMapping("/konten/{kontoId}/rechnung", produces = [MediaType.APPLICATION_PDF_VALUE])
fun downloadRechnung(@PathVariable kontoId: String): ResponseEntity<ByteArray> {
val uuid = try { Uuid.parse(kontoId) } catch (_: Exception) { return ResponseEntity.badRequest().build() }
val konto = kontoService.getKontoById(uuid) ?: return ResponseEntity.notFound().build()
val pdf = pdfService.generateRechnung(konto)
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"rechnung_${konto.personName.replace(" ", "_")}.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(pdf)
}
@GetMapping("/veranstaltungen/{veranstaltungId}/offene-posten")
fun getOffenePosten(@PathVariable veranstaltungId: String): ResponseEntity<List<KontoDto>> {
val uuid = try { Uuid.parse(veranstaltungId) } catch (_: Exception) { return ResponseEntity.badRequest().build() }
val konten = kontoService.getOffenePosten(uuid)
return ResponseEntity.ok(konten.map { it.toDto() })
}
@GetMapping("/veranstaltungen/{veranstaltungId}/konten")
fun getKontenFuerVeranstaltung(@PathVariable veranstaltungId: String): ResponseEntity<List<KontoDto>> {
val uuid = try { Uuid.parse(veranstaltungId) } catch (_: Exception) { return ResponseEntity.badRequest().build() }
val konten = kontoService.getKontenFuerVeranstaltung(uuid)
return ResponseEntity.ok(konten.map { it.toDto() })
}
private fun TeilnehmerKonto.toDto() = KontoDto(
kontoId = kontoId.toString(),
veranstaltungId = veranstaltungId.toString(),
@@ -2,14 +2,33 @@
package at.mocode.billing.service
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.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.context.event.EventListener
import org.springframework.core.env.Environment
import kotlin.uuid.ExperimentalUuidApi
@EnableDiscoveryClient
@SpringBootApplication
class BillingServiceApplication
class BillingServiceApplication(private val env: Environment) {
private val log = LoggerFactory.getLogger(BillingServiceApplication::class.java)
@EventListener(ApplicationReadyEvent::class)
fun onApplicationReady() {
val springPort = env.getProperty("server.port", "8087")
val appName = env.getProperty("spring.application.name", "billing-service")
log.info("----------------------------------------------------------")
log.info("Application '{}' is running!", appName)
log.info("Spring Management Port: {}", springPort)
log.info("Profiles: {}", env.activeProfiles.joinToString(", "))
log.info("----------------------------------------------------------")
}
}
fun main(args: Array<String>) {
runApplication<BillingServiceApplication>(*args)
@@ -0,0 +1,92 @@
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.billing.service
import at.mocode.billing.domain.model.TeilnehmerKonto
import at.mocode.billing.domain.repository.BuchungRepository
import com.lowagie.text.*
import com.lowagie.text.pdf.PdfPCell
import com.lowagie.text.pdf.PdfPTable
import com.lowagie.text.pdf.PdfWriter
import org.springframework.stereotype.Service
import java.awt.Color
import java.io.ByteArrayOutputStream
import java.text.NumberFormat
import java.util.*
import kotlin.uuid.ExperimentalUuidApi
@Service
class PdfService(
private val buchungRepository: BuchungRepository
) {
fun generateRechnung(konto: TeilnehmerKonto): ByteArray {
val out = ByteArrayOutputStream()
val document = Document(PageSize.A4)
PdfWriter.getInstance(document, out)
document.open()
// Header
val titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 18f)
val header = Paragraph("Rechnung / Kontoauszug", titleFont)
header.alignment = Element.ALIGN_CENTER
header.spacingAfter = 20f
document.add(header)
// Teilnehmer Info
val infoFont = FontFactory.getFont(FontFactory.HELVETICA, 12f)
document.add(Paragraph("Teilnehmer: ${konto.personName}", infoFont))
document.add(Paragraph("Datum: ${java.time.LocalDate.now()}", infoFont))
document.add(Paragraph("Konto-ID: ${konto.kontoId}", infoFont))
document.add(Paragraph("Veranstaltung: ${konto.veranstaltungId}", infoFont))
document.add(Paragraph(" ", infoFont))
// Tabelle
val table = PdfPTable(4)
table.widthPercentage = 100f
table.setWidths(floatArrayOf(2f, 4f, 2f, 2f))
val headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 11f)
fun addCell(text: String, font: Font = headFont, bgColor: Color? = Color.LIGHT_GRAY) {
val cell = PdfPCell(Phrase(text, font))
if (bgColor != null) cell.backgroundColor = bgColor
cell.setPadding(5f)
table.addCell(cell)
}
addCell("Datum")
addCell("Zweck")
addCell("Typ")
addCell("Betrag")
val buchungen = buchungRepository.findByKonto(konto.kontoId)
val currencyFormat = NumberFormat.getCurrencyInstance(Locale.GERMANY)
val bodyFont = FontFactory.getFont(FontFactory.HELVETICA, 10f)
buchungen.forEach { b ->
addCell(b.gebuchtAm.toString().substring(0, 10), bodyFont, null)
addCell(b.verwendungszweck, bodyFont, null)
addCell(b.typ.name, bodyFont, null)
val betragStr = currencyFormat.format(b.betragCent / 100.0)
addCell(betragStr, bodyFont, null)
}
document.add(table)
// Saldo
val saldoFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 14f)
val saldoPara = Paragraph(" ", saldoFont)
saldoPara.spacingBefore = 20f
document.add(saldoPara)
val saldoText = "Gesamtsaldo: ${currencyFormat.format(konto.saldoCent / 100.0)}"
val finalSaldo = Paragraph(saldoText, saldoFont)
finalSaldo.alignment = Element.ALIGN_RIGHT
document.add(finalSaldo)
document.close()
return out.toByteArray()
}
}
@@ -0,0 +1,67 @@
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.billing.service
import at.mocode.billing.domain.model.BuchungsTyp
import at.mocode.billing.domain.model.Tagesabschluss
import at.mocode.billing.domain.repository.BuchungRepository
import at.mocode.billing.domain.repository.TagesabschlussRepository
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.springframework.stereotype.Service
import kotlin.time.Instant
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@Service
class TagesabschlussService(
private val buchungRepository: BuchungRepository,
private val tagesabschlussRepository: TagesabschlussRepository
) {
/**
* Erstellt einen Tagesabschluss für die angegebene Veranstaltung und den Zeitraum.
* Standardmäßig wird der Zeitraum von "heute 00:00" bis "jetzt" genommen,
* wenn keine Zeiten angegeben sind.
*/
fun erstelleAbschluss(
veranstaltungId: Uuid,
von: Instant,
bis: Instant,
abgeschlossenVon: String,
bemerkungen: String? = null
): Tagesabschluss {
return transaction {
val buchungen = buchungRepository.findByVeranstaltungAndZeitraum(veranstaltungId, von, bis)
val summeBar = buchungen
.filter { it.typ == BuchungsTyp.ZAHLUNG_BAR }
.sumOf { it.betragCent }
val summeKarte = buchungen
.filter { it.typ == BuchungsTyp.ZAHLUNG_KARTE }
.sumOf { it.betragCent }
val summeGutschrift = buchungen
.filter { it.typ == BuchungsTyp.GUTSCHRIFT }
.sumOf { it.betragCent }
val abschluss = Tagesabschluss(
veranstaltungId = veranstaltungId,
abgeschlossenVon = abgeschlossenVon,
summeBarCent = summeBar,
summeKarteCent = summeKarte,
summeGutschriftCent = summeGutschrift,
anzahlBuchungen = buchungen.size,
bemerkungen = bemerkungen
)
tagesabschlussRepository.save(abschluss)
}
}
fun getAbschluesse(veranstaltungId: Uuid): List<Tagesabschluss> {
return transaction {
tagesabschlussRepository.findByVeranstaltung(veranstaltungId)
}
}
}
@@ -59,6 +59,7 @@ class TeilnehmerKontoService(
BuchungsTyp.NENNGEBUEHR,
BuchungsTyp.NACHNENNGEBUEHR,
BuchungsTyp.STARTGEBUEHR,
BuchungsTyp.SPORTFOERDERBEITRAG,
BuchungsTyp.BOXENGEBUEHR -> if (betragCent > 0) -betragCent else betragCent
BuchungsTyp.ZAHLUNG_BAR,
@@ -88,4 +89,42 @@ class TeilnehmerKontoService(
kontoRepository.findByVeranstaltung(veranstaltungId)
}
}
fun getOffenePosten(veranstaltungId: Uuid): List<TeilnehmerKonto> {
return transaction {
kontoRepository.findOffenePosten(veranstaltungId)
}
}
/**
* Storniert eine existierende Buchung durch eine Gegenbuchung.
*/
fun storniereBuchung(buchungId: Uuid, grund: String): TeilnehmerKonto {
return transaction {
val ursprung = buchungRepository.findById(buchungId)
?: throw IllegalArgumentException("Buchung nicht gefunden: $buchungId")
if (ursprung.typ == BuchungsTyp.STORNIERUNG) {
throw IllegalArgumentException("Ein Storno kann nicht erneut storniert werden.")
}
val konto = kontoRepository.findById(ursprung.kontoId)!!
// Gegenbuchung erstellen (Betrag umkehren)
val stornoBuchung = Buchung(
kontoId = ursprung.kontoId,
betragCent = -ursprung.betragCent,
typ = BuchungsTyp.STORNIERUNG,
verwendungszweck = "Storno von ${ursprung.buchungId}: $grund",
storniertBuchungId = ursprung.buchungId
)
buchungRepository.save(stornoBuchung)
val neuerSaldo = konto.saldoCent - ursprung.betragCent
kontoRepository.updateSaldo(konto.kontoId, neuerSaldo)
kontoRepository.findById(konto.kontoId)!!
}
}
}
@@ -1,6 +1,7 @@
package at.mocode.billing.service.config
import at.mocode.billing.service.persistence.BuchungTable
import at.mocode.billing.service.persistence.TagesabschlussTable
import at.mocode.billing.service.persistence.TeilnehmerKontoTable
import jakarta.annotation.PostConstruct
import org.jetbrains.exposed.v1.jdbc.Database
@@ -31,7 +32,8 @@ class BillingDatabaseConfiguration(
transaction {
SchemaUtils.create(
TeilnehmerKontoTable,
BuchungTable
BuchungTable,
TagesabschlussTable
)
}
log.info("Billing database schema initialized successfully")
@@ -38,6 +38,7 @@ object BuchungTable : Table("buchungen") {
val typ = varchar("typ", 50)
val verwendungszweck = varchar("verwendungszweck", 500)
val gebuchtAm = timestamp("gebucht_am").defaultExpression(CurrentTimestamp)
val storniertBuchungId = uuid("storniert_buchung_id").nullable()
override val primaryKey = PrimaryKey(id)
@@ -45,3 +46,24 @@ object BuchungTable : Table("buchungen") {
index("idx_buchung_konto", isUnique = false, kontoId)
}
}
/**
* Exposed-Tabellendefinition für Tagesabschlüsse.
*/
object TagesabschlussTable : Table("tagesabschluesse") {
val id = uuid("tagesabschluss_id")
val veranstaltungId = uuid("veranstaltung_id")
val abgeschlossenAm = timestamp("abgeschlossen_am").defaultExpression(CurrentTimestamp)
val abgeschlossenVon = varchar("abgeschlossen_von", 200)
val summeBarCent = long("summe_bar_cent")
val summeKarteCent = long("summe_karte_cent")
val summeGutschriftCent = long("summe_gutschrift_cent")
val anzahlBuchungen = integer("anzahl_buchungen")
val bemerkungen = text("bemerkungen").nullable()
override val primaryKey = PrimaryKey(id)
init {
index("idx_tagesabschluss_veranstaltung", isUnique = false, veranstaltungId)
}
}
@@ -4,17 +4,18 @@ package at.mocode.billing.service.persistence
import at.mocode.billing.domain.model.Buchung
import at.mocode.billing.domain.model.BuchungsTyp
import at.mocode.billing.domain.model.Tagesabschluss
import at.mocode.billing.domain.model.TeilnehmerKonto
import at.mocode.billing.domain.repository.BuchungRepository
import at.mocode.billing.domain.repository.TagesabschlussRepository
import at.mocode.billing.domain.repository.TeilnehmerKontoRepository
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.*
import org.jetbrains.exposed.v1.datetime.CurrentTimestamp
import org.jetbrains.exposed.v1.jdbc.insert
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.update
import org.springframework.stereotype.Repository
import kotlin.time.Instant
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -44,6 +45,13 @@ class ExposedTeilnehmerKontoRepository : TeilnehmerKontoRepository {
.map { it.toModel() }
}
override fun findOffenePosten(veranstaltungId: Uuid): List<TeilnehmerKonto> {
return TeilnehmerKontoTable
.selectAll()
.where { (TeilnehmerKontoTable.veranstaltungId eq veranstaltungId) and (TeilnehmerKontoTable.saldoCent less 0) }
.map { it.toModel() }
}
override fun save(konto: TeilnehmerKonto): TeilnehmerKonto {
val existing = findById(konto.kontoId)
if (existing == null) {
@@ -95,6 +103,29 @@ class ExposedBuchungRepository : BuchungRepository {
.map { it.toModel() }
}
override fun findById(buchungId: Uuid): Buchung? {
return BuchungTable
.selectAll()
.where { BuchungTable.id eq buchungId }
.singleOrNull()
?.toModel()
}
override fun findByVeranstaltungAndZeitraum(
veranstaltungId: Uuid,
von: Instant,
bis: Instant
): List<Buchung> {
// Da Buchungen über Konten verknüpft sind, müssen wir einen Join machen oder über die Konten der Veranstaltung filtern
return Join(BuchungTable, TeilnehmerKontoTable, JoinType.INNER, BuchungTable.kontoId, TeilnehmerKontoTable.id)
.selectAll()
.where {
(TeilnehmerKontoTable.veranstaltungId eq veranstaltungId) and
(BuchungTable.gebuchtAm.between(von, bis))
}
.map { it.toModel() }
}
override fun save(buchung: Buchung): Buchung {
BuchungTable.insert {
it[id] = buchung.buchungId
@@ -103,6 +134,7 @@ class ExposedBuchungRepository : BuchungRepository {
it[typ] = buchung.typ.name
it[verwendungszweck] = buchung.verwendungszweck
it[gebuchtAm] = buchung.gebuchtAm
it[storniertBuchungId] = buchung.storniertBuchungId
}
return buchung
}
@@ -113,6 +145,45 @@ class ExposedBuchungRepository : BuchungRepository {
betragCent = this[BuchungTable.betragCent],
typ = BuchungsTyp.valueOf(this[BuchungTable.typ]),
verwendungszweck = this[BuchungTable.verwendungszweck],
gebuchtAm = this[BuchungTable.gebuchtAm]
gebuchtAm = this[BuchungTable.gebuchtAm],
storniertBuchungId = this[BuchungTable.storniertBuchungId]
)
}
@Repository
class ExposedTagesabschlussRepository : TagesabschlussRepository {
override fun findByVeranstaltung(veranstaltungId: Uuid): List<Tagesabschluss> {
return TagesabschlussTable
.selectAll()
.where { TagesabschlussTable.veranstaltungId eq veranstaltungId }
.map { it.toModel() }
}
override fun save(abschluss: Tagesabschluss): Tagesabschluss {
TagesabschlussTable.insert {
it[id] = abschluss.tagesabschlussId
it[veranstaltungId] = abschluss.veranstaltungId
it[abgeschlossenAm] = abschluss.abgeschlossenAm
it[abgeschlossenVon] = abschluss.abgeschlossenVon
it[summeBarCent] = abschluss.summeBarCent
it[summeKarteCent] = abschluss.summeKarteCent
it[summeGutschriftCent] = abschluss.summeGutschriftCent
it[anzahlBuchungen] = abschluss.anzahlBuchungen
it[bemerkungen] = abschluss.bemerkungen
}
return abschluss
}
private fun ResultRow.toModel() = Tagesabschluss(
tagesabschlussId = this[TagesabschlussTable.id],
veranstaltungId = this[TagesabschlussTable.veranstaltungId],
abgeschlossenAm = this[TagesabschlussTable.abgeschlossenAm],
abgeschlossenVon = this[TagesabschlussTable.abgeschlossenVon],
summeBarCent = this[TagesabschlussTable.summeBarCent],
summeKarteCent = this[TagesabschlussTable.summeKarteCent],
summeGutschriftCent = this[TagesabschlussTable.summeGutschriftCent],
anzahlBuchungen = this[TagesabschlussTable.anzahlBuchungen],
bemerkungen = this[TagesabschlussTable.bemerkungen]
)
}
@@ -0,0 +1,50 @@
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}
port: ${SPRING_CLOUD_CONSUL_PORT:8500}
discovery:
enabled: true
register: true
prefer-ip-address: true
health-check-path: /actuator/health
health-check-interval: 10s
# 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:
exposure:
include: health,info,metrics,prometheus
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"
@@ -0,0 +1,60 @@
@file:OptIn(ExperimentalUuidApi::class)
package at.mocode.billing.service
import at.mocode.billing.domain.model.BuchungsTyp
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import kotlin.time.Clock
import kotlin.time.Duration.Companion.hours
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@SpringBootTest
@ActiveProfiles("test")
class TagesabschlussServiceTest {
@Autowired
lateinit var kontoService: TeilnehmerKontoService
@Autowired
lateinit var tagesabschlussService: TagesabschlussService
@Test
fun `Tagesabschluss aggregiert Buchungen korrekt`() {
val vId = Uuid.random()
val k1 = kontoService.getOrCreateKonto(vId, Uuid.random(), "Reiter A")
val k2 = kontoService.getOrCreateKonto(vId, Uuid.random(), "Reiter B")
val jetzt = Clock.System.now()
val von = jetzt - 1.hours
val bis = jetzt + 1.hours
// Buchungen erstellen
kontoService.buche(k1.kontoId, 5000L, BuchungsTyp.ZAHLUNG_BAR, "Barzahlung 1")
kontoService.buche(k2.kontoId, 3000L, BuchungsTyp.ZAHLUNG_BAR, "Barzahlung 2")
kontoService.buche(k1.kontoId, 2500L, BuchungsTyp.ZAHLUNG_KARTE, "Kartenzahlung")
kontoService.buche(k2.kontoId, 1000L, BuchungsTyp.GUTSCHRIFT, "Gutschrift")
// Gebühren (sollten nicht in den Zahlungs-Summen auftauchen)
kontoService.buche(k1.kontoId, 1500L, BuchungsTyp.NENNGEBUEHR, "Gebühr")
// Abschluss erstellen
val abschluss = tagesabschlussService.erstelleAbschluss(
veranstaltungId = vId,
von = von,
bis = bis,
abgeschlossenVon = "Admin"
)
assertNotNull(abschluss)
assertEquals(8000L, abschluss.summeBarCent)
assertEquals(2500L, abschluss.summeKarteCent)
assertEquals(1000L, abschluss.summeGutschriftCent)
assertEquals(5, abschluss.anzahlBuchungen) // 2x Bar + 1x Karte + 1x Gutschrift + 1x Gebühr
}
}
@@ -61,4 +61,34 @@ class TeilnehmerKontoServiceTest {
val historian = service.getBuchungsHistorie(konto.kontoId)
assertEquals(2, historian.size)
}
@Test
fun `Buchung stornieren`() {
val veranstaltungId = Uuid.random()
val personId = Uuid.random()
val konto = service.getOrCreateKonto(veranstaltungId, personId, "Storno Test")
// 1. Ursprüngliche Buchung
val gebuchtKonto = service.buche(
kontoId = konto.kontoId,
betragCent = 2500L,
typ = BuchungsTyp.BOXENGEBUEHR,
zweck = "Boxenmiete"
)
assertEquals(-2500L, gebuchtKonto.saldoCent)
val buchung = service.getBuchungsHistorie(konto.kontoId).first()
// 2. Stornieren
val storniertKonto = service.storniereBuchung(buchung.buchungId, "Falsche Box")
assertEquals(0L, storniertKonto.saldoCent)
// 3. Historie prüfen
val buchungen = service.getBuchungsHistorie(konto.kontoId)
assertEquals(2, buchungen.size)
assertTrue(buchungen.any { it.typ == BuchungsTyp.STORNIERUNG })
val storno = buchungen.find { it.typ == BuchungsTyp.STORNIERUNG }!!
assertEquals(2500L, storno.betragCent)
assertEquals(buchung.buchungId, storno.storniertBuchungId)
}
}
+2 -2
View File
@@ -5,8 +5,8 @@
# ===================================================================
# === CENTRALIZED BUILD ARGUMENTS ===
ARG GRADLE_VERSION=9.4.1
ARG JAVA_VERSION=25
ARG GRADLE_VERSION=9.5.0
ARG JAVA_VERSION=25.0.2
ARG BUILD_DATE
ARG VERSION=1.0.0-SNAPSHOT
@@ -1,3 +1,7 @@
@file:OptIn(ExperimentalWasmDsl::class)
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
@@ -7,38 +11,27 @@ group = "at.mocode"
version = "1.0.0"
kotlin {
// Toolchain is now handled centrally in the root build.gradle.kts
val enableWasm = providers.gradleProperty("enableWasm").orNull == "true"
// JVM target for backend usage
jvm()
// JS target for frontend usage (Compose/Browser)
js {
wasmJs {
browser()
// no need for binaries.executable() in a library
}
// Optional Wasm target for browser clients
if (enableWasm) {
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs {
browser()
}
}
sourceSets {
commonMain {
dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(projects.core.coreDomain)
}
commonMain.dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
}
commonTest {
dependencies {
implementation(libs.kotlin.test)
}
commonTest.dependencies {
implementation(kotlin("test"))
}
jvmTest.dependencies {
implementation(projects.platform.platformTesting)
}
}
}
@@ -78,7 +78,8 @@ data class NennungEinreichenRequest(
val zahlerId: Uuid? = null,
val startwunsch: StartwunschE = StartwunschE.KEIN_WUNSCH,
val istNachnennung: Boolean = false,
val bemerkungen: String? = null
val bemerkungen: String? = null,
val email: String? = null
)
/**
@@ -1,26 +1,39 @@
@file:OptIn(ExperimentalWasmDsl::class)
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
jvm()
wasmJs {
browser()
}
sourceSets {
commonMain {
kotlin.srcDir("src/main/kotlin")
dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(projects.backend.services.masterdata.masterdataDomain)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
}
all {
languageSettings.optIn("kotlin.uuid.ExperimentalUuidApi")
}
commonTest {
kotlin.srcDir("src/test/kotlin")
dependencies {
implementation(kotlin("test"))
implementation(projects.platform.platformTesting)
}
commonMain.dependencies {
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(projects.backend.services.masterdata.masterdataDomain)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
implementation(kotlin("test"))
}
jvmTest.dependencies {
implementation(projects.platform.platformTesting)
}
}
}

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