feat(ocr): add per-model history endpoints via path segments

Add findByPersonIdIsNullOrderByCreatedAtDesc + findByPersonIdOrderByCreatedAtDesc to
OcrTrainingRunRepository. Add dto/TrainingHistoryResponse. Expose
GET /api/ocr/training-info/global and GET /api/ocr/training-info/{personId} on
OcrController, both requiring ADMIN; getSenderTrainingHistory guards person existence
via PersonService and returns 404 for unknown personId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-18 00:08:38 +02:00
parent b1b7418404
commit 178afcd496
6 changed files with 154 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.dto.BatchOcrDTO;
import org.raddatz.familienarchiv.dto.OcrStatusDTO;
import org.raddatz.familienarchiv.dto.TrainingHistoryResponse;
import org.raddatz.familienarchiv.dto.TrainingInfoResponse;
import org.raddatz.familienarchiv.dto.TriggerOcrDTO;
import org.raddatz.familienarchiv.model.AppUser;
@@ -135,6 +136,18 @@ public class OcrController {
return ocrTrainingService.getTrainingInfo();
}
@GetMapping("/api/ocr/training-info/global")
@RequirePermission(Permission.ADMIN)
public TrainingHistoryResponse getGlobalTrainingHistory() {
return ocrTrainingService.getGlobalTrainingHistory();
}
@GetMapping("/api/ocr/training-info/{personId}")
@RequirePermission(Permission.ADMIN)
public TrainingHistoryResponse getSenderTrainingHistory(@PathVariable UUID personId) {
return ocrTrainingService.getSenderTrainingHistory(personId);
}
private UUID resolveUserId(Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) return null;
try {

View File

@@ -0,0 +1,11 @@
package org.raddatz.familienarchiv.dto;
import org.raddatz.familienarchiv.model.OcrTrainingRun;
import java.util.List;
import java.util.Map;
public record TrainingHistoryResponse(
List<OcrTrainingRun> runs,
Map<String, String> personNames
) {}

View File

@@ -19,4 +19,8 @@ public interface OcrTrainingRunRepository extends JpaRepository<OcrTrainingRun,
boolean existsByPersonIdAndStatus(UUID personId, TrainingStatus status);
List<OcrTrainingRun> findTop20ByOrderByCreatedAtDesc();
List<OcrTrainingRun> findByPersonIdIsNullOrderByCreatedAtDesc();
List<OcrTrainingRun> findByPersonIdOrderByCreatedAtDesc(UUID personId);
}

View File

@@ -2,6 +2,7 @@ package org.raddatz.familienarchiv.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.dto.TrainingHistoryResponse;
import org.raddatz.familienarchiv.dto.TrainingInfoResponse;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
@@ -219,6 +220,17 @@ public class OcrTrainingService {
);
}
public TrainingHistoryResponse getGlobalTrainingHistory() {
List<OcrTrainingRun> runs = trainingRunRepository.findByPersonIdIsNullOrderByCreatedAtDesc();
return new TrainingHistoryResponse(runs, Map.of());
}
public TrainingHistoryResponse getSenderTrainingHistory(UUID personId) {
String personName = personService.getById(personId).getDisplayName();
List<OcrTrainingRun> runs = trainingRunRepository.findByPersonIdOrderByCreatedAtDesc(personId);
return new TrainingHistoryResponse(runs, Map.of(personId.toString(), personName));
}
@EventListener(ApplicationReadyEvent.class)
@Transactional
public void recoverOrphanedRuns() {