18 KiB
Familienarchiv — C4 Architecture Diagrams
For domain terminology used in these diagrams, see docs/GLOSSARY.md.
Level 1 — System Context
Who uses the system and what external systems does it interact with.
C4Context
title System Context: Familienarchiv
Person(admin, "Administrator", "Manages users, triggers bulk imports, reviews and transcribes documents")
Person(member, "Family Member", "Searches, browses, reads, and transcribes 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, reads, and transcribes via browser", "HTTPS")
Level 2 — Containers
The deployable units that make up the system and how they communicate.
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 auth session cookies, document search and viewer, transcription editor, annotation layer, family tree (Stammbaum), stories (Geschichten), activity feed (Chronik), enrichment workflow, and admin panel.")
Container(backend, "API Backend", "Spring Boot 4 / Java 21 / Jetty", "REST API. Implements document management, search, user auth, file upload/download, transcription, OCR orchestration, and SSE notifications.")
Container(ocr, "OCR Service", "Python FastAPI / port 8000", "Handwritten text recognition (HTR) and OCR microservice. Single-node by design — see ADR-001. Reachable only on the internal Docker network; no external port exposed.")
ContainerDb(db, "Relational Database", "PostgreSQL 16", "Stores document metadata, persons, users, permission groups, tags, transcription blocks, audit log, 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, user, "SSE notifications (server-sent events)", "HTTP / SSE — direct backend-to-browser")
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(backend, ocr, "OCR job requests with presigned MinIO URL", "HTTP / REST / JSON")
Rel(ocr, storage, "Fetches PDF via presigned URL", "HTTP / S3 presigned")
Rel(mc, storage, "Creates bucket on startup", "MinIO Client CLI")
Level 3 — Components: API Backend
The internal structure of the Spring Boot backend, split into three focused views.
3a — Security & Authentication
How requests are authenticated and write operations are authorised.
C4Component
title Component Diagram: API Backend — Security & Authentication
Container(frontend, "Web Frontend", "SvelteKit")
ContainerDb(db, "PostgreSQL")
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. Permits password-reset, invite, and register endpoints without authentication.")
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(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 email from DB. Converts group permissions to Spring GrantedAuthority objects. Logs unknown permissions.")
}
Rel(frontend, secFilter, "All requests", "HTTP / Basic Auth header")
Rel(secFilter, permAspect, "Authenticated requests proceed to guarded methods", "AOP @Around")
Rel(secConf, userDetails, "Wires as UserDetailsService", "")
Rel(userDetails, db, "Loads user by email", "JDBC")
3b — Document, File & Import Domain
Document management, file storage, and bulk Excel import.
C4Component
title Component Diagram: API Backend — Document, File & Import Domain
Container(frontend, "Web Frontend", "SvelteKit")
ContainerDb(db, "PostgreSQL")
ContainerDb(minio, "MinIO")
System_Boundary(backend, "API Backend (Spring Boot)") {
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(adminCtrl, "AdminController", "Spring MVC — /api/admin", "Triggers asynchronous Excel mass import (requires ADMIN permission).")
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(massImport, "MassImportService", "Spring Service — @Async", "Reads Excel files from /import mount. Delegates to ExcelService. Runs asynchronously so the HTTP response returns immediately.")
Component(excelSvc, "ExcelService", "Spring Service", "Parses Excel workbooks (Apache POI). Column indices are configurable via application.properties. Creates/updates document records per row.")
Component(minioConf, "MinioConfig", "Spring @Configuration", "Creates the S3Client bean with path-style access for MinIO. Validates MinIO connectivity on startup.")
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", "See diagram 3c. Used here by DocumentService to resolve sender / receiver persons.")
Component(tagRepo, "TagRepository", "Spring Data JPA", "See diagram 3c. Used here by DocumentService to find or create tags.")
Rel(frontend, docCtrl, "Document requests", "HTTP / JSON")
Rel(frontend, adminCtrl, "Trigger import", "HTTP / JSON")
Rel(docCtrl, docSvc, "Delegates to", "")
Rel(adminCtrl, massImport, "Triggers", "")
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(minioConf, fileSvc, "Provides S3Client bean", "")
Rel(fileSvc, minio, "PUT / GET objects", "S3 API / HTTP")
Rel(docRepo, db, "SQL queries", "JDBC")
Rel(personRepo, db, "SQL queries", "JDBC")
Rel(tagRepo, db, "SQL queries", "JDBC")
3c — People, Users & Group Administration
Person, user, and group management, including startup seed data.
C4Component
title Component Diagram: API Backend — People, Users & Group Administration
Container(frontend, "Web Frontend", "SvelteKit")
ContainerDb(db, "PostgreSQL")
System_Boundary(backend, "API Backend (Spring Boot)") {
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(groupCtrl, "GroupController", "Spring MVC — /api/groups", "Lists and manages permission groups.")
Component(tagCtrl, "TagController", "Spring MVC — /api/tags", "Lists tags for typeahead.")
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(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(groupRepo, "UserGroupRepository", "Spring Data JPA", "Manages permission groups.")
Component(tagRepo, "TagRepository", "Spring Data JPA", "Finds or creates tags by name (case-insensitive).")
}
Rel(frontend, personCtrl, "Person requests", "HTTP / JSON")
Rel(frontend, userCtrl, "User requests", "HTTP / JSON")
Rel(frontend, groupCtrl, "Group requests", "HTTP / JSON")
Rel(frontend, tagCtrl, "Tag requests", "HTTP / JSON")
Rel(personCtrl, personRepo, "Reads persons", "")
Rel(userCtrl, userSvc, "Delegates to", "")
Rel(groupCtrl, groupRepo, "Reads / writes groups", "")
Rel(tagCtrl, tagRepo, "Lists tags", "")
Rel(userSvc, userRepo, "Reads / writes users", "")
Rel(userSvc, groupRepo, "Assigns groups", "")
Rel(dataInit, db, "Seeds initial data", "JDBC")
Rel(personRepo, db, "SQL queries", "JDBC")
Rel(userRepo, db, "SQL queries", "JDBC")
Rel(groupRepo, db, "SQL queries", "JDBC")
Rel(tagRepo, db, "SQL queries", "JDBC")
Level 3 — Components: Web Frontend
The internal structure of the SvelteKit frontend, split into two focused views.
3a — Middleware, Auth & Layout
Per-request middleware: session validation, i18n, and auth cookie handling.
C4Component
title Component Diagram: Web Frontend — Middleware, Auth & Layout
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(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.")
}
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(hooks, layout, "Stores authenticated user in event.locals", "")
Rel(loginPage, backend, "POST /api/users/me (auth check)", "HTTP / Basic Auth")
3b — Pages & Shared Components
Application pages and reusable UI components.
C4Component
title Component Diagram: Web Frontend — Pages & Shared Components
Person(user, "User")
Container(backend, "API Backend", "Spring Boot")
System_Boundary(frontend, "Web Frontend (SvelteKit / SSR)") {
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(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, homePage, "Searches and browses", "HTTPS / Browser")
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(adminPage, backend, "GET/POST/DELETE /api/users", "HTTP / JSON")
Rel(adminPage, backend, "POST /api/admin/trigger-import", "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")
Rel(apiPersons, backend, "GET /api/persons", "HTTP / JSON")
Rel(apiTags, backend, "GET /api/tags", "HTTP / JSON")
Authentication Flow
How a user session is established and maintained across requests.
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.
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