Files
familienarchiv/backend/CLAUDE.md
2026-05-05 12:39:20 +02:00

6.1 KiB

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

src/main/java/org/raddatz/familienarchiv/
├── audit/          # Audit logging infrastructure
├── config/         # MinioConfig, AsyncConfig, RateLimitInterceptor
├── controller/     # REST endpoints — thin, delegate to services
├── dashboard/      # Dashboard analytics queries and endpoints
├── dto/            # Input/request DTOs (e.g., DocumentUpdateDTO, GroupDTO)
├── exception/      # DomainException + ErrorCode enum
├── model/          # JPA entities (Document, Person, Tag, AppUser, etc.)
├── repository/     # Spring Data JPA interfaces + Specifications
├── security/       # SecurityConfig, Permission enum, @RequirePermission, PermissionAspect
└── service/        # Business logic — the only place that touches repositories

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.
    • DocumentServicePersonService.getById()PersonRepository
    • DocumentServicePersonRepository 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<String> 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:

@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:

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:

@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

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)