Commit Graph

101 Commits

Author SHA1 Message Date
Patrick Plate be932c1930 docs: Sprint 12 planning, analysis, reviews, and code review
- sprint12-analysis.md (full page audit)
- sprint12-plan.md (button fix plan)
- sprint12-testplan.md (button fix test plan)
- sprint12-phase2-integration-tests.md (v3, expert-approved)
- sprint12-phase2-panel-review.md (3 review cycles, 95% confidence)
- sprint12-code-review.md (approved with comments, blockers fixed)
2026-06-18 14:43:25 +02:00
Patrick Plate 776149e7d3 test: add full-stack Playwright integration test infrastructure
Sprint 12 Phase 2: Real integration tests with seed DB
- R__seed_test_data.sql (Flyway repeatable, 7 members, strains, batches, docs, board, events)
- TestResetController (profile-gated per-test DB reset)
- docker-compose.test.yml (self-contained, tmpfs Postgres)
- Dockerfile.playwright (v1.60.0, pre-installed deps)
- 13 integration spec files, 70+ test cases (@smoke + @full)
- seed-constants.ts, selectors.ts, api-client.ts test helpers
2026-06-18 14:43:16 +02:00
Patrick Plate 6e25914074 feat: wire Documents + Board page buttons, add mock-mode dual operation
Sprint 12 Phase 1: Golden Test Standard
- Documents: React Query, upload/download/delete wired, category colors+icons, table min-widths, data-testid
- Board: React Query, create position/elect/remove wired, confirmation dialogs, data-testid
- Both pages: mock-mode fallback (works without backend)
2026-06-18 14:43:00 +02:00
Patrick Plate 90cdac7468 fix: revert V27 checksum + add V35 for generated_reports timestamps
Deploy to TrueNAS / deploy (push) Successful in 27s
V27 was modified after it was applied on production, causing a Flyway
checksum mismatch. Reverted V27 to original and moved the created_at/
updated_at columns to a new V35 migration.
2026-06-17 21:45:09 +02:00
Patrick Plate fa567c1c3f feat: Sprint 11 test coverage — +166 unit tests, schema drift fix (V34), Testcontainers 1.21.3
Deploy to TrueNAS / deploy (push) Failing after 2m11s
Phase 2: AssemblyServiceTest (22), EventServiceTest (13), ForumServiceTest (14), InfoBoardServiceTest (10)
Phase 3: Camt053ParserTest (19), CsvBankParserTest (14), BankImportServiceTest (14), BankStatementParserServiceTest (9)
Phase 4: JwtServiceTest (17), LoginRateLimiterTest (8), TenantFilterAspectTest (8), DocumentServiceTest (12), GlobalExceptionHandlerTest (6)
Phase 5: V34 schema drift fix migration, MigrationIntegrationTest + AbstractIntegrationTest fixes
Infrastructure: V27 fix (added timestamps), Testcontainers upgrade 1.20.4 -> 1.21.3, test resources (bankimport samples)
2026-06-17 21:38:32 +02:00
Patrick Plate f1959eb3d2 ci(deploy): re-trigger after socket automount fix (empty options + docker_host)
Deploy to TrueNAS / deploy (push) Successful in 5m21s
2026-06-16 20:30:35 +02:00
Patrick Plate 592abc4b6d ci(deploy): re-trigger TrueNAS deploy after runner socket-mount fix
Deploy to TrueNAS / deploy (push) Failing after 18s
2026-06-16 20:27:51 +02:00
Patrick Plate 3b15d7439d ci(deploy): auto-deploy to TrueNAS via self-hosted Gitea Actions runner
Deploy to TrueNAS / deploy (push) Failing after 3s
- Replace VPS SSH deploy workflow with a self-contained job that runs on the
  TrueNAS act_runner (host docker socket mounted). Checks out the pushed commit,
  builds, and rolls out the cannamanage compose stack in-place (project=cannamanage),
  then health-checks backend :8081 + frontend :3000.
- Commit docker-compose.truenas.yml (port remap 8081 + AUTH_SECRET) into the repo;
  it was previously host-only, so a fresh checkout could not reproduce the deploy.
  Use the !override tag for the backend ports list.
2026-06-16 18:52:18 +02:00
Patrick Plate 59b785b8ed test(sprint-11): centralize JaCoCo coverage rules and add bank import + finance test coverage
Deploy to Production / test (push) Failing after 1s
Deploy to Production / deploy (push) Has been skipped
- pom.xml: introduce risk-tiered JaCoCo rules in parent POM
  - bundle: 80% line coverage
  - bankimport/finance packages: 90% (highest precision)
  - api.security: 85%
  - scheduler/notification: 70%
  - exclude entity/enums/dto/config from coverage measurement
  - add Surefire 3.5.2 plugin management
- cannamanage-service/pom.xml: remove obsolete module-local ComplianceService=100% rule
  (subsumed by parent package rules), add explicit jackson-databind dep so
  ByteBuddy can mock AuditService.METADATA_MAPPER
- Add AbstractServiceTest base class for service-layer tests
- Add FinanceServiceTest
- Add bankimport test suite:
  - Mt940ParserTest with malformed input fixtures
    (encoding, overflow, truncated, generic)
  - PaymentMatchingServiceTest with ParsedTransactionBuilder helper
  - CAMT.053 / Sparkasse MT940 sample fixtures
  - XXE attack fixtures (billion-laughs, SSRF, generic)
- docs/sprint-11/: analysis, plan, plan-review, testplan
2026-06-15 21:37:49 +02:00
Patrick Plate 6f7352124d fix(security): hardening — rate limiting, CORS config, audit safety, CSP headers, validation
Deploy to Production / test (push) Failing after 10m44s
Deploy to Production / deploy (push) Has been skipped
- Fix 1: Login rate limiting (5 attempts/min/IP) on POST /api/v1/auth/login
  - New LoginRateLimiter (ConcurrentHashMap + @Scheduled reset every 60s)
  - HTTP 429 with German message on exceed
  - Client IP via X-Forwarded-For with proxy fallback
  - @EnableScheduling on CannaManageApplication

