This commit is contained in:
team 1
2026-05-12 11:53:36 +02:00
parent 3f914c1efd
commit 64d1ec71e8
7 changed files with 289 additions and 5 deletions

View File

@@ -92,6 +92,7 @@ final class AdminEvalController extends AbstractController
return $this->render('admin/evals/case_new.html.twig', [
'types' => $evals->supportedTypes(),
'cases_by_type' => $evals->casesByType(),
'case_draft' => $draft,
]);
}
@@ -146,7 +147,46 @@ final class AdminEvalController extends AbstractController
return $this->render('admin/evals/case_new.html.twig', [
'types' => $evals->supportedTypes(),
'cases_by_type' => $evals->casesByType(),
'case_draft' => $draft,
], new Response('', Response::HTTP_UNPROCESSABLE_ENTITY));
}
#[Route('/cases/delete', name: 'admin_evals_case_delete', methods: ['POST'])]
public function deleteCase(Request $request, EvalAdminService $evals): Response
{
$this->denyAccessUnlessGranted(ApplicationRoles::ROLE_KNOWLEDGE_ADMIN);
$type = trim((string) $request->request->get('type', 'retrieval'));
$caseId = trim((string) $request->request->get('case_id', ''));
if (!$this->isCsrfTokenValid(
sprintf('admin_eval_case_delete_%s_%s', $type, $caseId),
(string) $request->request->get('_token')
)) {
throw $this->createAccessDeniedException();
}
try {
$deleted = $evals->deleteCase($type, $caseId);
$type = (string) ($deleted['type'] ?? $type);
$this->addFlash(
'success',
sprintf('Eval-Case "%s" wurde aus %s.ndjson entfernt.', (string) ($deleted['id'] ?? $caseId), $type)
);
} catch (\Throwable $e) {
$this->addFlash('danger', $e->getMessage());
}
if (!in_array($type, $evals->supportedTypeNames(), true)) {
$type = 'retrieval';
}
return $this->redirectToRoute('admin_evals_case_new', [
'type' => $type,
]);
}
}

View File

@@ -290,6 +290,77 @@ final readonly class EvalAdminService
];
}
/**
* @return array{type:string,id:string,path:string,case_count:int}
*/
public function deleteCase(string $type, string $caseId): array
{
$type = $this->assertSupportedType($type);
$caseId = $this->normalizeExistingCaseId($caseId);
$path = $this->caseFilePath($type);
if (!is_file($path)) {
throw new \RuntimeException(sprintf('Eval-Case-Datei wurde nicht gefunden: %s', $path));
}
$lines = file($path, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
throw new \RuntimeException(sprintf('Eval-Case-Datei konnte nicht gelesen werden: %s', $path));
}
$keptLines = [];
$deleted = false;
foreach ($lines as $line) {
$trimmed = trim((string) $line);
if ($trimmed === '') {
continue;
}
try {
$decoded = json_decode($trimmed, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new \RuntimeException(sprintf(
'Eval-Case-Datei enthält ungültiges JSON und wurde nicht verändert: %s',
$e->getMessage()
));
}
if (!is_array($decoded)) {
throw new \RuntimeException('Eval-Case-Datei enthält eine ungültige NDJSON-Zeile und wurde nicht verändert.');
}
if ((string) ($decoded['id'] ?? '') === $caseId) {
$deleted = true;
continue;
}
$keptLines[] = $trimmed;
}
if (!$deleted) {
throw new \RuntimeException(sprintf(
'Eval-Case "%s" wurde im Typ "%s" nicht gefunden.',
$caseId,
$type
));
}
$contents = $keptLines === [] ? '' : implode(PHP_EOL, $keptLines) . PHP_EOL;
$written = file_put_contents($path, $contents, LOCK_EX);
if ($written === false) {
throw new \RuntimeException(sprintf('Eval-Case-Datei konnte nicht geschrieben werden: %s', $path));
}
return [
'type' => $type,
'id' => $caseId,
'path' => $path,
'case_count' => count($this->loadCases($type)),
];
}
/**
* @param array<int, EvalCase> $cases
* @return array<int, EvalCase>
@@ -411,6 +482,23 @@ final readonly class EvalAdminService
return $id;
}
private function normalizeExistingCaseId(string $id): string
{
$id = trim($id);
if ($id === '') {
throw new \InvalidArgumentException('Es wurde keine Eval-Case-ID zum Löschen übergeben.');
}
if (preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $id) !== 1) {
throw new \InvalidArgumentException(
'Die Eval-Case-ID ist ungültig. Erlaubt sind nur Buchstaben, Zahlen, Unterstriche und Bindestriche.'
);
}
return $id;
}
private function caseIdExists(string $id): bool
{
foreach (array_keys(self::TYPES) as $type) {