Commit Graph

77 Commits

Author SHA1 Message Date
Marcel
d91a10ef8e feat(backend): add V16 migration for notifications table and user preference columns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 19:55:40 +01:00
Marcel
d6f4ea05d9 feat(#68): fall back to filename as title when createDocument gets no title
When a document is created without an explicit title (null or blank),
the service now derives the title from the uploaded filename using the
same titleFromFilename() logic already used by storeDocument — stripping
the extension for plain names and formatting structured names as
"Firstname Lastname (DD.MM.YYYY)".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:51:24 +01:00
Marcel
99e3163c0e feat(quick-upload): pre-fill date and sender from structured filename
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 2m28s
CI / Backend Unit Tests (pull_request) Successful in 2m14s
CI / E2E Tests (pull_request) Failing after 28m25s
storeDocument() now uses the ParsedFilename record to also set
documentDate and sender on new quick-uploads. Sender lookup is
an exact case-insensitive first+last name match — no new persons
are created. Unmatched filenames behave as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 15:43:39 +01:00
Marcel
f0940524e7 feat(filename): support compound last names like de Gruyter
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 2m17s
CI / Backend Unit Tests (pull_request) Successful in 2m13s
CI / E2E Tests (pull_request) Failing after 25m0s
Replace the four fixed regexes with a split-based algorithm:
- first segment = date → last segment = firstName, rest = lastName parts
- last segment = date → second-to-last = firstName, rest = lastName parts

18881025_de_Gruyter_Walter.pdf now correctly yields "Walter de Gruyter".
Simple two-segment names behave identically to before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 15:33:21 +01:00
Marcel
a302f96560 feat(quick-upload): generate better title from structured filename
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 2m16s
CI / Backend Unit Tests (pull_request) Successful in 2m8s
CI / E2E Tests (pull_request) Failing after 26m22s
titleFromFilename() mirrors the same four patterns as the frontend
parseFilename() utility. Dropzone uploads to Mueller_Hans_19650312.pdf
now land with title "Hans Mueller (12.03.1965)" instead of the raw
stripped filename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 15:18:34 +01:00
Marcel
0ce18e1eed feat(documents): add metadataComplete flag and enrichment queue endpoints
Adds a metadata_complete column (default true for existing rows) to drive
the enrichment queue. New drop-zone uploads always start as false; createDocument
uses an explicit DTO flag or a heuristic (any of date/sender/receivers present →
true); the mass importer applies the same heuristic per row.

New endpoints: GET /api/documents/incomplete-count, /incomplete, /incomplete/next.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 13:25:57 +01:00
Marcel
d078ad8224 feat(upload): warn on duplicate filename with link to existing document
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- storeDocument now returns StoreResult(document, isNew) to distinguish
  new uploads from updates to existing documents
- QuickUploadResult gains an `updated` list alongside `created`
- Frontend shows an amber warning with a "View document" link for duplicates
  instead of silently re-uploading and leaving the user confused

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 11:31:31 +01:00
Marcel
0795e4099f fix(delete): add cascade deletes and fix SvelteKit named action conflict
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- Add V14 migration: ON DELETE CASCADE for document_tags and document_receivers
  so deleting a document removes its join-table rows automatically
- Rename default form action to 'update' in the edit page — SvelteKit forbids
  mixing a default action with named actions (was causing 500 on delete)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 11:12:21 +01:00
Marcel
91a29d501d feat(documents): add delete button to document edit form
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- DELETE /api/documents/{id} endpoint (204 No Content, WRITE_ALL required)
- DocumentService.deleteDocument() — throws 404 if not found, cascades
  via DB foreign keys (versions, annotations, comments all ON DELETE CASCADE)
- Delete form action in edit page server: redirects to / on success
- Two-step confirmation in the save bar: first click reveals inline
  "Wirklich löschen?" + confirm/cancel, avoiding native browser dialogs
- i18n key doc_delete_confirm added to de/en/es

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 10:52:43 +01:00
Marcel
963807ff05 fix(upload): structured error codes for quick-upload, fix duplicate filename crash
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- Switch errors from plain strings to { filename, code } objects so the
  frontend can show translated messages instead of raw exception text
- Add UNSUPPORTED_FILE_TYPE error code end-to-end (Java enum → errors.ts
  → de/en/es messages)
- Fix IncorrectResultSizeDataAccessException when a filename exists more
  than once in the DB: use findFirstByOriginalFilename instead of
  findByOriginalFilename in storeDocument()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 10:38:30 +01:00
Marcel
6a663cefe6 fix(search): sort document overview by createdAt DESC instead of documentDate ASC
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Newly uploaded documents (from bulk drop-zone or Excel import) have no
documentDate, so they were sinking to the bottom. Sorting by createdAt
DESC puts the most recently added documents first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 10:32:46 +01:00
Marcel
332b5b3c40 feat(upload): add POST /api/documents/quick-upload endpoint for bulk file upload
Adds a new multipart endpoint that accepts multiple files and creates one
document per file without requiring any form metadata. Each document gets
title = filename-without-extension and status = UPLOADED.

- Fix storeDocument() to strip the file extension from the document title
- Validate content type (PDF/JPEG/PNG/TIFF) server-side; unsupported files
  are skipped and returned as per-file errors in QuickUploadResult
- Tests cover 401/403 auth, success path, and unsupported file type

Closes #66 (backend part)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 09:59:59 +01:00
Marcel
399fa36f60 fix(e2e): reset admin password to configured value on every e2e backend startup
The password-reset E2E test changes the admin password mid-test and relies on a
UI step to restore it. If that step fails or the test is interrupted the account
is left with the wrong password, locking out all subsequent runs.

Fix: in DataInitializer.initE2EData (e2e profile only), always reset the admin
password to the value from ${app.admin.password} (default: admin123) on startup.
This is idempotent — it is safe to run even when the password is already correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:47:56 +01:00
Marcel
0ec86220d3 feat(backend): add POST /api/admin/backfill-file-hashes endpoint
- DocumentRepository: findByFileHashIsNullAndFilePathIsNotNull()
- AnnotationRepository: findByDocumentIdAndFileHashIsNull()
- FileService: downloadFileBytes() downloads raw bytes from S3 for hashing
- AnnotationService: backfillAnnotationFileHashForDocument() sets hash on null-hash annotations
- DocumentService: backfillFileHashes() iterates documents with null hash,
  downloads bytes, computes SHA-256, saves doc, then propagates hash to annotations
- AdminController: POST /api/admin/backfill-file-hashes delegates to DocumentService

Closes #56

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 17:32:29 +01:00
Marcel
93f57477cd feat(backend): hash uploaded files and store hash on documents and annotations
- Flyway V13: add file_hash column to documents and document_annotations
- FileService.uploadFile() now returns UploadResult(s3Key, fileHash) with SHA-256 hash computed from raw bytes
- Document and DocumentAnnotation models gain a fileHash field
- DocumentService propagates the hash at all three upload sites (storeDocument, createDocument, updateDocument)
- AnnotationService.createAnnotation() accepts and persists a fileHash
- AnnotationController resolves the document's hash and passes it through

Closes #55

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 17:08:55 +01:00
Marcel
3e5d296b09 feat(comments): add CommentController and CreateCommentDTO (green)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:36:33 +01:00
Marcel
ee49bac2ef test(comments): add failing CommentControllerTest (red)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:34:47 +01:00
Marcel
48040dc7e4 feat(comments): add DocumentComment entity, CommentRepository, and CommentService (green)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:33:39 +01:00
Marcel
83e5a1fde5 test(comments): add failing CommentServiceTest and V12 migration (red)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:32:11 +01:00
Marcel
37f5c3d005 feat(db): add migration to grant ANNOTATE_ALL to existing admin groups
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 2m27s
CI / Backend Unit Tests (pull_request) Successful in 2m12s
CI / E2E Tests (pull_request) Successful in 23m43s
CI / Unit & Component Tests (push) Successful in 2m28s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / E2E Tests (push) Successful in 22m17s
Covers existing deployments where the Administrators group was created
before DataInitializer started including ANNOTATE_ALL.

Refs #40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 08:52:32 +01:00
Marcel
ea1c097ae0 fix(e2e): activate e2e profile in dev mode and create reader user idempotently
- Add e2e to the dev Maven profile's spring.profiles.active so
  DataInitializer always runs when developing/testing locally
- Create the reader test user independently of the person-seed guard
  so it survives restarts where seed data already exists
- Set SPRING_PROFILES_ACTIVE=dev,e2e in docker-compose backend service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 08:25:54 +01:00
Marcel
b45ec744b2 feat: add PDF annotation feature (#40)
Backend:
- Add ANNOTATE_ALL permission
- Add ANNOTATION_NOT_FOUND and ANNOTATION_OVERLAP error codes
- V10 migration: document_annotations table with page/rect/color/owner
- DocumentAnnotation entity, AnnotationRepository, CreateAnnotationDTO
- AnnotationService: overlap detection (rectangle intersection), ownership enforcement on delete
- AnnotationController: GET (authenticated), POST/DELETE (ANNOTATE_ALL)
- 15 new tests (AnnotationServiceTest, AnnotationControllerTest) — TDD red/green

Frontend:
- AnnotationLayer.svelte: pointer-event drawing, colored rect overlays, delete buttons
- PdfViewer.svelte: annotate toggle, color picker, loads/saves/deletes annotations via API
- Disabled annotate button with tooltip for users without ANNOTATE_ALL
- canAnnotate exposed from layout server, passed to PdfViewer
- errors.ts + de/en/es translations for new error codes
- 3 new unit tests for AnnotationLayer — TDD red/green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:27:21 +01:00
Marcel
3e65b2feb3 feat: add admin backfill-versions endpoint for documents without history
Adds POST /api/admin/backfill-versions which creates an initial snapshot
(editorName="Datenimport", changedFields=[]) for every document that has
no version entry yet, using the document's createdAt as the version timestamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:27:21 +01:00
Marcel
7af49daf9c fix: use tools.jackson (Jackson 3) instead of com.fasterxml.jackson in DocumentVersionService
Spring Boot 4 auto-configures a tools.jackson.databind.ObjectMapper bean.
The service was importing the Jackson 2 package, causing a no-qualifying-bean
error at startup.

Refs #38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 11:41:16 +01:00
Marcel
28256dbd08 feat: wire document versioning into DocumentService and DocumentController
DocumentService now calls documentVersionService.recordVersion() after
createDocument and updateDocument. DocumentController exposes two new
read-only endpoints: GET /{id}/versions and GET /{id}/versions/{versionId}.

Refs #38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 11:30:05 +01:00
Marcel
315b368f88 feat: add DocumentVersion entity, repository, service, and migration
Creates the document_versions table (V9) with JSONB snapshot and
changed_fields columns. DocumentVersionService records a version on
every create/update, resolves the editor name from the security context,
and computes changedFields by diffing against the previous snapshot.

Refs #38
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 11:29:41 +01:00
Marcel
b9aff799fa fix(e2e): use username check instead of count() for admin user creation
Some checks failed
CI / Unit & Component Tests (push) Successful in 2m3s
CI / Backend Unit Tests (push) Successful in 2m5s
CI / E2E Tests (push) Has started running
CI / Unit & Component Tests (pull_request) Successful in 2m5s
CI / Backend Unit Tests (pull_request) Successful in 1m56s
CI / E2E Tests (pull_request) Failing after 18m40s
When the e2e profile is active, initE2EData (which creates a reader user)
can run before initAdminUser. The old count() == 0 guard then skips admin
creation entirely, causing every login test to fail with 401.

Switch to findByUsername(adminUsername).isEmpty() so the admin is created
regardless of which CommandLineRunner runs first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 08:46:13 +01:00
Marcel
5f49a5787c feat(backend): add password reset via email
- Add PasswordResetToken entity, repository (Flyway V8 migration)
- PasswordResetService: token generation, validation, nightly cleanup
- AuthController: POST /api/auth/forgot-password and /api/auth/reset-password (both permitAll)
- AuthE2EController (@Profile("e2e")): GET /api/auth/reset-token-for-test for CI testing
- spring-boot-starter-mail dependency; JavaMailSender optional (@Autowired required=false)
- mail health indicator disabled; mail config via MAIL_HOST/PORT/USERNAME/PASSWORD env vars
- 5 unit tests written TDD-style (all pass)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 07:26:35 +01:00
Marcel
2a46136f61 test(e2e): seed read-only "reader" user in e2e profile
Adds a "Leser" group (READ_ALL only) and "reader" / "reader123"
user to the deterministic e2e seed so the permissions spec can log
in as a read-only user without relying on admin-created test data.

Refs #48
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 19:59:07 +01:00
Marcel
fb4f8e820c feat(admin): add dedicated routes for admin user management (#37)
Some checks failed
CI / Unit & Component Tests (push) Successful in 2m4s
CI / Backend Unit Tests (push) Successful in 1m59s
CI / E2E Tests (push) Failing after 18m4s
CI / Unit & Component Tests (pull_request) Successful in 2m2s
CI / Backend Unit Tests (pull_request) Successful in 2m0s
CI / E2E Tests (pull_request) Failing after 16m10s
- New GET /admin/users/new page: create user with all profile fields
  (login, password, firstName, lastName, birthDate, email, contact, groups)
- New GET /admin/users/[id] page: edit user profile, groups, and
  optional password change without requiring current password
- New PUT /api/users/{id} backend endpoint (ADMIN_USER permission)
  with AdminUpdateUserRequest DTO for admin-override user updates
- Refactored admin users tab: replaced inline editing with edit links
  to dedicated routes; create button now links to /admin/users/new
- Extended CreateUserRequest with profile fields so new users can be
  created with full profile data in a single request
- Added 28 component tests across 3 new spec files (TDD)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 16:33:50 +01:00
Marcel
2f803b2740 feat(backend): add user profile fields and profile/password endpoints
Add firstName, lastName, birthDate, contact to AppUser via V7 migration.
Add PUT /api/users/me and POST /api/users/me/password endpoints.
Add GET /api/users/{id} for public profile lookup.
Add EMAIL_ALREADY_IN_USE and WRONG_CURRENT_PASSWORD error codes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:02:55 +01:00
Marcel
0525e66d55 feat(conversations): filter person typeahead to correspondents of selected person
All checks were successful
CI / E2E Tests (push) Successful in 18m17s
CI / Unit & Component Tests (push) Successful in 3m37s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / Unit & Component Tests (pull_request) Successful in 2m12s
CI / Backend Unit Tests (pull_request) Successful in 2m1s
CI / E2E Tests (pull_request) Successful in 15m17s
Closes #29

Backend:
- Add PersonRepository.findCorrespondents / findCorrespondentsWithFilter
  (native SQL, orders by shared document count DESC, limit 10)
- Add PersonService.findCorrespondents(personId, q) delegating to the
  correct repository method based on whether a query string is present
- Expose GET /api/persons/{id}/correspondents?q= in PersonController

Frontend:
- Add optional restrictToCorrespondentsOf prop to PersonTypeahead
- On focus with the prop set, fetch correspondents immediately (no typing
  required) — opens the dropdown showing top correspondents
- On input with the prop set, hit the correspondents endpoint with q= param
- Without the prop, keep existing /api/persons?q= behaviour unchanged
- Wire the prop bidirectionally in /conversations: sender restricts receiver
  and vice versa

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:23:11 +01:00
Marcel
c006113db9 refactor(backend): replace 7-parameter updatePerson with PersonUpdateDTO
Reduces parameter count from 7 to 2 (id + dto), keeping all validation
and trimming logic in the service. Controller now binds request JSON
directly to the DTO via @RequestBody.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:52:16 +01:00
Marcel
5160009175 refactor(backend): fix typos, dead debug logs, and bare orElseThrow calls
- UserService: remove debug log dumping all DB groups ("Groupds in DB"),
  fix indentation of createUserOrUpdate, clean up log messages
- DocumentService: fix typo reciever → receiver in searchDocuments parameter,
  remove broken log.info("Tags", tags) with missing format specifier,
  replace bare orElseThrow() with DomainException in updateDocumentTags
  and createDocument, remove what-comments on Lombok annotations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:50:16 +01:00
Marcel
557b62ac5c feat(backend): add received-documents endpoint for persons
Add findByReceiversId to DocumentRepository, getDocumentsByReceiver
to DocumentService, and GET /api/persons/{id}/received-documents
to PersonController. Tests added for both service and controller layers.

Closes #1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:39:18 +01:00
Marcel
6e6663376d fix(migrations): make V5/V6 idempotent with IF NOT EXISTS
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Avoids Flyway errors when columns already exist in the DB due to
migration history mismatches from parallel feature branches.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:14:44 +01:00
Marcel
041bbdc2e6 merge(feat/person-birth-death-years): resolve conflicts with main, bump migration to V6
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 1m45s
CI / Backend Unit Tests (pull_request) Successful in 2m4s
CI / E2E Tests (pull_request) Failing after 18m40s
CI / Unit & Component Tests (push) Successful in 1m57s
CI / Backend Unit Tests (push) Successful in 2m13s
CI / E2E Tests (push) Failing after 18m39s
Resolves merge conflicts with main (feat/person-notes merged first).
Combines both features: birth/death years and notes field on person detail.
Renames migration V5__add_birth_death_years to V6 to avoid Flyway conflict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:04:44 +01:00
Marcel
08f7ae9a5c feat(persons): add notes field to person profile (issue #23)
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
V5 Flyway migration adds TEXT notes column; Person entity, service, and
controller updated to persist notes. Frontend edit form adds textarea and
view mode renders the notes section. Backed by 2 new service unit tests
(persist + blank clears).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:57:05 +01:00
Marcel
b07391541b feat(persons): add birth/death year fields (issue #18)
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 1m48s
CI / Backend Unit Tests (pull_request) Successful in 2m3s
CI / E2E Tests (pull_request) Failing after 17m10s
V5 Flyway migration adds birth_year and death_year INTEGER columns.
Service validates birthYear <= deathYear (400 otherwise). Frontend edit
form adds year number inputs; view mode renders * year / † year. Backed
by 3 backend service tests and 1 E2E test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:45:02 +01:00
Marcel
6ef7b292cc fix(flyway): baseline existing schemas at V4 on first run
Local dev databases that existed before Flyway was introduced have tables
but no flyway_schema_history. Flyway refuses to migrate a non-empty schema
without a history table. baselineOnMigrate=true with baselineVersion=4
stamps those databases as already at V4 without re-running migrations.
Fresh databases (CI) have an empty schema so the baseline is never
triggered and all 4 migrations run normally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
a65cbf9bae fix(backend): switch base image from alpine to debian for bash compatibility
mvnw is a bash script; eclipse-temurin:21-jdk-alpine only provides ash
(busybox), causing the container to exit silently with code 0 before the
JVM starts. The Debian-based eclipse-temurin:21-jdk image includes bash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
a60905674f fix(backend): explicit Flyway bean to bypass broken auto-configuration
Spring Boot 4.0 Flyway auto-configuration is not triggering in the CI
environment — confirmed by empty DB and no flyway_schema_history table.
Replace YAML-based auto-config with an explicit @Bean that creates and
runs Flyway directly on startup, independent of any auto-configuration
conditions. Disable the auto-config via spring.flyway.enabled=false to
prevent interference. Add @DependsOn("flyway") to DataInitializer to
enforce that CommandLineRunner beans are only registered after migrations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
e6db43850b fix(security): permit /actuator/health without authentication
The CI health check (curl -sf) and Docker Compose health check (wget)
both hit /actuator/health unauthenticated. With anyRequest().authenticated()
the endpoint returned 401, curl -f treated it as failure, and the health
check loop never exited successfully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
802f1ab0e0 fix(backend): explicit Flyway config and DataInitializer null title fix
Adding explicit spring.flyway.* config (url/user/password) ensures Flyway
creates its own JDBC connection and runs migrations independently of the JPA
datasource initialization order in Spring Boot 4.0.

Fix DataInitializer creating a Document with title=null, which would hit the
NOT NULL constraint in the documents table once the admin user init succeeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
9b67db74eb feat: auto-start Spring Boot backend via docker-compose
Replace the devcontainer (sleep infinity + VS Code image) with a proper
dev setup:
- Dockerfile: eclipse-temurin:21-jdk-alpine running ./mvnw spring-boot:run
- Source mounted at /app, Maven deps cached in named volume maven_cache
- Healthcheck on /actuator/health so frontend waits until backend is ready
- frontend depends_on backend: service_healthy (was service_started)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:03:14 +01:00
Marcel
225d6e44c9 test(backend): add service unit tests and controller slice tests
Some checks failed
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
Service unit tests (Mockito, no Spring context):
- DocumentServiceTest — getById, updateDocument, deleteTagCascading, createPlaceholder
- PersonServiceTest — getById, findOrCreateByAlias, mergePersons
- TagServiceTest — getById, findOrCreate, update, delete
- UserServiceTest — findByUsername, deleteUser, createUserOrUpdate, getGroupById

Controller slice tests (@WebMvcTest):
- DocumentControllerTest — 401/403/200 for GET /search, POST /, PUT /{id}
- TagControllerTest — 401/403/200 for GET, PUT /{id}, DELETE /{id}

Also removes FamilienarchivApplicationTests (full @SpringBootTest
requires DB + S3 infrastructure; covered by e2e job instead).

65 tests total, all passing.
Refs #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 21:33:24 +01:00
Marcel
7cb20dec50 test: add e2e tests 2026-03-17 13:34:05 +00:00
Marcel
973620a097 build: add mvn properties 2026-03-17 13:33:02 +00:00
Marcel
25e095ea47 refactor: enforce Controller → Service → Repository layering throughout backend
- Created TagService: encapsulates all tag find/create/update/delete operations
- Extended PersonService: added findAll(), getById(), getAllById(), findOrCreateByAlias()
- Extended UserService: added createGroup(), updateGroup(), deleteGroup(), getGroupById()
- DocumentService: replaced direct PersonRepository/TagRepository access with
  PersonService/TagService calls; added getDocumentById(), getDocumentsBySender(),
  getConversationFiltered(), deleteTagCascading()
- MassImportService: replaced PersonRepository/TagRepository with PersonService/TagService
- PersonController: removed direct repo injections, delegates to PersonService/DocumentService
- DocumentController: removed DocumentRepository injection, delegates to DocumentService
- TagController: removed TagRepository/DocumentRepository, delegates to TagService/DocumentService
- GroupController: removed UserGroupRepository injection, delegates to UserService

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 08:49:33 +01:00
Marcel
97e5255d7f refactor: move createPerson logic into PersonService
Controller was directly calling personRepository.save() for person creation.
Extracted into PersonService.createPerson() to enforce Controller → Service → Repository layering.
Also documented the layering rules in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 08:28:30 +01:00