fix: correct malformed @Value annotations in DataInitializer

Missing closing braces caused Spring to inject the literal placeholder
string instead of resolving the property, silently ignoring any
app.admin.username / app.admin.password env-var overrides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-15 12:16:00 +01:00
parent e63adb964d
commit 09ec2103c8
5 changed files with 573 additions and 2 deletions

View File

@@ -6,6 +6,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**Familienarchiv** is a family document archival system — a full-stack web app for digitizing, organizing, and searching family documents. Key features: file uploads (stored in MinIO/S3), metadata management, Excel batch import, full-text search, conversation threads between family members, and role-based access control.
## Collaboration Principles
**Be honest and objective**: Evaluate all suggestions, ideas, and feedback on their technical merits. Don't be overly complimentary or sycophantic. If something doesn't make sense, doesn't align with best practices, or could be improved, say so directly and constructively. Technical accuracy and project quality take precedence over being agreeable.
## Core Workflow: Research → Plan → Implement → Validate
**Start every feature with:** "Let me research the codebase and create a plan before implementing."
1. **Research** - Understand existing patterns and architecture
2. **Plan** - Propose approach and verify with you
3. **Implement** - Build with tests and error handling
4. **Validate** - ALWAYS run formatters, linters, and tests after implementation
- Whenever working on a feature or issue, let's always come up with a plan first, then save it to a file called `/.agent/current-plan.md`, before getting started with code changes. Update this file as the work progresses.
- Let's use pure functions where possible to improve readability and testing.
## Stack
- **Backend**: Spring Boot 4.0 (Java 21, Maven, Jetty, JPA/Hibernate, Flyway, Spring Security, Spring Session JDBC)

View File

