fix(backend): resolve cross-domain repo + controller→repo violations (#417) #420

Merged
marcel merged 9 commits from feat/issue-417-resolve-layering-violations into main 2026-05-05 10:50:04 +02:00

9 Commits

Author SHA1 Message Date
Marcel
89e9a2452e refactor(test): remove issue reference from makeService javadoc
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m44s
CI / OCR Service Tests (pull_request) Successful in 41s
CI / Backend Unit Tests (pull_request) Failing after 3m16s
CI / Unit & Component Tests (push) Failing after 4m5s
CI / OCR Service Tests (push) Successful in 57s
CI / Backend Unit Tests (push) Failing after 3m12s
Issue numbers in code comments rot as the codebase evolves. The why
(keeping real-database fidelity without pulling full service trees in)
is what matters, not the fix number.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 10:37:06 +02:00
Marcel
2506523f3b refactor(transcription/annotation): break mutual repo dependency
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m2s
CI / OCR Service Tests (push) Successful in 42s
CI / Backend Unit Tests (push) Failing after 3m17s
CI / Unit & Component Tests (pull_request) Failing after 3m49s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m17s
TranscriptionService injected AnnotationRepository; AnnotationService injected
TranscriptionBlockRepository. Each side now talks through the other domain's
service:

- TranscriptionService.deleteByAnnotationId — new write delegation; called
  from AnnotationService.deleteAnnotation in place of the foreign repo.
- AnnotationService.deleteById / deleteAllById — new write delegations; called
  from TranscriptionService for cascading annotation cleanup.
- AnnotationService.findById (added in #417 commit 6) replaces the read.
- @Lazy on AnnotationService's TranscriptionService field breaks the
  resulting two-bean cycle at construction time, mirroring the existing
  @Lazy self-reference pattern in SenderModelService.

Refs #417 (C6.2 violations #10 and #11).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:48:26 +02:00
Marcel
f5151f3949 refactor(ocr-training): route SenderModelService and OcrTrainingService through TranscriptionBlockQueryService
Both services injected TranscriptionBlockRepository directly to read block
counts. They now go through TranscriptionBlockQueryService (count() and
countManualKurrentBlocksByPerson() added as 1-line delegations) — chosen over
TranscriptionService to avoid the existing
SenderModelService → TrainingDataExportService → TranscriptionBlockQueryService
chain reaching back into TranscriptionService and creating a cycle.

Refs #417 (C6.2 violations #8 and #9).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:40:34 +02:00
Marcel
310bb5b2d5 refactor(training-export): route export services through owning services
SegmentationTrainingExportService and TrainingDataExportService each injected
TranscriptionBlockRepository, AnnotationRepository and DocumentRepository
directly. They now go through:

- TranscriptionBlockQueryService (extended) for the three eligible-block
  queries — used over TranscriptionService to keep
  SenderModelService → TrainingDataExportService → TranscriptionService cycle-free.
- AnnotationService.findById (new) — read API on the annotation domain.
- DocumentService.findById (already added in #417 commit 3).

The TrainingDataExportServiceTest @DataJpaTest delegates the new service reads
to the real JPA repositories via Mockito stubs in the new makeService helper,
so the integration coverage stays unchanged.

Refs #417 (C6.2 violations #6 and #7).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:36:20 +02:00
Marcel
0ca95d5ad7 refactor(import): route MassImportService through DocumentService
MassImportService injected DocumentRepository for the find-or-create pattern
during ODS/Excel import. Move the two repository touchpoints (findByOriginalFilename,
save) onto DocumentService as 1-line delegations and update the consumer.

Refs #417 (C6.2 violation #1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:27:30 +02:00
Marcel
8b177b9430 refactor(transcription-queue): route through DocumentService projections
TranscriptionQueueService injected DocumentRepository to fetch the four queue
projections. Move the four read methods (findSegmentationQueue,
findTranscriptionQueue, findReadyToReadQueue, findWeeklyStats) onto
DocumentService as 1-line delegations and update the consumer.

Refs #417 (C6.2 violation #5).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:23:25 +02:00
Marcel
e2e7b79067 refactor(thumbnail): route document access through DocumentService
The Thumbnail trio (ThumbnailService, ThumbnailBackfillService,
ThumbnailAsyncRunner) all injected DocumentRepository directly. They now go
through three new DocumentService delegations:

- findById(UUID): Optional<Document> — no-throw variant for the runner's
  log-and-skip behaviour on missing documents.
- findForThumbnailBackfill() — wraps the existing
  findByFilePathIsNotNullAndThumbnailKeyIsNull query.
- updateThumbnailMetadata(Document) — wraps save() for the post-thumbnail
  entity update.

DocumentService also gains @Lazy on its existing ThumbnailAsyncRunner field
to break the new DocumentService ↔ ThumbnailAsyncRunner cycle. lombok.config
adds @Lazy to copyableAnnotations so the field annotation reaches the
generated constructor parameter.

Refs #417 (C6.2 violations #2, #3, #4).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 07:20:01 +02:00
Marcel
5c1332cb0e refactor(auth): route password reset through service layer + e2e helper
- PasswordResetService injects UserService instead of AppUserRepository.
- New UserService.findByEmailOptional preserves the silent-fail behaviour of
  the old findByEmail-returning-Optional path; the existing throwing
  findByEmail is unchanged.
- New PasswordResetService.findLatestActiveTokenForEmail exposes the latest
  active reset token without leaking the repository upward.
- New @Profile("e2e") PasswordResetTestHelper wraps that read so the
  AuthE2EController no longer touches PasswordResetTokenRepository directly.
  Profile guard moves from the controller-only annotation to also cover the
  helper bean, so the production graph never instantiates either.

Refs #417 (C6.1 violation #2 + C6.2 violation #12).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 22:26:11 +02:00
Marcel
d5e0e969ef refactor(stats): introduce StatsService and require READ_ALL
StatsController previously injected PersonRepository and DocumentRepository
directly, violating the controller→service→repository layering rule. Move the
two count() calls into a thin StatsService that delegates to PersonService.count
and DocumentService.count. While here, add the missing @RequirePermission(READ_ALL)
flagged by AUDIT-2 §7 — anonymous callers were able to read aggregate document/
person counts.

Refs #417 (C6.1 violation #1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 22:20:14 +02:00