# Backend — Familienarchiv ## Overview Spring Boot 4.0 monolith serving the Familienarchiv REST API. Handles document management, person/entity tracking, transcription workflows, OCR orchestration, user management, and full-text search. ## Tech Stack - **Framework**: Spring Boot 4.0 (Java 21) - **Build**: Maven (`./mvnw` wrapper) - **Server**: Jetty (not Tomcat — excluded in pom.xml) - **Data**: PostgreSQL 16, JPA/Hibernate, Spring Data JPA - **Migrations**: Flyway (SQL files in `src/main/resources/db/migration/`) - **Security**: Spring Security, Spring Session JDBC, JWT tokens - **File Storage**: MinIO via AWS SDK v2 (S3-compatible) - **Spreadsheet Import**: Apache POI 5.5.0 (Excel/ODS) - **API Docs**: SpringDoc OpenAPI 3.x (`/v3/api-docs` — dev profile only) - **Monitoring**: Spring Boot Actuator (`/actuator/health`) ## Package Structure Package-by-domain: each domain owns its controller, service, repository, entities, and DTOs. ``` src/main/java/org/raddatz/familienarchiv/ ├── audit/ # Audit logging (AuditService, AuditLogQueryService) ├── config/ # Infrastructure config (MinioConfig, AsyncConfig, WebConfig) ├── dashboard/ # Dashboard analytics + StatsController/StatsService ├── document/ # Document domain — entities, controller, service, repository, DTOs │ ├── annotation/ # DocumentAnnotation, AnnotationService, AnnotationController │ ├── comment/ # DocumentComment, CommentService, CommentController │ └── transcription/ # TranscriptionBlock, TranscriptionService, TranscriptionBlockQueryService ├── exception/ # DomainException, ErrorCode, GlobalExceptionHandler ├── filestorage/ # FileService (S3/MinIO) ├── geschichte/ # Geschichte (story) domain ├── importing/ # MassImportService ├── notification/ # Notification domain + SseEmitterRegistry ├── ocr/ # OCR domain — OcrService, OcrBatchService, training ├── person/ # Person domain — Person, PersonService, PersonController │ └── relationship/ # PersonRelationship sub-domain ├── security/ # SecurityConfig, Permission, @RequirePermission, PermissionAspect ├── tag/ # Tag domain — Tag, TagService, TagController └── user/ # User domain — AppUser, UserGroup, UserService, auth controllers ``` ## Layering Rules (Strict) ``` Controller → Service → Repository → DB ``` - **Controllers never call repositories directly.** - **Services never reach into another domain's repository.** Call the other domain's service instead. - ✅ `DocumentService` → `PersonService.getById()` → `PersonRepository` - ❌ `DocumentService` → `PersonRepository` directly ## Key Entities | Entity | Table | Key Relationships | |---|---|---| | `Document` | `documents` | ManyToOne sender (Person), ManyToMany receivers (Person), ManyToMany tags (Tag) | | `Person` | `persons` | Referenced by documents as sender/receiver; name aliases table | | `Tag` | `tag` | ManyToMany with documents via `document_tags`; self-referencing parent for tree | | `AppUser` | `app_users` | ManyToMany groups (UserGroup) | | `UserGroup` | `user_groups` | Has a `Set permissions` | | `TranscriptionBlock` | `transcription_blocks` | Per-document, per-page text blocks with polygons | | `DocumentAnnotation` | `document_annotations` | Free-form annotations on document pages | | `Comment` | `document_comments` | Threaded comments with mentions | | `Notification` | `notifications` | User notification feed | | `OcrJob` / `OcrJobDocument` | `ocr_jobs`, `ocr_job_documents` | Batch OCR job tracking | **`DocumentStatus` lifecycle:** `PLACEHOLDER → UPLOADED → TRANSCRIBED → REVIEWED → ARCHIVED` ## Entity Code Style All entities use these Lombok annotations: ```java @Entity @Table(name = "table_name") @Data @NoArgsConstructor @AllArgsConstructor @Builder public class MyEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private UUID id; // ... } ``` - `@Schema(requiredMode = REQUIRED)` on every field the backend always populates — drives TypeScript generation. - Collections use `@Builder.Default` with `new HashSet<>()` as default. - Timestamps use `@CreationTimestamp` / `@UpdateTimestamp`. ## Services - Annotated with `@Service`, `@RequiredArgsConstructor`, optionally `@Slf4j`. - Write methods: `@Transactional`. - Read methods: no annotation (default non-transactional). - Cross-domain access goes through the other domain's service, never its repository. ## Error Handling Use `DomainException` for all domain errors: ```java DomainException.notFound(ErrorCode.DOCUMENT_NOT_FOUND, "...") DomainException.forbidden("...") DomainException.conflict(ErrorCode.IMPORT_ALREADY_RUNNING, "...") DomainException.internal(ErrorCode.FILE_UPLOAD_FAILED, "...") ``` When adding a new `ErrorCode`: 1. Add to `ErrorCode.java` 2. Mirror in frontend `src/lib/errors.ts` 3. Add Paraglide translation key in `messages/{de,en,es}.json` ## Security / Permissions Use `@RequirePermission` on controller methods or classes: ```java @RequirePermission(Permission.WRITE_ALL) public Document updateDocument(...) { ... } ``` Available permissions: `READ_ALL`, `WRITE_ALL`, `ADMIN`, `ADMIN_USER`, `ADMIN_TAG`, `ADMIN_PERMISSION` `PermissionAspect` checks the current user's `UserGroup.permissions` at runtime. ## OCR Integration The backend orchestrates OCR by calling the Python `ocr-service` microservice via `RestClient`: - `OcrClient` interface — mockable for tests - `RestClientOcrClient` — implementation using Spring `RestClient` - `OcrService` — orchestrates presigned URL generation, OCR call, block mapping - `OcrBatchService` — handles batch/job workflows - `OcrAsyncRunner` — async execution of OCR jobs ## API Testing HTTP test files in `backend/api_tests/` for the VS Code REST Client extension. ## How to Run ### Local Development ```bash cd backend # Run with dev profile (requires PostgreSQL + MinIO running via docker-compose) ./mvnw spring-boot:run # Build JAR (with tests) ./mvnw clean package # Build JAR skipping tests ./mvnw clean package -DskipTests # Run all tests ./mvnw test # Run a single test class ./mvnw test -Dtest=ClassName # Run with coverage (JaCoCo) ./mvnw clean verify ``` ### OpenAPI TypeScript Generation 1. Build and start backend with `--spring.profiles.active=dev` 2. In `frontend/`, run: `npm run generate:api` ### Profiles - **dev** (default): Enables OpenAPI, dev configs, e2e seeds - **prod**: Production profile — no dev endpoints ## Testing - Unit tests: Mockito + JUnit, pure in-memory - Slice tests: `@WebMvcTest`, `@DataJpaTest` with Testcontainers PostgreSQL - Integration tests: Full Spring context with Testcontainers - Coverage gate: 88% branch coverage overall (JaCoCo)