From 883c3381a74441f549473b247fc31b5a197579fe Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 6 May 2026 09:51:46 +0200 Subject: [PATCH] docs(c4): split L3 monolith diagrams into five focused sub-diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend L3 split into 3a (Security & Auth), 3b (Document/File/Import), 3c (People/Users/Groups). Frontend L3 split into 3a (Middleware/Auth/Layout) and 3b (Pages & Shared Components). Each sub-diagram stays within dagre's clean-layout range (5–10 components, 6–12 relationships). Co-Authored-By: Claude Sonnet 4.6 --- docs/architecture/c4-diagrams.md | 173 ++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 63 deletions(-) diff --git a/docs/architecture/c4-diagrams.md b/docs/architecture/c4-diagrams.md index a5b2b22b..8cf1dfe9 100644 --- a/docs/architecture/c4-diagrams.md +++ b/docs/architecture/c4-diagrams.md @@ -59,114 +59,171 @@ C4Container ## Level 3 — Components: API Backend -The internal structure of the Spring Boot 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. ```mermaid C4Component - title Component Diagram: API Backend + 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.") + 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 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 to guarded methods", "AOP @Around") + Rel(secConf, userDetails, "Wires as UserDetailsService", "") + Rel(userDetails, db, "Loads user by username", "JDBC") +``` + +### 3b — Document, File & Import Domain + +Document management, file storage, and bulk Excel import. + +```mermaid +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(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(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", "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") + 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(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(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(userRepo, db, "SQL queries", "JDBC") Rel(tagRepo, db, "SQL queries", "JDBC") - Rel(groupRepo, db, "SQL queries", "JDBC") +``` + +### 3c — People, Users & Group Administration + +Person, user, and group management, including startup seed data. + +```mermaid +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(secConf, userDetails, "Wires", "") - Rel(minioConf, fileSvc, "Provides S3Client bean", "") + 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. +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. ```mermaid C4Component - title Component Diagram: Web Frontend + 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. + +```mermaid +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(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.") @@ -176,32 +233,22 @@ C4Component 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(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(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") + Rel(apiPersons, backend, "GET /api/persons", "HTTP / JSON") + Rel(apiTags, backend, "GET /api/tags", "HTTP / JSON") ``` ---