- Fix 2: CORS origins configurable via cannamanage.cors.allowed-origins env var
  - Defaults to localhost + docker frontend for dev
  - SecurityConfig reads with @Value, splits comma-separated list

- Fix 3: Audit JSON safety — replaced manual string concat with Jackson ObjectMapper
  - New AuditService.toMetadataJson(Map) helper
  - RetentionService and AuthorityExportService refactored

- Fix 4: Tomcat max-http-form-post-size=2MB prevents DoS via oversized payloads

- Fix 5: @Valid added to @RequestBody on 17+ endpoints across
  ComplianceRecordsController, FinanceController, ConsentController,
  StaffController, ComplianceDeadlineController, SubscriptionController,
  ForumController (admin + portal)

- Fix 6: Content-Security-Policy 'default-src \'self\'; frame-ancestors \'none\''
  + frameOptions(deny) on both API + portal filter chains
2026-06-15 19:29:32 +02:00
Patrick Plate 6319552675 fix(security): resolve 4 production blockers from final review
- IDOR (HIGH): DocumentController download/delete now verify document.clubId matches TenantContext; returns 403 on mismatch via new loadOwnedDocument() helper
- Path Traversal (HIGH): DocumentService.sanitizeFilename() strips path components, removes control/reserved chars, caps at 200 chars, falls back to UUID. Applied to uploadDocument() and archiveProtocol()
- JWT Dev Secret (HIGH): @PostConstruct guard in JwtService throws IllegalStateException if secret null/<32 chars/equals fail-loud marker. application.properties default replaced with CHANGE_ME_IN_PRODUCTION_THIS_WILL_FAIL_ON_STARTUP placeholder (env var CANNAMANAGE_SECURITY_JWT_SECRET set in docker-compose.yml; test profiles have their own valid secrets)
- SecurityConfig (MEDIUM): explicit /api/v1/documents/** matcher with hasAnyRole(ADMIN, STAFF, MEMBER) for defense-in-depth

Verified: Docker rebuild healthy, backend starts cleanly (JWT guard accepts env var), Playwright 203 pass (2 pre-existing login failures unrelated — dev compose profile has no seed users; admin@test.de only loaded via docker-compose.test.yml)
2026-06-15 19:11:35 +02:00
Patrick Plate 8c969c610f feat(sprint10): Phase 4+5 — Frontend import wizard + integration testing
Phase 4 — Frontend Import Wizard:
- bank-import.ts service: types (BankImportSession, BankTransaction,
  CsvColumnMapping, ImportSessionStatus, MatchStatus) + 12 React Query hooks
  (sessions, transactions, mappings, upload/confirm/skip/assign/complete)
- /finance/import page: 4-step wizard (Upload -> Map -> Review -> Confirm)
  * Drag-and-drop upload with bank format auto-detect (MT940/CAMT.053/CSV)
  * CSV column mapping editor (saves as reusable mapping)
  * Review table with color-coded MATCHED/SUGGESTED/UNMATCHED/CONFIRMED rows,
    confidence % badges, member-assign Combobox, skip/confirm/bulk-confirm
  * Completion summary + import history table with resume action
- de.json + en.json: full bankImport.* namespace (steps, upload, map, review,
  complete, history, status, sessionStatus, actions, errors)
- Navigation: Finanzen converted to nested submenu (Uebersicht + Import)

Phase 5 — Integration Testing:
- docker compose down -v + up -d --build (clean rebuild)
- Playwright e2e/sprint10-system-test.spec.ts: verifies /finance/import
  unauthenticated -> /login?callbackUrl=%2Ffinance%2Fimport (PASS)
- Backend health + frontend route registration verified

Bugfix bundled (blocked backend startup):
- PaymentRepository: countOverdueByClubId* queries referenced non-existent
  Payment.dueDate column (regression from Sprint 9 Phase 6, commit 57f418f).
  Switched to Payment.periodTo (the implicit due date for billing periods).
2026-06-15 18:33:40 +02:00
Patrick Plate 5defe42d67 feat(sprint10): Phase 3 — BankImportService + REST API
Implements the orchestrator and REST endpoints for the bank statement
import wizard (Sprint 10 Phase 3).

Service layer (cannamanage-service):
- BankImportService: upload → SHA-256 dedup → parse → match → persist
  in two transactional steps (file I/O outside @Transactional, persist
  in @Transactional helper). Methods: uploadAndParse, confirmMatch,
  confirmAllMatched (≥90% confidence), manualAssign, skipTransaction,
  completeSession, query helpers.
- GoBD §147 AO immutability guard: assertSessionMutable() rejects any
  mutation on COMPLETED/FAILED sessions with German error messages.
- Hard 5MB upload cap enforced before parsing.
- Audit events: BANK_IMPORT_STARTED / BANK_PAYMENT_CONFIRMED /
  BANK_IMPORT_COMPLETED. Uploader notified via NotificationService.

REST layer (cannamanage-api):
- BankImportController under /api/v1/finance/import/*:
  POST sessions (multipart), GET sessions/single/transactions(?status=),
  POST {id}/transactions/{txnId}/confirm|assign|skip,
  POST {id}/confirm-all, POST {id}/complete,
  GET/POST/DELETE csv-mappings.
- Permission: FINANCE_IMPORT with MANAGE_FINANCES fallback.
- Defence-in-depth tenant check on every path-parameter ID.

DTOs (cannamanage-api/dto/bankimport):
- ImportSessionResponse, TransactionResponse, ConfirmRequest,
  AssignRequest, SkipRequest, BulkConfirmResponse, CreateMappingRequest.

Persistence:
- V33__bank_import_file_hash.sql: adds file_hash VARCHAR(64) + unique
  partial index (club_id, file_hash) for duplicate-upload detection.
- BankImportSession.fileHash field, repository.existsByClubIdAndFileHash.

Configuration:
- application.properties: multipart enabled, max-file-size=5MB,
  max-request-size=6MB.

Build: mvn package -DskipTests  (cannamanage-api fat JAR 92MB).
2026-06-15 17:47:27 +02:00
Patrick Plate 527e9b1219 feat(sprint10): Phase 2 — Payment matching engine with confidence scoring 2026-06-15 17:30:28 +02:00
Patrick Plate 55110c95af feat(sprint10): Phase 1 — Data model + bank statement parsers (MT940, CAMT.053, CSV)
Implements the Sprint 10 Phase 1 foundation for the Smart Payment Import feature:

Domain layer:
- 3 new enums: BankFormat (MT940, CAMT053, CSV), ImportSessionStatus, MatchStatus
- StaffPermission.FINANCE_IMPORT
- AuditEventType: BANK_IMPORT_STARTED/COMPLETED/FAILED + BANK_PAYMENT_CONFIRMED
- NotificationType.BANK_IMPORT_COMPLETED
- ConsentType.BANK_DATA (DSGVO consent for IBAN storage)
- 3 new entities: BankImportSession, BankTransaction, CsvColumnMapping
- Member: + iban (VARCHAR 34) + ibanConsentDate
- MemberStatus.LEFT (semantic alias for RESIGNED, referenced by Sprint 9 RetentionService)

Persistence:
- V30__bank_import_sessions.sql
- V31__bank_transactions.sql
- V32__csv_column_mappings.sql (also adds iban + iban_consent_date to members)
- 3 Spring Data repositories

Parser infrastructure (cannamanage-service/src/main/java/de/cannamanage/service/bankimport):
- BankStatementParser interface (Strategy pattern, Spring-injected list)
- ParsedTransaction + ParseResult records
- BankStatementParseException (parse errors)
- Mt940Parser: custom state machine, CENTURY_BOUNDARY=70 for YY→YYYY, proprietary
  header tolerance (skips lines before first :20: for StarMoney/WISO/Hibiscus wrappers)
- Camt053Parser: StAX streaming with XXE hardening (IS_SUPPORTING_EXTERNAL_ENTITIES,
  SUPPORT_DTD, IS_REPLACING_ENTITY_REFERENCES all false); supports camt.053.001.02
  and camt.053.001.08 namespaces
- CsvBankParser: Apache Commons CSV with configurable columns per club; German number
  format ("1.234,56"); ISO-8859-1 default encoding
- BankStatementParserService: filename-extension hint + content probe; throws
  UnrecognizedFormatException when no parser claims the file

Build verified via Docker (cannamanage-api:sprint10-phase1).

Sprint 9 fix (incidental, required to compile):
- Added MemberStatus.LEFT (Sprint 9 RetentionService referenced it but the enum
  value was missing)
- MemberListRegistryGenerator: added LEFT to formatStatus() switch (mapped to
  "Ausgetreten", same as RESIGNED)

Sprint 10 docs: analysis, plan, plan-review, testplan.

Co-Authored-By: Lumen <lumen@cannamanage.de>
2026-06-15 17:21:55 +02:00
Patrick Plate 57f418f7c9 feat(sprint9): Phase 6 — Compliance dashboard, RetentionService, testing
Backend:
- ComplianceDashboardService: traffic-light status per ComplianceArea
  (KCANG/FINANCE/DSGVO/VEREIN) based on deadlines, payments, board positions
- RetentionService: scheduled anonymization of expired member data (KCanG §24,
  5 years), with dry-run preview and retention report endpoints
- ComplianceDeadlineSeeder: seeds 5 standard recurring deadlines on club creation
- ComplianceDashboardController: GET /api/v1/compliance/dashboard,
  GET /retention, POST /retention/preview
- Repository additions: countOverdue, countActive board positions/members

Frontend:
- /compliance page with traffic-light status cards per area
- Overdue deadlines section (highlighted red) with 'days overdue' badges
- Upcoming deadlines with 'days until due' badges and 'Complete' buttons
- Retention info cards (KCanG §24: 5y, AO §147: 10y, DSGVO: 2y)
- Navigation: added 'Compliance-Status' to sidebar under Compliance group
- compliance-dashboard.ts service with mock data for dev mode

Build verified: pnpm build passes clean.
2026-06-15 14:12:01 +02:00
Patrick Plate 87511e0485 feat(sprint9): Phase 5 — Berichtszentrale, sidebar reorg, dashboard enhancement
- Sidebar: reorganized into 4 collapsible groups (Betrieb, Kommunikation, Verwaltung, Compliance)
- Berichtszentrale: new /reports-center page with report cards grouped by category (Finance, KCanG, DSGVO, Admin), format selector, date range pickers, Behörden-Export dialog with password protection
- Dashboard: added Outstanding Payments and Monthly Income KPI cards, Upcoming Events widget, Latest Announcements widget, conditional alert cards
- Pricing: fixed mobile overflow at 375px viewport on comparison table
- Frontend service: new compliance-reports.ts with React Query hooks for report generation, authority export, and download
- i18n: added reportsCenter.* and dashboard widget keys to de.json and en.json
2026-06-15 13:45:48 +02:00
Patrick Plate c3722ab726 feat(sprint9): Phase 4 — DSGVO templates + Verein admin reports
Implements 7 report generators for Phase 4:

DSGVO templates:
- VvtReportGenerator: Art. 30 DSGVO Verarbeitungsverzeichnis (PDF)
- TomReportGenerator: Art. 32 DSGVO Technisch-organisatorische Maßnahmen (PDF)
- DsfaReportGenerator: Art. 35 DSGVO Datenschutz-Folgenabschätzung (PDF)
- LoeschkonzeptGenerator: Löschkonzept with retention rules (PDF + JSON)
- BreachNotificationGenerator: Art. 33/34 DSGVO 72h breach notification (PDF)

Verein administration:
- MemberListRegistryGenerator: §67 BGB Mitgliederliste for Amtsgericht (PDF)
- BoardChangeGenerator: §67 BGB Vorstandsänderung notification (PDF)

Also adds:
- BreachReportParameters record for breach notification input
- MemberRepository.findByClubIdOrderByLastNameAscFirstNameAsc()
2026-06-15 13:22:46 +02:00
Patrick Plate 3ca231dc9c feat(sprint9): Phase 3 — KCanG compliance reports + Behörden-Export
Implemented 6 KCanG compliance report generators and the hero
Behörden-Export feature:

- AnnualAuthorityReportGenerator: Multi-section §22 KCanG annual report
  (9 sections: Vereinsdaten, Mitgliederstatistik, Anbauübersicht,
  Weitergabe-Statistik, Bestandsführung, Vernichtung, Transport,
  Prävention, Jugendschutz)
- DistributionLogGenerator: §19(4) distribution log (PDF+CSV,
  anonymized member data per DSGVO)
- DestructionProtocolGenerator: §22 destruction protocol with
  signature lines and sequential numbering
- TransportCertificateGenerator: §22(4) transport documentation
- BestandsfuehrungGenerator: Stock flow report (PDF+CSV) with
  per-batch breakdown
- PreventionActivityReportGenerator: §23 prevention activities

Authority Export (Behörden-Export) — THE HERO FEATURE:
- AuthorityExportService: Streaming ZIP generation via ZipOutputStream
- Re-authentication required (password re-entry + BCrypt verification)
- Mandatory reason field stored in audit trail
- Rate limited: max 1 export per hour per tenant
- ZIP contains all compliance PDFs + anonymized member JSON + manifest
- Memory-efficient: PDFs generated and streamed sequentially

Endpoint: POST /api/v1/reports/authority-export
Request: { year, password, reason }
Response: StreamingResponseBody (application/zip)

Also enhanced repositories:
- DestructionRecordRepository: date-range queries + sum aggregation
- TransportRecordRepository: date-range queries
2026-06-15 12:53:12 +02:00
Patrick Plate a29c38756c feat(sprint9): Phase 2 — Financial report generators (EÜR, Kassenbuch, Beitragsbescheinigung)
Implements Sprint 9 Phase 2 financial report generators:

- MemberReportParameters: new parameter record for per-member reports
- EurReportGenerator: Einnahmen-Überschuss-Rechnung (§4(3) EStG)
  - PDF: professional layout with income/expense sections, monthly breakdown
  - CSV: semicolon-delimited, ISO-8859-1, German decimal format
  - JSON: ELSTER-compatible structure for Steuerberater import
- KassenbuchExportGenerator: GoBD-compliant cash book export
  - PDF: landscape A4, running balance, sequential Beleg-Nr
  - CSV: GoBD-compliant format with injection prevention
  - Includes opening balance calculation and period totals
- BeitragsbescheinigungGenerator: membership fee confirmation per member
  - PDF: club letterhead, payment table, signature lines
  - For member tax purposes (Sonderausgaben)
- ReportGeneratorService: added getAvailableTypes() method
- ReportController: added GET /api/v1/reports/types endpoint

All generators are @Service beans auto-discovered by ReportGeneratorService.
Docker build verified green.
2026-06-15 12:22:53 +02:00
Patrick Plate 26a77dd269 feat(sprint9): Phase 1 — Data model + ReportGenerator infrastructure
- 7 new enums: ReportType, ExportFormat, DestructionMethod, TransportStatus,
  ComplianceArea, ComplianceStatus, RetentionCategory
- Extended: StaffPermission (+3), AuditEventType (+5), NotificationType (+2)
- Flyway V23-V29: destruction_records, transport_records, propagation_sources,
  prevention_activities, generated_reports, compliance_deadlines, distribution THC/CBD
- 6 new JPA entities extending AbstractTenantEntity
- 6 new Spring Data repositories with tenant-scoped queries
- ReportGenerator<T> interface + ReportGeneratorService (auto-discovery, format dispatch)
- ComplianceRecordsController (CRUD for destruction/transport/propagation/prevention)
- ComplianceDeadlineController (create, list, complete, overdue)
- DateRangeReportParameters record for report generation
2026-06-15 12:01:06 +02:00
Patrick Plate 2d83c4b8a1 fix: resolve Sprint 8 compilation issues, Docker build green 2026-06-15 09:57:32 +02:00
Patrick Plate 61b0cd92be feat(sprint8): Phase 5+6 — Integration, schedulers, tier enforcement, testing
Phase 5 — Integration:
- PaymentReminderScheduler: monthly cron at 9am, sends PAYMENT_REMINDER
  and PAYMENT_OVERDUE (30+ days) notifications, audit logged
- BoardTermScheduler: daily cron at 8am, sends BOARD_TERM_EXPIRING
  notification 30 days before term end (1-day window for single delivery)
- Assembly protocol auto-archive: completeAssembly() generates PDF via
  AssemblyProtocolService and stores it in DocumentService.archiveProtocol()
- PlanTierService: Sprint 8 limits added (Kassenbuch 3mo starter, 1 MV/year
  starter, 100MB/1GB/unlimited document storage)
- Notification wiring: PAYMENT_RECEIVED sent to member on recordPayment(),
  sendToAllMembers() added to NotificationService for assembly invitations
- Seed data: 2 fee schedules (Regular €30, Reduced €15), 4 fee assignments,
  3 sample payments, 2 board positions, 2 board members

Phase 6 — Testing infrastructure:
- V22 migration: protocol_document_id on assemblies table
- Schedulers disabled in test profile (cannamanage.schedulers.enabled=false)
- Scheduler property configurable via SCHEDULERS_ENABLED env var

Additional fixes (pre-existing Phase 4 issues):
- AssemblyProtocolService: fixed Document import ambiguity (OpenPDF vs entity)
- AuditService: added convenience overloads for UUID actorId/clubId
- ReceiptPdfService: fixed getReceiptNumber/getMemberNumber/getPaymentDate
- StaffPermissionChecker: added getUserId/getClubId/getTenantId helpers
- FinanceService: added getPaymentById, exportLedgerCsv methods
2026-06-15 09:22:49 +02:00
Patrick Plate e4698827ee feat(sprint8): Phase 4 — Dokumentenarchiv + Vorstandsverwaltung
Backend:
- V20 migration: documents table with category, access_level, file storage
- V21 migration: board_positions + board_members with term tracking
- Document entity + DocumentCategory/DocumentAccessLevel enums
- BoardPosition + BoardMember entities
- Extended AuditEventType (DOCUMENT_UPLOADED/DELETED, BOARD_MEMBER_ELECTED/REMOVED)
- Extended StaffPermission (MANAGE_DOCUMENTS)
- Extended NotificationType (BOARD_TERM_EXPIRING)
- DocumentService: upload, list, download, delete, storage usage
- BoardService: positions CRUD, elect/remove members, current/history
- DocumentController: multipart upload, filtered list, download, delete, portal
- BoardController: positions, elect, remove, current board, history, portal

Frontend:
- documents.ts + board.ts service layers
- Admin /documents page: grouped by category, upload dialog, filter, download/delete
- Admin /board page: current board cards, position management, elect member dialog
- Navigation: added Dokumente + Vorstand to sidebar
- i18n: documents.* + board.* keys in de.json + en.json
2026-06-15 08:53:38 +02:00
Patrick Plate b22702317a feat(sprint8): Phase 3 — Mitgliederversammlung (assemblies, voting, protocol PDF)
Backend:
- V19 migration: assemblies, assembly_agenda_items, assembly_attendees, assembly_votes, assembly_vote_records
- Enums: AssemblyType, AssemblyStatus, AgendaItemType, VoteType, VoteDecision, VoteResult
- Entities: Assembly, AssemblyAgendaItem, AssemblyAttendee, AssemblyVote, AssemblyVoteRecord
- Repositories: Assembly, AgendaItem, Attendee, Vote, VoteRecord
- AssemblyService: full lifecycle (create, invite, start, attend, vote, quorum, complete)
- AssemblyProtocolService: OpenPDF protocol generation (§147 AO compliant)
- AssemblyController: admin + portal endpoints
- Extended: AuditEventType, NotificationType, StaffPermission

Frontend:
- Assembly service with full API client and TypeScript types
- Admin assemblies list page with create dialog (agenda builder)
- Admin assembly detail page (quorum, agenda, votes, attendees)
- Navigation: Versammlungen with Gavel icon (after Finanzen)

Legal basis: §32-§40 BGB (Mitgliederversammlung), §147 AO (retention)
2026-06-15 08:39:10 +02:00
Patrick Plate 3211ade5be feat(sprint8): Phase 2 — Treasury frontend + PDF receipts
Backend:
- ReceiptPdfService: Generates Quittung PDF per payment (OpenPDF, A4)
- FinancialReportService: Annual financial report PDF (Jahresabschluss)
- FinanceController: Added receipt download, annual report, CSV export endpoints
- Portal receipt download with member ownership verification

Frontend:
- src/services/finance.ts: Complete React Query service (types, hooks, mutations)
- /finance: Dashboard with KPI cards, recent transactions, outstanding members
- /finance/payments: Payment list with filtering, void, receipt download
- /finance/kassenbuch: Kassenbuch ledger with date range, CSV export
- /finance/fee-schedules: Fee schedule CRUD with interval management
- /finance/reports: Annual report PDF download
- /portal/finance: Member self-service balance + payment history + receipts

Navigation & i18n:
- Added Finanzen (Wallet icon) to admin sidebar
- Portal finance page for member payments
- Comprehensive de.json + en.json finance keys (~100 translations)
2026-06-15 08:24:43 +02:00
Patrick Plate 721503b231 feat(sprint8): Phase 1 — Treasury backend (fee schedules, payments, Kassenbuch)
- Extend StaffPermission with MANAGE_FINANCES, VIEW_FINANCES
- Extend AuditEventType with PAYMENT_RECORDED, PAYMENT_VOIDED, FEE_SCHEDULE_CREATED, FEE_SCHEDULE_UPDATED, EXPENSE_RECORDED
- Extend NotificationType with PAYMENT_REMINDER, PAYMENT_OVERDUE, PAYMENT_RECEIVED
- New enums: PaymentMethod, PaymentStatus, TransactionType, FeeInterval, ExpenseCategory
- V18 Flyway migration: fee_schedules, member_fee_assignments, payments, ledger_entries tables
- Entities: FeeSchedule, MemberFeeAssignment, Payment, LedgerEntry
- Repositories with financial queries (balance, outstanding, period sums)
- FinanceService: fee schedule CRUD, record/void payments, expenses, Kassenbuch, summaries
- FinanceController: 14 admin endpoints + 2 portal self-service endpoints
- LedgerEntry is append-only per §147 AO (no update/delete)
- All amounts in cents (Integer) to avoid floating-point precision issues
2026-06-15 08:00:04 +02:00
Patrick Plate cfb38e8fc6 test: authenticated admin E2E suite + accessibility + visual regression baselines
- Global setup: authenticates as admin, saves storageState for reuse
- playwright.config.ts: 3 projects (setup, authenticated, unauthenticated)
- authenticated-admin.spec.ts: 16 admin pages tested with real auth session
- accessibility.spec.ts: axe-core scans on all admin, public, and portal pages
- visual-regression.spec.ts: dark mode baselines for key pages (toHaveScreenshot)
- @axe-core/playwright added as devDependency
- .gitignore updated: excludes .auth/ and test-results/

Full suite: 262 tests passing (setup:1, authenticated:52, unauthenticated:209)
2026-06-13 22:30:29 +02:00
Patrick Plate aabde17532 feat(sprint7): Phase 4 — Integration (SMTP, tier enforcement, WebSocket)
Phase 4 implementation:
- 4.1 IONOS SMTP email configuration (production + docker profiles)
- 4.2 Portal navigation update (info board, events, forum links)
- 4.3 Tier enforcement: PlanTierService (forum=Pro+, info board limits)
- 4.4 WebSocket real-time updates (WebSocketEventPublisher)
- 4.5 EmailService: notification, event reminder, info board templates + rate limiting
- 4.6 Enterprise custom FROM: CustomMailDomain entity, DNS verification, controller

New files:
- PlanTierService: tier checks for forum/info board/enterprise features
- NotificationDispatchService: EMAIL channel dispatch via preferences
- WebSocketEventPublisher: STOMP topic push for forum/info board/events
- CustomMailDomainService: DNS TXT record verification for custom FROM
- MailSettingsController: Enterprise custom domain API endpoints
- CustomMailDomain entity + repository
- V16 migration: email dispatch index
- V17 migration: custom_mail_domains table
- Frontend: use-forum-subscription + use-info-board-subscription hooks
- Portal navbar: added info board, events, forum navigation items
- i18n: added portal nav translations (de + en)

Also fixed pre-existing Phase 2.5/3 compilation issues:
- Member entity: added userId field
- AuditService: added convenience overloads (logEvent, 4-param log)
- AuditEventType: added INFO_BOARD_POST_UPDATED, INFO_BOARD_POST_DELETED
- QuotaViolationCode: added TIER_UPGRADE_REQUIRED
- StaffPermissionChecker: added requirePermission(UserDetails, ...)
- TenantContext: added getCurrentTenantId() alias
- MemberRepository: added findByUserId, findByClubId, findAllByClubId
- EmailServiceTest: updated for new constructor signature
2026-06-13 20:51:10 +02:00
Patrick Plate a539ed9eb2 feat(sprint7): Phase 3 — Forum MVP
- Flyway V15: forum_topics, forum_replies, forum_reactions, forum_reports tables
- Enums: ForumTargetType, ReactionType, ReportStatus
- Extended AuditEventType with FORUM_REPLY_CREATED, FORUM_REPORT_REVIEWED
- Entities: ForumTopic, ForumReply, ForumReaction, ForumReport
- Repositories: ForumTopicRepository, ForumReplyRepository, ForumReactionRepository, ForumReportRepository
- ForumService: full CRUD, moderation (lock/pin/delete), 60-min edit window,
  toggle reactions, content reporting, notifications on new topics/replies
- ForumController: admin + portal endpoints (topics, replies, reactions, reports, moderation)
- Frontend: forum.ts service with React Query hooks (admin + portal)
- Frontend: Admin forum page with topic list, moderation actions (lock/pin/delete)
- Frontend: Portal forum page with topic list, reply thread, reactions, report
- Navigation: added Forum with MessageSquare icon
- i18n: forum.* keys in de.json and en.json
2026-06-13 20:31:17 +02:00
Patrick Plate 05fd679c4d feat(sprint7): Phase 2.5 — Club Event Calendar
- Flyway V14: club_events + event_rsvps tables with reminder_sent tracking
- Enums: EventType, RsvpStatus, RecurrenceRule + extend AuditEventType/NotificationType
- Entities: ClubEvent (extends AbstractTenantEntity), EventRsvp (unique event+member)
- Repositories: ClubEventRepository, EventRsvpRepository with date-range and status queries
- EventService: CRUD, RSVP with maxAttendees enforcement (409 if full), iCal RFC 5545 generation, recurring event virtual expansion, notifications on create/cancel, auto-post to Info Board
- EventReminderScheduler: hourly check, 24h reminder to ACCEPTED/MAYBE attendees
- EventController: admin CRUD (MANAGE_INFO_BOARD permission), portal upcoming events, RSVP endpoint, iCal download (text/calendar), attendee list
- Frontend: events.ts service (React Query hooks matching apiClient pattern), admin calendar page (month grid with event dots, create dialog, event cards), portal events page (RSVP buttons, capacity display)
- Navigation: added Kalender with Calendar icon
- i18n: events.* keys in de.json and en.json
- UI: added @radix-ui/react-switch + Switch component
2026-06-13 20:16:56 +02:00
Patrick Plate 4aa27cd4f9 feat(sprint7): Phase 2 — Info Board (Schwarzes Brett)
Backend:
- V13 Flyway migration: info_board_posts, post_attachments, post_read_status tables
- InfoBoardPost entity with category enum (EVENT, RULE, GENERAL, MAINTENANCE)
- PostAttachment entity (table created, upload deferred to later)
- PostReadStatus entity with composite key (post_id, member_id)
- InfoBoardPostRepository with paginated queries + unread count
- InfoBoardService: CRUD, pin/archive, mark-as-read, notification dispatch
- InfoBoardController: admin CRUD + portal read/unread endpoints
- Integration with NotificationService and AuditService

Frontend:
- info-board.ts service with React Query hooks for all endpoints
- Admin Info Board page at /info-board with create dialog, filters, pin/archive/delete
- Navigation: added 'Schwarzes Brett' to admin sidebar
- i18n: added infoBoard.* keys to de.json and en.json
- Fixed pre-existing prettier issues in notification-compose.ts
- Fixed BufferSource type issue in push-subscription.ts
2026-06-13 19:41:20 +02:00
Patrick Plate 706a6e257b feat(sprint7): Phase 1 — notifications enhancement + push infrastructure
Phase 1 (Notification Enhancement):
- Extended NotificationType enum (ADMIN_MESSAGE, INFO_BOARD_POST, FORUM_REPLY, FORUM_MENTION)
- Extended StaffPermission enum (SEND_NOTIFICATIONS, MANAGE_INFO_BOARD, MODERATE_FORUM)
- Extended AuditEventType with Sprint 7 events
- Flyway V11: notification_sends + notification_send_recipients tables
- NotificationSend + NotificationSendRecipient entities
- NotificationSendRepository + NotificationSendRecipientRepository
- Extended NotificationService with sendBroadcast() and sendToSelected()
- NotificationComposeController (POST /compose, GET /sends)
- ComposeNotificationRequest DTO

Phase 1B (Push Infrastructure):
- Flyway V12: device_tokens + notification_preferences tables
- DeviceToken entity + DevicePlatform enum
- NotificationPreference entity + NotificationChannel enum
- DeviceTokenRepository + NotificationPreferenceRepository
- DeviceRegistrationService (register/unregister/list devices, max 10 per user)
- NotificationPreferenceService (get/create defaults, update, IN_APP always on)
- NotificationDispatchService (multi-channel fan-out: WebSocket, Web Push, FCM, Email)
- WebPushSender (VAPID-based, simplified for MVP)
- FcmPushSender (graceful degradation if not configured)
- PushPayload DTO
- DeviceRegistrationController (POST/GET/DELETE /devices, GET /vapid-key)
- NotificationPreferenceController (GET/PUT /preferences)
- ConsentType extended (NOTIFICATION_PUSH, NOTIFICATION_EMAIL)
- TargetType enum (ALL, SELECTED)

Frontend:
- Updated sw.js with push event handler + notification click handler
- push-subscription.ts (subscribeToPush, unsubscribe, permission helpers)
- notification-compose.ts service (compose, sends, devices, preferences APIs)
- i18n keys (de.json + en.json) for compose, preferences, push, devices

Configuration:
- application-docker.properties: VAPID + FCM push config properties
- MemberRepository: added findAllActiveUserIds() for broadcast
2026-06-13 19:25:19 +02:00
Patrick Plate 329b7abb18 fix: replace shadboard.svg with Cannabis leaf icon from lucide-react
Uses the same Cannabis icon as the login page for consistent branding.
Removed unused next/image imports.
2026-06-13 17:53:02 +02:00
Patrick Plate 7fe8d4f707 fix: rebrand Shadboard → CannaManage, staff permissions UX
- Sidebar, footer, bottom-bar-header: replaced 'Shadboard' with 'CannaManage'
- Footer: removed 'Designed by Qualiora' attribution
- Staff permissions: single-column layout, alphabetically sorted by label
- Edit permissions dialog: useEffect syncs state when dialog opens
  (fixes pre-fill not working when controlled externally)
2026-06-13 17:45:31 +02:00
Patrick Plate 9aaf771469 fix: consent banner fails open on API error (500/403)
The consent check endpoint (/consent/check) returns 500 via the
proxy when the backend returns 403 (missing JWT forwarding).
Previously this caused the banner to show permanently since
consentCheck was undefined. Now isError = true hides the banner
(fail-open strategy — don't block users when backend is unavailable).
2026-06-13 17:30:19 +02:00
Patrick Plate 27690a836e fix: consent banner dismiss on decline + short viewport layout
Bug 1: Clicking 'Ablehnen' now properly dismisses the dialog by calling
the delete account mutation and signing out (previously it redirected to
/settings/privacy which re-rendered the banner in a loop).

Bug 2: Restructured the dialog layout with flex-col + overflow-y-auto on
the content area only. Header and action buttons are pinned (shrink-0)
so they're always accessible on short viewports. Added max-h constraint
with min() to cap at 600px or 90vh.
2026-06-13 17:11:20 +02:00
Patrick Plate cd77eb6448 fix: correct BCrypt hash in seed SQL and fix Playwright test selectors
Root cause: The BCrypt hash in init.sql was the famous Stack Overflow
hash of 'password' (a0),
not the hash of 'test123' as documented.

Also fixed three test issues in system-test.spec.ts:
1. waitForURL regex /dashboard|\//' matched any URL with '/' (instant resolve)
   → replaced with predicate that waits for URL to not contain /login
2. Reports locator used invalid Playwright selector syntax
   → fixed to use proper :has-text() selector for 'Berichte'
3. Navigation test used 'nav a' but app uses shadcn data-sidebar
   → broadened selector to include [data-sidebar] a
4. Console error filter excluded only favicon/maps/hydration
   → also exclude 'Failed to load resource' and 'MISSING_MESSAGE'
   (pre-existing issues from incomplete API endpoints)
2026-06-13 17:01:56 +02:00
Patrick Plate 52251cf711 fix(api): resolve consent/dsgvo 'User not found' — principal is userId not email
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
ConsentController.resolveUserId() and DsgvoController.resolveUserId() read
auth.getName() as an email and did findByEmailAndTenantId(...), but JwtAuthFilter
sets the Authentication principal to the userId (UUID) — the JWT subject is the
userId, not the email. So auth.getName() returns a UUID string, the email lookup
never matched, and every consent/dsgvo call threw 'User not found' (404/500).

This made the DSGVO consent banner unusable: /consent/check 404'd (banner always
shown) and clicking Accept POSTed /consent which 500'd with no UI feedback — the
button appeared to 'not react'.

Fix: parse auth.getName() as the userId UUID directly and verify existsById.
2026-06-13 10:52:43 +02:00
Patrick Plate 26a77b5e16 docs: record 'Oops' crash fix (intl + PWA middleware) verified via Playwright
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
2026-06-13 10:45:44 +02:00
Patrick Plate 4be9c4cf2c fix(frontend): resolve app-wide 'Oops' crash + PWA middleware interception
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Root cause (found via Playwright browser probe — curl could not detect client-side
hydration errors):

1. ROOT-LAYOUT INTL CRASH (the 'Oops' on every page incl /login):
   app/layout.tsx renders global client components (PwaInstallPrompt → useTranslations,
   Toaster, Sonner) as siblings of {children} inside <Providers>, but only each
   route-group layout wrapped its own children in NextIntlClientProvider. So those
   global components mounted with NO intl context → 'No intl context found' → React
   hydration crash → global-error 'Oops'. Fix: wrap the root body in
   NextIntlClientProvider via getMessages() (RootLayout now async). Nested providers
   stay valid (next-intl supports nesting).

2. PWA MIDDLEWARE INTERCEPTION (manifest.json syntax error + stale cache):
   middleware matcher did not exclude /manifest.json or /sw.js, so unauthenticated
   browsers got 307→/login (HTML) for both. Browser parsed HTML as JSON
   ('manifest.json:1 Syntax error') and an HTML/old service worker kept serving
   stale bundles ('website hasn't changed' after redeploys). Fix: exclude
   manifest.json, sw.js, icons, offline from the matcher.

3. SERVICE-WORKER STALE CACHE: bump CACHE_NAME v1→v2 so the activate handler purges
   old cached bundles from clients that loaded the broken build.

Also adds scripts/debug/dashboard-probe.mjs — a Playwright probe that logs in and
captures real client-side console/network errors + screenshot.
2026-06-13 10:36:09 +02:00
Patrick Plate 2347a7a1d9 docs: record auth fixes — login verified end-to-end (admin@test.de)
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
2026-06-13 10:14:21 +02:00
Patrick Plate 281adda27c fix(frontend): align NextAuth authorize() with flat backend LoginResponse
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Login reached the backend (HTTP 200) but NextAuth returned CredentialsSignin.
Cause: authorize() read data.member.id/email/clubName/clubId, but the backend
LoginResponse is flat — { accessToken, refreshToken, expiresIn, role } with no
member object. Accessing data.member.id on undefined threw, so authorize()
returned null.

Decode the JWT payload to recover identity claims (sub=userId, email,
tenant_id=clubId) and use the flat top-level role. Adds a small decodeJwtPayload
helper (claims only, no signature verification needed here).
2026-06-13 10:10:48 +02:00
Patrick Plate dac884c4fe fix(deploy): use valid base64 JWT secret in docker-compose
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
After a successful login the backend returned HTTP 500:
io.jsonwebtoken.io.DecodingException: Illegal base64 character: '-'.
JwtService.getSigningKey() does Decoders.BASE64.decode(secret) before building
the HMAC key (JJWT 0.12 convention). The compose secret was the plaintext
'docker-dev-secret-key-minimum-32-characters-long-for-hmac', which contains
hyphens and is not valid base64, so token signing threw once auth succeeded.

Replace with a proper base64 value (openssl rand -base64 48). The base
application.properties default was already correctly base64-encoded; only the
docker override was wrong.
2026-06-13 10:08:34 +02:00
Patrick Plate 6570ea364a docs: mark CannaManage deploy RESOLVED — live on TrueNAS:3000
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Records the three real root causes (BASE_URL metadataBase, Spring Boot 4
Flyway starter, mail health indicator) and the 8080->8081 host port remap.
2026-06-13 10:01:39 +02:00
Patrick Plate 60844efaba fix(api): disable mail health indicator in docker profile
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Backend now starts and Flyway migrations run, but /actuator/health returned
503 (DOWN) so Docker marked the container unhealthy and the frontend refused
to start. Cause: spring-boot-starter-mail registers a mail health indicator
that tries to connect to localhost:1025; there is no SMTP container in this
deployment, so it reports DOWN and drags the aggregate health to DOWN.

Disable the mail health indicator in the docker profile. Mail being down must
not make the whole service unhealthy in an SMTP-less deployment.
2026-06-13 09:57:01 +02:00
Patrick Plate 8490da4705 fix(api): add spring-boot-starter-flyway for Spring Boot 4 migrations
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Backend crashed on startup with 'Schema validation: missing table
[audit_events]'. Root cause: this is Spring Boot 4.0.6, which modularized
autoconfiguration. FlywayAutoConfiguration moved out of spring-boot-autoconfigure
into a dedicated spring-boot-flyway module that is only pulled in by
spring-boot-starter-flyway. The pom only had flyway-database-postgresql
(+ transitive flyway-core) but NOT the starter, so spring.flyway.enabled=true
was inert: no migrations ran, flyway_schema_history was never created, and
Hibernate ddl-auto=validate failed on the empty schema.

Adds spring-boot-starter-flyway (autoconfigure module + flyway-core); keeps
flyway-database-postgresql for the Postgres dialect.
Ref: https://spring.io/blog/2025/10/28/modularizing-spring-boot/
2026-06-13 09:52:22 +02:00
Patrick Plate f6a7143d1b fix(frontend): guard metadataBase against undefined BASE_URL
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Root cause of the SSG/page-data build crash: src/app/layout.tsx evaluated
new URL(process.env.BASE_URL) at module load. BASE_URL was never set as a
build-time ENV, so it was undefined -> ERR_INVALID_URL, input: 'undefined'.
Because this is the root layout, its metadata is collected for every route,
explaining why both /impressum (marketing) and /portal-login (non-marketing)
failed identically. The earlier NextAuth/middleware/force-dynamic fixes could
not help because metadata evaluation happens before any of that.

- layout.tsx: fall back to http://localhost:3000 when BASE_URL is unset
- Dockerfile: add BASE_URL build-time placeholder (matches AUTH_URL pattern)
2026-06-13 09:44:21 +02:00
Patrick Plate 1eead286ba docs: add Roo handover doc for TrueNAS Docker deploy session
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
2026-06-13 09:40:02 +02:00
Patrick Plate 9a4df56eaf fix(frontend): exclude marketing routes from NextAuth middleware matcher
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
2026-06-13 09:39:17 +02:00