@@ -30,10 +30,10 @@ import java.util.concurrent.ThreadLocalRandom;
@Slf4j
public class DataInitializer {
@Value("${app.admin.username:admin")
@Value("${app.admin.username:admin}")
private String adminUsername;
@Value("${app.admin.password:admin123")
@Value("${app.admin.password:admin123}")
private String adminPassword;
private final AppUserRepository userRepository;

141
docs/TODO-backend.md Normal file
View File

@@ -0,0 +1,141 @@
# Backend TODO
Findings from architectural review. Ordered roughly by severity.
---
## Bugs
### `@Value` annotation missing closing brace
**File:** `config/DataInitializer.java:3337`
```java
// Current (broken — Spring may silently fail to resolve the property)
@Value("${app.admin.username:admin")
@Value("${app.admin.password:admin123")
// Fix
@Value("${app.admin.username:admin}")
@Value("${app.admin.password:admin123}")
```
Spring's `@Value` SpEL parser requires the closing `}`. Without it, the literal string `"${app.admin.username:admin"` is injected instead of the resolved value. The default credentials will appear to work but env-var overrides will silently be ignored.
---
### Default credentials logged in plaintext
**File:** `config/DataInitializer.java:64`
```java
log.info("Default Admin erstellt: User='admin', Pass='admin123'");
```
The password appears in application logs. Remove the password from the log statement entirely. Log only the username.
---
## Design Issues
### Test data runs in every environment
**File:** `config/DataInitializer.java:69143`
`initData` seeds 500 fake documents and 4 random persons whenever the database is empty. This runs unconditionally — including in production — and the log message at line 141 claims "50 Personen" when only 4 are created (copy/paste error).
**Fix:** Guard the bean with a Spring profile so it only runs locally:
```java
@Bean
@Profile("dev")
public CommandLineRunner initData(...) { ... }
```
Set `spring.profiles.active=dev` in `application.properties` for local use, and ensure the production environment does not set that profile.
---
### Two redundant permission groups created on startup
**File:** `config/DataInitializer.java:4953` and `101105`
`initAdminUser` creates group `"Administrators"` with `{ADMIN, READ_ALL, WRITE_ALL}`.
`initData` creates group `"Admins"` with `{READ_ALL, WRITE_ALL, ADMIN}` — identical permissions, different name, different bean.
Both beans run when the DB is empty, resulting in two duplicate admin groups and the admin user only belonging to the first one. `initData` is also guarded by `personRepo.count() > 0` — so if persons already exist it won't create the second group, making startup behaviour inconsistent.
**Fix:** Consolidate into a single `CommandLineRunner` bean or extract group creation into a shared `@PostConstruct` method. Remove `initData`'s group creation entirely (it belongs in `initAdminUser`).
---
### CSRF protection disabled with no plan to re-enable it
**File:** `config/SecurityConfig.java:39`
```java
.csrf(csrf -> csrf.disable())
```
The comment says "for development". HTTP Basic Auth over a cookie-authenticated SvelteKit frontend is still vulnerable to CSRF for mutating endpoints (PUT, DELETE, POST).
**Fix:** Either:
- Re-enable CSRF with `CookieCsrfTokenRepository` and pass the token to the frontend, or
- Adopt a stateless JWT/session approach and verify `Origin`/`Referer` headers
At minimum, document this as a known gap that must be addressed before any external deployment.
---
### Spring Session tables are created but never used
**File:** `db/migration/V1__initial_schema.sql` (Spring Session tables), `config/SecurityConfig.java`
The schema includes `spring_session` and `spring_session_attributes` tables, but the auth model is stateless HTTP Basic — credentials are re-validated from the database on every request. Spring Session JDBC is wiring itself up but managing nothing meaningful.
**Fix:** Decide on one model:
- **Keep Basic Auth (stateless):** Remove the Spring Session JDBC dependency and tables. Each request re-authenticates — which is fine for a small internal app.
- **Switch to session-based auth:** Replace Basic Auth with form login, issue a server-side session ID, and let Spring Session manage it. This reduces per-request DB hits.
---
### `Permission` enum not used consistently — permissions are plain strings
**File:** `security/Permission.java`, `security/RequirePermission.java`, `security/PermissionAspect.java`
`@RequirePermission` takes a `Permission` enum value, but user group permissions are stored and compared as raw `String` values (e.g., `"ADMIN"`, `"READ_ALL"`). The comparison in `PermissionAspect` calls `permission.name()` to convert the enum back to a string for matching — so type-safety is only at the annotation call site, not end-to-end.
**Fix:** Make the entire stack type-safe: store permissions as the enum's name (which is already happening) but also parse them back into `Permission` enum values when loading `UserDetails` authorities. This way a typo in the DB is caught at load time rather than silently failing permission checks.
---
### `MassImportService` provides no status or error feedback
**File:** `service/MassImportService.java`, `controller/AdminController.java`
`/api/admin/trigger-import` returns immediately (async), but there is no way for the admin to know whether the import succeeded, failed, or is still running. Errors during async execution are silently swallowed.
**Fix options:**
- Store import job status in a DB table (`import_jobs`) with state (`RUNNING`, `DONE`, `FAILED`) and expose a `GET /api/admin/import-status` endpoint
- Alternatively, make the endpoint synchronous since it already blocks on file I/O — only use async if you need true non-blocking behaviour
---
## Missing Capabilities
### No test coverage
**File:** `src/test/java/org/raddatz/familienarchiv/FamilienarchivApplicationTests.java`
The only test is a Spring context load test. No unit or integration tests exist for any service, repository, or controller logic.
**Suggested starting points (highest value for effort):**
1. `DocumentSpecifications` — pure logic, easy to unit test with an in-memory H2 or Testcontainers PostgreSQL
2. `ExcelService` — parsing logic, test with fixture `.xlsx` files (one exists in `api_tests/`)
3. `PermissionAspect` — security logic should be tested; use `@WithMockUser` from Spring Security Test
---
### No API documentation
There is no OpenAPI/Swagger endpoint. The only documentation is the `.http` files in `backend/api_tests/`.
**Fix:** Add `springdoc-openapi-starter-webmvc-ui` to `pom.xml`. It generates `/swagger-ui.html` and `/v3/api-docs` automatically from existing controller annotations with zero additional code.
---
### `FileService` content-type detection is fragile
File extension is inferred from the S3 key string. Many document types (TIFF scans, HEIC photos, Word documents) are unhandled and fall back to `application/octet-stream`, forcing a download instead of inline display.
**Fix:** Use `java.nio.file.Files.probeContentType()` or Apache Tika for robust MIME detection. Alternatively, store the content-type at upload time in the `Document` entity and retrieve it on download.
---
### Generic error response is hardcoded in German
**File:** `controller/GlobalExceptionHandler.java:36`
```java
.body(new ErrorResponse("Ein Fehler ist aufgetreten"))
```
The fallback error message is hardcoded in German. It should use a locale-aware message source or at minimum be in English as the lingua franca of APIs.

148
docs/TODO-frontend.md Normal file
View File

@@ -0,0 +1,148 @@
# Frontend TODO
Findings from architectural review. Ordered roughly by severity.
---
## Bugs
### Backend URL hardcoded as `localhost` in the session hook
**File:** `src/hooks.server.ts:20`
```ts
const response = await fetch('http://localhost:8080/api/users/me', {
```
The `userGroup` handle (which runs on every request) calls the backend via a hardcoded `localhost:8080` URL. The `API_INTERNAL_URL` env var used in `handleFetch` is not applied here. Inside Docker, `localhost` from the frontend container does not resolve to the backend container — requests will fail silently (the catch swallows the error) and `event.locals.user` will be `undefined` for every request.
**Fix:** Centralise the base URL:
```ts
const API_BASE = env.API_INTERNAL_URL ?? 'http://localhost:8080';
// then use it in both userGroup and handleFetch
```
---
### `import { env } from 'process'` bypasses SvelteKit's env system
**File:** `src/hooks.server.ts:4`
```ts
import { env } from 'process';
```
This is a raw Node.js import that bypasses SvelteKit's `$env/dynamic/private` and `$env/static/private` modules. It won't work if the adapter is ever changed (e.g., to Deno or a serverless edge runtime), and it skips SvelteKit's build-time validation that env vars are actually set.
**Fix:**
```ts
import { env } from '$env/dynamic/private';
```
---
## Design Issues
### Every page load hits the backend twice minimum
**File:** `src/hooks.server.ts:1534`
The `userGroup` hook calls `GET /api/users/me` on every single server-side request to check the session. Then each page's `+page.server.ts` load function makes its own API calls. For a search page that's three sequential backend round-trips before the user sees anything.
**Fix options:**
- Cache the user in the session (if Spring Session is adopted on the backend) — validate the session cookie once, not per request
- If sticking with Basic Auth: trust the cookie value directly and only call `/api/users/me` when the user object is actually needed (e.g., in `+layout.server.ts`), not unconditionally in the hook
---
### Two conflicting package managers
**Files:** `package-lock.json` (npm), `yarn.lock` (Yarn)
Both lock files exist, meaning the project has been installed with both npm and Yarn at different times. This leads to divergent dependency trees and confusing contributor setup.
**Fix:** Pick one (npm is already the default in the devcontainer), delete the other lock file, and add an `.npmrc` or `package.json` `engines` field to enforce it:
```json
"engines": { "npm": ">=10" }
```
---
### i18n is a stub — only German is implemented
**Files:** `messages/en.json`, `messages/es.json`
English and Spanish message files contain only the scaffolding example key `hello_world`. All actual UI strings are rendered directly in German inside Svelte components and are not extracted into the message catalogue.
**Fix options:**
- If multi-language support is a real requirement: extract all German strings from `.svelte` files into `messages/de.json` and provide translations in `en.json` / `es.json`
- If it's not a requirement: remove Paraglide, the `messages/` directory, and the i18n hook to reduce complexity
---
### No `Dockerfile` — frontend cannot run in Docker
**File:** `docker-compose.yml` (frontend service commented out)
The frontend service in `docker-compose.yml` is commented out because no `Dockerfile` exists. The SvelteKit Node adapter is already configured, so producing a Dockerfile is straightforward.
**Fix:** Add `frontend/Dockerfile`:
```dockerfile
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24-alpine
WORKDIR /app
COPY --from=builder /app/build ./build
COPY --from=builder /app/package.json .
EXPOSE 3000
CMD ["node", "build"]
```
Then uncomment and complete the frontend service in `docker-compose.yml`, adding `API_INTERNAL_URL=http://backend:8080`.
---
### Demo routes are leftover scaffolding
**Files:** `src/routes/demo/+page.svelte`, `src/routes/demo/paraglide/+page.svelte`
These routes were generated by the SvelteKit + Paraglide scaffolding tool. They serve no function in the application and are publicly accessible.
**Fix:** Delete `src/routes/demo/` entirely.
---
## Missing Capabilities
### No shared TypeScript types with the backend
The frontend manually constructs objects and parses JSON responses without any type definitions that match the backend's DTOs (`DocumentUpdateDTO`, `GroupDTO`, etc.). A breaking change in the backend API — renaming a field, changing a type — won't be caught until runtime.
**Fix options (pick one):**
- **Simple:** Define matching TypeScript interfaces manually in `src/lib/types.ts` for all backend response shapes and use them in load functions
- **Robust:** Add `springdoc-openapi` to the backend and use `openapi-typescript` to generate types from the OpenAPI spec as part of the build pipeline
---
### No global error page
SvelteKit renders a default unstyled error page for unhandled errors and 404s. There is no `src/routes/+error.svelte` in the project.
**Fix:** Create `src/routes/+error.svelte` with the application's layout and a user-friendly message. Use SvelteKit's `$page.status` and `$page.error` stores to display appropriate messages for 404 vs 500.
---
### No loading state during navigation
Long-running searches or slow backend responses give the user no visual feedback. SvelteKit's `$navigating` store is available but appears unused.
**Fix:** Add a global navigation progress indicator in `+layout.svelte`:
```svelte
<script>
import { navigating } from '$app/stores';
</script>
{#if $navigating}
<div class="progress-bar" />
{/if}
```
---
### `handleFetch` redirect inside a hook can cause silent failures
**File:** `src/hooks.server.ts:46`
```ts
throw redirect(302, '/login');
```
Throwing a redirect inside `handleFetch` during a server-side load that makes multiple API calls (e.g., the search page calling both `/api/documents/search` and `/api/persons`) means only the first missing-token fetch triggers the redirect — subsequent ones may behave unpredictably.
**Fix:** Move the auth guard to `+layout.server.ts` as the single authoritative redirect point, and let `handleFetch` simply pass through without the token (letting the backend return 401). Handle 401 responses from the backend in load functions with an explicit `redirect(302, '/login')`.

View File

@@ -0,0 +1,266 @@
# Familienarchiv — C4 Architecture Diagrams
## Level 1 — System Context
Who uses the system and what external systems does it interact with.
```mermaid
C4Context
title System Context: Familienarchiv
Person(admin, "Administrator", "Manages users, triggers bulk imports, reviews documents")
Person(member, "Family Member", "Searches, browses, and reads archived documents")
System(familienarchiv, "Familienarchiv", "Web application for digitising, organising, and searching family documents")
Rel(admin, familienarchiv, "Manages via browser", "HTTPS")
Rel(member, familienarchiv, "Searches and views via browser", "HTTPS")
```
---
## Level 2 — Containers
The deployable units that make up the system and how they communicate.
```mermaid
C4Container
title Container Diagram: Familienarchiv
Person(user, "User", "Admin or family member")
System_Boundary(archiv, "Familienarchiv (Docker Compose)") {
Container(frontend, "Web Frontend", "SvelteKit / Node.js", "Server-side rendered UI. Handles session cookies, search UI, document viewer, and admin panel.")
Container(backend, "API Backend", "Spring Boot 4 / Java 21 / Jetty", "REST API. Implements document management, search, user auth, file upload/download, and Excel import.")
ContainerDb(db, "Relational Database", "PostgreSQL 16", "Stores document metadata, persons, users, permission groups, tags, and Spring Session data.")
ContainerDb(storage, "Object Storage", "MinIO (S3-compatible)", "Stores the actual document files (PDFs, scans). Objects keyed as documents/{UUID}_{filename}.")
Container(mc, "Bucket Init Helper", "MinIO Client (mc)", "One-shot container on startup. Creates the archive bucket with private access policy.")
}
Rel(user, frontend, "Uses", "HTTPS / Browser")
Rel(frontend, backend, "API requests with Basic Auth token", "HTTP / REST / JSON")
Rel(backend, db, "Reads and writes metadata and sessions", "JDBC / SQL")
Rel(backend, storage, "Uploads and streams document files", "HTTP / S3 API (AWS SDK v2)")
Rel(mc, storage, "Creates bucket on startup", "MinIO Client CLI")
```
---
## Level 3 — Components: API Backend
The internal structure of the Spring Boot backend.
```mermaid
C4Component
title Component Diagram: API Backend
Container(frontend, "Web Frontend", "SvelteKit")
ContainerDb(db, "PostgreSQL")
ContainerDb(minio, "MinIO")
System_Boundary(backend, "API Backend (Spring Boot)") {
Component(secFilter, "Security Filter Chain", "Spring Security", "Enforces authentication on all requests. Parses Basic Auth header and validates credentials via BCrypt.")
Component(permAspect, "PermissionAspect", "Spring AOP", "Intercepts methods annotated with @RequirePermission. Checks user's granted authorities against the required permission. Throws 401/403 if denied.")
Component(docCtrl, "DocumentController", "Spring MVC — /api/documents", "CRUD for documents. Endpoints: search, get by ID, update metadata, upload file, download file, get conversation thread.")
Component(personCtrl, "PersonController", "Spring MVC — /api/persons", "Lists and searches family members. Also returns all documents sent by a person.")
Component(userCtrl, "UserController", "Spring MVC — /api/users", "Returns current user (/me). Creates and deletes users (requires ADMIN_USER permission).")
Component(adminCtrl, "AdminController", "Spring MVC — /api/admin", "Triggers asynchronous Excel mass import (requires ADMIN permission).")
Component(groupCtrl, "GroupController", "Spring MVC — /api/groups", "Lists and manages permission groups.")
Component(tagCtrl, "TagController", "Spring MVC — /api/tags", "Lists tags for typeahead.")
Component(docSvc, "DocumentService", "Spring Service", "Core business logic: store, update, search documents. Resolves persons and tags. Delegates file I/O to FileService. Builds JPA Specifications for dynamic search queries.")
Component(fileSvc, "FileService", "Spring Service", "Wraps AWS SDK v2 S3Client. Uploads files with UUID-keyed paths. Downloads with content-type detection (PDF, JPEG, PNG, octet-stream).")
Component(excelSvc, "ExcelService", "Spring Service", "Parses Excel workbooks (Apache POI). Column indices are configurable via application.properties. Creates/updates document records per row.")
Component(massImport, "MassImportService", "Spring Service — @Async", "Reads Excel files from /import mount. Delegates to ExcelService. Runs asynchronously so the HTTP response returns immediately.")
Component(userSvc, "UserService", "Spring Service", "User CRUD. Encodes passwords with BCrypt. Assigns users to permission groups.")
Component(dataInit, "DataInitializer", "CommandLineRunner", "On startup: creates default admin user and groups if none exist. Seeds test data (persons, documents) if DB is empty.")
Component(docRepo, "DocumentRepository", "Spring Data JPA", "Queries documents. Supports Specification-based dynamic search, conversation thread queries (bidirectional sender/receiver), and filename lookups.")
Component(docSpec, "DocumentSpecifications", "JPA Criteria API", "Factory for composable query predicates: hasText (full-text across title/filename/transcription/location), hasSender, hasReceiver (join), isBetween (date range), hasTags (subquery AND logic).")
Component(personRepo, "PersonRepository", "Spring Data JPA", "Lists all persons sorted by last name. Supports name search for typeahead.")
Component(userRepo, "AppUserRepository", "Spring Data JPA", "Finds users by username. Used by Spring Security and UserService.")
Component(tagRepo, "TagRepository", "Spring Data JPA", "Finds or creates tags by name (case-insensitive).")
Component(groupRepo, "UserGroupRepository", "Spring Data JPA", "Manages permission groups.")
Component(minioConf, "MinioConfig", "Spring @Configuration", "Creates the S3Client bean with path-style access for MinIO. Validates MinIO connectivity on startup.")
Component(secConf, "SecurityConfig", "Spring @Configuration", "Configures filter chain: all routes require authentication, CSRF disabled, BCrypt password encoder, DaoAuthenticationProvider with CustomUserDetailsService.")
Component(userDetails, "CustomUserDetailsService", "Spring Security UserDetailsService", "Loads AppUser by username from DB. Converts group permissions to Spring GrantedAuthority objects.")
}
Rel(frontend, secFilter, "All requests", "HTTP / Basic Auth header")
Rel(secFilter, permAspect, "Authenticated requests proceed", "")
Rel(secFilter, docCtrl, "Routes to", "")
Rel(secFilter, personCtrl, "Routes to", "")
Rel(secFilter, userCtrl, "Routes to", "")
Rel(secFilter, adminCtrl, "Routes to", "")
Rel(permAspect, docCtrl, "Guards", "AOP @Around")
Rel(permAspect, userCtrl, "Guards", "AOP @Around")
Rel(permAspect, adminCtrl, "Guards", "AOP @Around")
Rel(docCtrl, docSvc, "Delegates to", "")
Rel(adminCtrl, massImport, "Triggers", "")
Rel(userCtrl, userSvc, "Delegates to", "")
Rel(docSvc, fileSvc, "Upload / download files", "")
Rel(docSvc, docRepo, "Reads / writes documents", "")
Rel(docSvc, docSpec, "Builds search predicates", "")
Rel(docSvc, personRepo, "Resolves sender / receivers", "")
Rel(docSvc, tagRepo, "Finds or creates tags", "")
Rel(massImport, excelSvc, "Parses Excel file", "")
Rel(excelSvc, docSvc, "Creates / updates documents", "")
Rel(userSvc, userRepo, "Reads / writes users", "")
Rel(userSvc, groupRepo, "Assigns groups", "")
Rel(userDetails, userRepo, "Loads user by username", "")
Rel(fileSvc, minio, "PUT / GET objects", "S3 API / HTTP")
Rel(docRepo, db, "SQL queries", "JDBC")
Rel(personRepo, db, "SQL queries", "JDBC")
Rel(userRepo, db, "SQL queries", "JDBC")
Rel(tagRepo, db, "SQL queries", "JDBC")
Rel(groupRepo, db, "SQL queries", "JDBC")
Rel(dataInit, db, "Seeds initial data", "JDBC")
Rel(secConf, userDetails, "Wires", "")
Rel(minioConf, fileSvc, "Provides S3Client bean", "")
```
---
## Level 3 — Components: Web Frontend
The internal structure of the SvelteKit frontend.
```mermaid
C4Component
title Component Diagram: Web Frontend
Person(user, "User")
Container(backend, "API Backend", "Spring Boot")
System_Boundary(frontend, "Web Frontend (SvelteKit / SSR)") {
Component(hooks, "hooks.server.ts", "SvelteKit Server Hook", "Two responsibilities: (1) userGroup handle — reads auth_token cookie, fetches /api/users/me, stores user in event.locals. (2) handleFetch — intercepts all outgoing fetch() calls, injects Authorization header from cookie. Redirects to /login if token absent.")
Component(i18n, "hooks.ts (Paraglide)", "SvelteKit Client Hook", "Client-side i18n middleware. Detects language from URL and sets the active locale for Paraglide.js translation functions.")
Component(layout, "+layout.server.ts", "SvelteKit Layout Loader", "Passes event.locals.user down to all child pages so every route has access to the authenticated user.")
Component(homePage, "/ (Home / Search)", "SvelteKit Route", "Loader: parses URL search params (q, from, to, senderId, receiverId, tags), fetches /api/documents/search and /api/persons, returns results. Page: renders search form with full-text, date range, sender/receiver typeahead, tag filters. Displays paginated document list.")
Component(docDetail, "/documents/[id]", "SvelteKit Route", "Loader: fetches /api/documents/{id}. Handles 401 redirect to login, 404 error. Page: shows document metadata, file viewer (PDF/image inline), transcription, tags.")
Component(docEdit, "/documents/[id]/edit", "SvelteKit Route", "Form with PersonTypeahead for sender/receiver, TagInput for tags, date/location fields. Submits PUT to /api/documents/{id}.")
Component(persons, "/persons and /persons/[id]", "SvelteKit Routes", "Lists all persons. Detail page shows person metadata and all documents they sent.")
Component(conversations, "/conversations", "SvelteKit Route", "Selects two persons via PersonTypeahead, fetches /api/documents/conversation, displays chronological exchange.")
Component(loginPage, "/login", "SvelteKit Route", "Form action: encodes username:password as Base64 Basic Auth token, POSTs to /api/users/me to validate, sets auth_token httpOnly cookie (SameSite=strict, maxAge=86400), redirects to /.")
Component(logoutPage, "/logout", "SvelteKit Route (server-only)", "Clears the auth_token cookie and redirects to /login.")
Component(adminPage, "/admin", "SvelteKit Route", "User management UI (create/delete users). Excel import trigger button (calls /api/admin/trigger-import).")
Component(apiPersons, "/api/persons (SvelteKit API)", "SvelteKit Server Route", "Proxies GET /api/persons?q=... to backend. Used by PersonTypeahead for typeahead suggestions.")
Component(apiTags, "/api/tags (SvelteKit API)", "SvelteKit Server Route", "Proxies GET /api/tags to backend. Used by TagInput for autocomplete.")
Component(typeahead, "PersonTypeahead.svelte", "Svelte Component", "Async autocomplete for selecting a person. Debounces input, calls /api/persons?q=.")
Component(tagInput, "TagInput.svelte", "Svelte Component", "Multi-tag input. Supports free-text entry and selecting existing tags from /api/tags.")
}
Rel(user, hooks, "Every browser request", "HTTPS")
Rel(hooks, backend, "GET /api/users/me (session check)", "HTTP / Basic Auth")
Rel(hooks, loginPage, "Redirect if no token", "")
Rel(layout, homePage, "Provides user context", "")
Rel(layout, docDetail, "Provides user context", "")
Rel(layout, adminPage, "Provides user context", "")
Rel(homePage, backend, "GET /api/documents/search", "HTTP / JSON")
Rel(homePage, backend, "GET /api/persons", "HTTP / JSON")
Rel(docDetail, backend, "GET /api/documents/{id}", "HTTP / JSON")
Rel(docDetail, backend, "GET /api/documents/{id}/file", "HTTP / Binary stream")
Rel(docEdit, backend, "PUT /api/documents/{id}", "HTTP / Multipart")
Rel(conversations, backend, "GET /api/documents/conversation", "HTTP / JSON")
Rel(loginPage, backend, "POST /api/users/me (auth check)", "HTTP / Basic Auth")
Rel(adminPage, backend, "GET/POST/DELETE /api/users", "HTTP / JSON")
Rel(adminPage, backend, "POST /api/admin/trigger-import", "HTTP / JSON")
Rel(apiPersons, backend, "GET /api/persons", "HTTP / JSON")
Rel(apiTags, backend, "GET /api/tags", "HTTP / JSON")
Rel(homePage, typeahead, "Uses for sender/receiver filter", "")
Rel(docEdit, typeahead, "Uses for sender/receiver selection", "")
Rel(docEdit, tagInput, "Uses for tag management", "")
Rel(typeahead, apiPersons, "Fetches suggestions", "HTTP")
Rel(tagInput, apiTags, "Fetches existing tags", "HTTP")
```
---
## Authentication Flow
How a user session is established and maintained across requests.
```mermaid
sequenceDiagram
actor User
participant Browser
participant Frontend as Frontend (SvelteKit)
participant Backend as Backend (Spring Boot)
participant DB as PostgreSQL
User->>Browser: Enter username + password
Browser->>Frontend: POST /login (form action)
Frontend->>Frontend: Base64 encode "user:password"
Frontend->>Backend: GET /api/users/me<br/>Authorization: Basic <token>
Backend->>Backend: Spring Security parses Basic Auth
Backend->>DB: SELECT user WHERE username=?
DB-->>Backend: AppUser + groups + permissions
Backend->>Backend: BCrypt.matches(password, hash)
Backend-->>Frontend: 200 OK — UserDTO
Frontend->>Browser: Set-Cookie: auth_token=<base64><br/>(httpOnly, SameSite=strict, maxAge=86400)
Browser->>Frontend: GET / (next request)
Frontend->>Frontend: hooks.server.ts reads auth_token cookie
Frontend->>Backend: GET /api/users/me<br/>Authorization: Basic <token>
Backend-->>Frontend: 200 OK — user in event.locals
Frontend-->>Browser: Render page with user context
```
---
## Document Upload Flow
How a document file moves from a user's browser to MinIO.
```mermaid
sequenceDiagram
actor User
participant Frontend as Frontend (SvelteKit)
participant Backend as Backend (Spring Boot)
participant Aspect as PermissionAspect (AOP)
participant DocSvc as DocumentService
participant FileSvc as FileService
participant MinIO
participant DB as PostgreSQL
User->>Frontend: Submit edit form (file + metadata)
Frontend->>Backend: PUT /api/documents/{id}<br/>multipart/form-data + Authorization header
Backend->>Aspect: @RequirePermission(WRITE_ALL) check
Aspect->>Aspect: Verify user has WRITE_ALL authority
Aspect-->>Backend: Proceed
Backend->>DocSvc: updateDocument(id, dto, file)
DocSvc->>DocSvc: Resolve sender Person by ID
DocSvc->>DocSvc: Resolve/create Tags
DocSvc->>FileSvc: uploadFile(file, filename)
FileSvc->>FileSvc: Generate key: documents/{UUID}_{filename}
FileSvc->>MinIO: PutObject(bucket, key, stream)
MinIO-->>FileSvc: Success
FileSvc-->>DocSvc: S3 key
DocSvc->>DB: UPDATE documents SET file_path=?, status='UPLOADED', ...
DB-->>DocSvc: OK
DocSvc-->>Backend: Updated Document entity
Backend-->>Frontend: 200 OK — Document JSON
Frontend-->>User: Refreshed document view
```