- Java 51.8%
- TypeScript 31.1%
- HTML 14.7%
- JavaScript 1.5%
- SCSS 0.7%
- Other 0.1%
| .cursor | ||
| .github | ||
| .vscode | ||
| backend | ||
| bank-browser | ||
| docker | ||
| docs | ||
| frontend | ||
| scripts | ||
| .agent.md | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| authentik-setup.md | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| docker-compose.dev.yml | ||
| docker-compose.yml | ||
| matrix-setup-dev.md | ||
| README.md | ||
node-K Management
Web-App für Vereinsverwaltung, Finanzen und Mitglieder-Self-Service — entwickelt für den Verein node-K.
Was die Anwendung kann
Mitglieder & Vorstand
- Mitgliederverwaltung — Self-Service-Portal, Vorstands- und Admin-Oberfläche
- Willkommensnachricht — Vorstand bearbeitet den Login-Dialog; Mitglieder können ihn dauerhaft ausblenden
- Mitgliedstypen — ordentliche, fördernde und Ehrenmitglieder
- E-Mail an Mitglieder — Vorlagen, Platzhalter, Empfänger-Presets, Anhänge, Hintergrund-Versand, Versandprotokoll mit Empfängerstatus
- Mitgliedsdokumente — Vorstand lädt Anhänge hoch und steuert die Sichtbarkeit; Mitglieder sehen freigegebene Dokumente im Profil
- Beitrags-Dauerauftrag — Anleitung für Mitglieder, Bestätigung nach Einrichtung, sichtbar für den Vorstand
- Externe Benachrichtigungen — Webhooks oder ntfy für Vereinsereignisse
- DSGVO — Datenexport und Anonymisierung auf Anfrage
Finanzen & Buchhaltung
- hledger-Buchführung — Buchungen in der Web-UI
- Git-versionierte Finanzdaten — Journal und Belege im Repository
- Automatische Beitragsverbuchung — monatlicher Lauf inkl. Dauerauftrag-Bestätigung
- Bank-Integration (Volksbank) — Kontoumsätze abrufen, abgleichen und zuordnen
- Buchungsvorlagen, Belege — wiederkehrende Buchungen; PDF/PNG/JPG-Anhänge; Kartenausgaben bei der Buchung verknüpfen
- Rechnungsprüfung — Transaktionsliste und Belege für Kassier und Rechnungsprüfer
Wallet & Kiosk
- Mitglieds-Wallet — Guthaben, Aufladung per QR-Code, Auszahlungsanträge
- Spenden & Terminal-Modus — Kiosk für Aufladungen und Spenden am Standort
Genehmigungen & Sicherheit
- 4-Augen-Prinzip — Kartenausgaben per Push genehmigen; Verknüpfung mit Buchungen
- Rollen & Berechtigungen — Mitglied, Vorstand, Kassier, Rechnungsprüfer, IT-Admin
- Authentik (OIDC) — Login und Gruppen-Sync
- Audit-Log — nachvollziehbare Protokollierung sicherheitsrelevanter Aktionen; Admin kann alte Einträge bereinigen
Integrationen & App
- Matrix — Konto verknüpfen, Raumberechtigungen, automatische Mitgliedschafts-Sync
- Web Push — Browser-Benachrichtigungen für Genehmigungen und Updates
- PWA — installierbare App mit automatischen Updates und In-App-Changelog
Tech Stack
| Backend | Java 25, Spring Boot 4.0.6, PostgreSQL, Flyway, JGit |
| Frontend | Angular 21 (zoneless, signals, standalone), Bootstrap 5 dark |
| Auth | authentik (OAuth2/OIDC) |
| Buchhaltung | hledger (CLI) |
| Deployment | Docker multi-stage build |
Projektstruktur
backend/ Spring Boot (Java 25, Maven)
frontend/ Angular SPA
docker/
├── Dockerfile Multi-stage: Angular → Maven → Runtime (JRE + hledger + git)
├── entrypoint.sh Container-Startskript (generiert Git-Deploy-Key, startet App als Non-Root)
docker-compose.yml App + PostgreSQL (Produktion & lokaler Test)
docker-compose.dev.yml Entwicklungs-Abhängigkeiten (authentik, PostgreSQL, Redis, Synapse, Element Web, bank-browser)
docker/matrix/ Lokale Matrix-Dev-Konfiguration (Synapse, Element)
docs/MATRIX_SETUP.md Matrix-Produktions-Setup
matrix-setup-dev.md Matrix lokales Dev-Setup (nach authentik-setup.md)
.env.local Lokale Umgebungsvariablen für direkte JVM-Ausführung (localhost URLs)
.env.docker Lokale Umgebungsvariablen für Container-Test (host.docker.internal URLs)
AI-Agents: Start mit
AGENTS.md· Architektur-Details in.agent.md· Code-Konventionen in.github/copilot-instructions.md
Feature-Dokumentation:
docs/vier-augen-prinzip.md
Quickstart: Entwicklung
Voraussetzungen: Java 25, Maven 3.9+, Node.js 22+, Docker Desktop.
1. Abhängigkeiten starten
docker compose --env-file .env.local -f docker-compose.dev.yml up -d
Startet authentik (http://localhost:9000), PostgreSQL (localhost:5432), Redis, Synapse (http://localhost:8008), Element Web (http://localhost:8088) und bank-browser (http://localhost:3000). Bank-Tokens aus .env.local — siehe docs/bank-browser-setup.md.
| URL | Dienst |
|---|---|
| http://localhost:9000 | authentik |
| http://localhost:8008 | Synapse (Matrix API) |
| http://localhost:8088 | Element Web |
| http://localhost:3000 | bank-browser (Volksbank Sidecar) |
Erster Start: Login mit
akadmin/admin12345, dann App einrichten — sieheauthentik-setup.md.
Matrix: Bot-Account und Env-Vars — siehematrix-setup-dev.md.
2. Backend starten
cd backend && mvn spring-boot:run
Läuft auf http://localhost:8080. Umgebungsvariablen aus .env.local werden von der IDE (z.B. IntelliJ EnvFile-Plugin) oder manuell geladen.
3. Frontend starten
cd frontend && npm install && npm start
Läuft auf http://localhost:4200, proxyt API-Calls an :8080.
Stoppen
docker compose --env-file .env.local -f docker-compose.dev.yml down
Matrix lokal testen
Mit docker-compose.dev.yml läuft ein vollständiger Matrix-Stack für lokale Entwicklung.
- Compose starten (siehe Quickstart oben)
- Setup-Schritte in
matrix-setup-dev.md: Bot-Account, Access Token,.env.local - Backend/Frontend starten,
/member/matrixund/admin/matrix/permissionstesten
Produktions-Deployment: docs/MATRIX_SETUP.md
Docker-Container testen
Baut das Image und startet es mit lokaler PostgreSQL. Authentik läuft extern via docker-compose.dev.yml.
Da der Container localhost nicht erreichen kann (das wäre der Container selbst), werden die authentik-URLs aufgeteilt:
| Variable | Zweck | Wert lokal |
|---|---|---|
AUTHENTIK_ISSUER_URI |
Muss dem iss-Claim im Token entsprechen (authentik setzt hier den Host des Token-Requests) |
http://host.docker.internal:9000/application/o/management/ |
AUTHENTIK_BASE_URL |
Backend→Backend Calls (Token, JWKS, UserInfo, API) | http://host.docker.internal:9000 |
AUTHENTIK_AUTHORIZE_URI |
Browser-Redirect für Login (muss vom User erreichbar sein) | http://localhost:9000/application/o/authorize/ |
# 1. Authentik starten (falls noch nicht läuft)
docker compose --env-file .env.local -f docker-compose.dev.yml up -d
# 2. Image bauen und starten
docker compose --env-file .env.docker up --build
App erreichbar unter http://localhost:8080.
Beim ersten Start wird automatisch ein SSH Deploy Key generiert — den öffentlichen Schlüssel über Admin → Git Deploy Key herunterladen und im Git-Server hinterlegen.
# Stoppen und Volumes löschen
docker compose down -v
Produktion deployen
Gleicher docker-compose.yml, Profile docker. In Produktion ist authentik von überall unter der gleichen URL erreichbar — daher zeigen alle drei Variablen auf dieselbe Basis-URL:
AUTHENTIK_ISSUER_URI=https://auth.node-k.at/application/o/management/
AUTHENTIK_AUTHORIZE_URI=https://auth.node-k.at/application/o/authorize/
AUTHENTIK_BASE_URL=https://auth.node-k.at
OIDC-Discovery wird im Docker-Profil generell nicht verwendet — die Endpoints werden immer explizit aus AUTHENTIK_BASE_URL abgeleitet. Das vermeidet Startup-Abhängigkeiten auf die IdP-Verfügbarkeit.
# .env mit Produktionswerten erstellen (siehe Umgebungsvariablen unten)
docker compose up -d
docker compose liest .env automatisch aus dem selben Verzeichnis — alternativ docker compose --env-file pfad/zu/.env up.
Nach dem ersten Start: Deploy Key über Admin-UI herunterladen und als Deploy Key im Git-Repository (z.B. Forgejo/GitLab) hinterlegen.
Umgebungsvariablen
Alle Variablen werden in der .env-Datei definiert.
Authentik / Auth
Login ist OIDC Authorization Code → Spring HTTP-Session (JSESSIONID). Die SPA speichert keine Access-/Refresh-Tokens; authentik-Refresh-Tokens werden nicht ausgewertet. Session-Laufzeit: server.servlet.session.timeout (8 h Inaktivität, Cookie max-age ebenfalls 8 h für PWA). Details: docs/roles-and-auth.md.
| Variable | Beschreibung |
|---|---|
AUTHENTIK_ISSUER_URI |
OIDC Issuer (muss dem iss-Claim in Tokens entsprechen) |
AUTHENTIK_AUTHORIZE_URI |
Authorization-Endpoint (Browser-Redirect für Login) |
AUTHENTIK_BASE_URL |
authentik Base URL für Backend-Calls (Token, JWKS, UserInfo, API) |
AUTHENTIK_CLIENT_ID |
OAuth2 Client ID |
AUTHENTIK_CLIENT_SECRET |
OAuth2 Client Secret |
AUTHENTIK_API_TOKEN |
Service Account Token |
Produktion: Alle drei URLs zeigen auf denselben Host (z.B.
https://auth.node-k.at). Lokaler Docker-Test:AUTHENTIK_ISSUER_URIundAUTHENTIK_BASE_URL=host.docker.internal:9000,AUTHENTIK_AUTHORIZE_URI=localhost:9000.
Anwendung
| Variable | Beschreibung | Standard |
|---|---|---|
GIT_REPO_URL |
Git Repository URL (SSH) | leer (nur lokales Repo) |
APP_MANAGEMENT_URL |
Öffentliche URL dieser Anwendung (manage.node-k.at) |
https://manage.node-k.at |
MEMBER_PORTAL_URL |
Authentik-Mitgliederportal (auth.node-k.at) — nicht die Management-App |
https://auth.node-k.at |
DB_PASSWORD |
PostgreSQL-Passwort | Pflichtfeld |
DB_USERNAME |
PostgreSQL-Benutzer | management |
E-Mail (SMTP)
Only override these if your setup differs from the defaults.
| Variable | Beschreibung | Standard |
|---|---|---|
MAIL_USERNAME |
SMTP-Benutzername | — |
MAIL_PASSWORD |
SMTP-Passwort | — |
MAIL_FROM_ADDRESS |
Standard-Absender-Adresse (Fallback, wenn MAIL_FROM_ADDRESSES leer) |
noreply@node-k.at |
MAIL_FROM_ADDRESSES |
Kommagetrennte Absender-Adressen für Vorstands-E-Mails | Wert von MAIL_FROM_ADDRESS |
MAIL_HOST |
SMTP-Server | node-k.at |
MAIL_PORT |
SMTP-Port | 465 |
MAIL_FROM_NAME |
Absender-Name | node-K Vereinsmanagement |
MAIL_SSL_ENABLE |
Implicit SSL | true |
MAIL_STARTTLS_ENABLE |
STARTTLS | false |
Deploy Key: Wird beim Container-Start automatisch unter
/data/ssh/git_deploy_keygeneriert. Der öffentliche Schlüssel ist über die Admin-UI downloadbar.
Matrix (optional)
| Variable | Beschreibung | Standard |
|---|---|---|
MATRIX_HOMESERVER_URL |
Matrix Homeserver Client API URL | leer (deaktiviert) |
MATRIX_BOT_USER_ID |
Bot Matrix-ID (@bot:domain) |
leer |
MATRIX_BOT_ACCESS_TOKEN |
Bot Access Token | leer |
MATRIX_HOMESERVER_DOMAIN |
Homeserver-Domain für ID-Validierung | leer |
MATRIX_LINK_CODE_TTL_MINUTES |
Gültigkeit Verifikationscode | 15 |
Ohne Matrix-Env-Vars läuft die App normal; UI zeigt „Matrix Server nicht erreichbar“.
Rollen
| Rolle | authentik-Gruppe | Berechtigungen |
|---|---|---|
| MEMBER | Club Member | Eigenes Profil bearbeiten |
| BOARD | Club Board Member | Mitgliederliste, Reports |
| TREASURER | Club Treasurer | Buchungen, Beiträge, hledger |
| AUDITOR | (keine — aus members.role) |
Rechnungsprüfung, Belege ansehen (Lesezugriff); ROLE_AUDITOR wird beim Login aus der DB abgeleitet |
| ADMIN | Club IT-Admin | Audit Log, E-Mail, Git Deploy Key Download, Auth-Rollen |
| TERMINAL | Kiosk Terminal | Kiosk-Zahlungsintents |
Rollen sind kumulativ: ADMIN > TREASURER > BOARD > MEMBER. Mapping passiert in RoleMapper.java basierend auf authentik-Gruppen im OIDC-Token — ausgenommen Rechnungsprüfer (members.role = "Rechnungsprüfer").
Wo bearbeite ich was? (Für Editoren / AI-Agents)
Eine Karte der häufigsten Änderungen. Detaillierte Konventionen in .agent.md.
Neue API-Route hinzufügen
- Controller in
backend/src/main/java/at/nodek/management/<feature>/<Feature>Controller.java @PreAuthorize("hasRole('BOARD')")o.ä. nicht vergessen- Service-Methode mit
@Transactional(am Service, nicht am Controller) - DTOs unter
<feature>/dto/ - Frontend-Client in
frontend/src/app/core/api/<feature>-api.service.ts— Interfaces incore/models.tsdefinieren, im Service re-exportieren
Neues Mitglieds-Feld
- Flyway-Migration in
backend/src/main/resources/db/migration/als nächsteV*__*.sql - Feld in
Member.javamit@Columnund ggf.@Builder.Default - DTO-Split beachten:
- Listen-/Tabellen-Felder →
BoardTableResponseDto(leichtgewichtig) - Detail-Felder (Modal-Anzeige) →
BoardMemberDetailResponseDto - Self-Service / Treasury / Admin-Legacy →
MemberResponse
- Listen-/Tabellen-Felder →
- Request-Felder in
CreateMemberRequestund/oderUpdateMemberRequest - Update-Logik in
MemberService.updateMemberAdmin()— immerrequest.getX() != nullCheck, sonst überschreibt ein leeres PUT alle Felder mit null - Frontend-Typ in
core/models.ts(BoardTableMember/MemberDetail) — wird automatisch re-exportiert übermember-api.service.ts - UI in
member-modal.component(Edit) und/odermember-view-modal.component(Anzeige) - Tabellenspalte:
member-table.component.html
Neue Auth-Rolle / Gruppen-Sync ändern
- Auth-Rollen-Entity:
AuthRole.java - Sync-Logik:
AuthRoleService.computeDesiredGroups()— Formel:additionalGroups ∪ Σ hasGroups − Σ hasNotGroups - Admin-UI:
admin-auth-roles.component.ts - Auth-Rollen-Zuweisung an Mitglieder ist ADMIN-only (
POST /api/admin/members/{id}/auth-roles/{roleId})
Neue Frontend-Route (Lazy Load)
- Routen-Datei:
frontend/src/app/pages/<area>/<area>.routes.ts - In
app.routes.tsregistrieren mitloadChildren: () => import(...)undcanActivate: [authGuard, roleGuard],data: { role: 'ROLE_X' } - Standalone-Component mit separatem
.html-Template (Projekt-Konvention) - Control-Flow:
@if/@for/@switch— kein*ngIf/*ngFor - State via
signal()/computed(), Eingaben viainput(), Events viaoutput()
Neue E-Mail-Vorlage
- Template unter
backend/src/main/resources/templates/email/<name>.html(Thymeleaf) - Render via
EmailService.renderTemplate(...)
Neue Geplante Aufgabe (Cron)
- In
backend/src/main/java/at/nodek/management/scheduler/—@Scheduledmit Idempotenz-Tabelle (Beispiel:MonthlyContributionJobverwendetcontribution_ledgermit UNIQUE-Constraint)
Audit-Log-Eintrag
Jede sicherheitsrelevante Aktion: auditService.log("EVENT_TYPE", entityId, "Beschreibung"). Konvention: SCREAMING_SNAKE_CASE für Event-Typen.
Bekannte Stolperfallen
Wichtig für AI-Agents und neue Editoren. Siehe
.agent.mdfür die vollständige Liste.
- Jackson 3.x: Imports sind
tools.jackson.databind.ObjectMapperundtools.jackson.core.type.TypeReference— nichtcom.fasterxml.jackson.*(Spring Boot 4 nutzt Jackson 3). - Zoneless Angular: Kein
zone.js. Templates nutzen@if/@for(keine Strukturdirektiven). State via Signals, nicht viaBehaviorSubject. - DTO-Trennung Board: Listen liefern
BoardTableResponseDto(nurauthRoleseager geladen). Detail-Endpoint/api/board/members/{id}für Modals — nie das volle Detail in Listen rendern. - Member-Update-Transaktion:
PUT /api/board/members/{id}nutztMemberService.applyBoardUpdate()— eine@Transactional-Methode für alle DB-Writes. Authentik-Sync erfolgt nach Commit im Controller. - Optimistic Locking:
Memberhat@Version-Feld (Long). Concurrent edits → 409 Conflict viaGlobalExceptionHandler. - Error Handling:
GlobalExceptionHandlermappt Exceptions auf strukturiertes JSON{ error, message, timestamp }. Frontend-Interceptor zeigt deutsche Meldungen. Typed Exceptions:AuthentikApiException,HledgerException,GitNotReadyException. additional_groups_jsonistNOT NULL: Initial-Wert"[]". Neue Member-Entities ohne Builder-Default werfen NPE in Postgres.- Template-Referenz vs.
viewChild: Wenn dein Template<app-x #foo />und deine TS-KlasseviewChild('foo')nutzt, gewinnt im Template die Template-Ref. Felder im Template ohne()aufrufen (foo.bar()), in TS via Signal mit()(this.fooRef()?.bar()). - Bundle-Size-Budget: Initial-Bundle übersteigt aktuell das 500 kB-Budget (~740 kB). Vor weiteren Imports: Lazy-Loading erwägen oder Budget in
angular.jsonheben. - Shared Models: Alle Domain-Interfaces leben in
frontend/src/app/core/models.ts. API-Services re-exportieren die Typen. Neue Interfaces dort anlegen, nicht inline in Services. - Loading States: Jede Daten-ladende Komponente soll einen
loading = signal(true)haben und<app-loading-spinner />ausshared/verwenden.