harden code

This commit is contained in:
team 1
2026-02-15 16:01:08 +01:00
parent 5b100039e0
commit c099f72703
13 changed files with 397 additions and 59 deletions

View File

@@ -1,13 +1,13 @@
<?php
namespace App\Controller\Admin;
use App\Entity\Document;
use App\Entity\DocumentVersion;
use App\Entity\IngestJob;
use App\Service\DocumentService;
use App\Service\IngestOrchestrator;
use App\Service\FormatText;
use App\Service\IngestJobService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -18,10 +18,15 @@ use Symfony\Component\Uid\Uuid;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
#[Route('/admin/documents')]
class DocumentController extends AbstractController
{
public function __construct(
private readonly FormatText $formatText,
)
{
}
#[Route('', name: 'admin_documents')]
public function index(EntityManagerInterface $em): Response
{
@@ -62,8 +67,10 @@ class DocumentController extends AbstractController
{
if ($request->isMethod('POST')) {
$title = $request->request->get('title');
$file = $request->files->get('file');
$title = $request->request->get('title') ?: $file->getClientOriginalName();
$title = $this->formatText->slugify($title);
if (!$file || !$title) {
$this->addFlash('error', 'Titel und Datei sind erforderlich.');
@@ -191,7 +198,7 @@ class DocumentController extends AbstractController
string $versionId,
Request $request,
EntityManagerInterface $em,
IngestOrchestrator $orchestrator
IngestJobService $jobService,
): ?RedirectResponse {
$dryRun = false;
if (!$this->isCsrfTokenValid('ingest_version', $request->request->get('_token'))) {
@@ -214,14 +221,47 @@ class DocumentController extends AbstractController
return null;
}
$orchestrator->runForVersion(
$version,
// ---------------------------------------------------------
// Asynchroner Ingest (ohne Messenger):
// 1) Job als QUEUED anlegen
// 2) Symfony-Command im Hintergrund starten
// 3) Direkt auf Job-Detailseite redirecten (Loader + Polling)
// ---------------------------------------------------------
$job = $jobService->startJob(
IngestJob::TYPE_DOCUMENT,
$this->getUser(),
$dryRun
$version->getDocument()->getId(),
$version->getId(),
null,
IngestJob::STATUS_QUEUED
);
return $this->redirectToRoute('admin_document_show', [
'id' => $version->getDocument()->getId()
// Hintergrundprozess starten (Provider-kompatibel, kein Worker/Daemon)
$projectDir = (string) $this->getParameter('kernel.project_dir');
$console = $projectDir . '/bin/console';
$cmd = sprintf(
'%s %s %s %s > /dev/null 2>&1 &',
escapeshellarg($console),
escapeshellarg('mto:agent:ingest:run'),
escapeshellarg((string) $job->getId()),
escapeshellarg('--no-interaction'),
);
// Best effort: wenn exec deaktiviert ist, sauber abbrechen.
if (!\function_exists('exec')) {
$jobService->markFailed($job, 'Server configuration does not allow background execution (exec disabled).');
$this->addFlash('error', 'Ingest konnte nicht asynchron gestartet werden (exec deaktiviert).');
return $this->redirectToRoute('admin_document_show', [
'id' => $version->getDocument()->getId()
]);
}
exec($cmd);
return $this->redirectToRoute('admin_job_show', [
'id' => (string) $job->getId(),
]);
}

View File

@@ -1,6 +1,5 @@
<?php
namespace App\Controller\Admin;
use App\Entity\IngestJob;
@@ -11,6 +10,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use App\Ingest\IngestFlow;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
#[Route('/admin/jobs')]
class IngestJobController extends AbstractController
@@ -44,13 +44,40 @@ class IngestJobController extends AbstractController
]);
}
#[Route(
'/{id}/status',
name: 'admin_job_status',
requirements: ['id' => '[0-9a-fA-F\-]{36}'],
methods: ['GET']
)]
public function status(string $id, EntityManagerInterface $em): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_USER');
/** @var IngestJob|null $job */
$job = $em->getRepository(IngestJob::class)->find($id);
if (!$job) {
throw new NotFoundHttpException();
}
return $this->json([
'id' => (string) $job->getId(),
'type' => $job->getType(),
'status' => $job->getStatus(),
'startedAt' => $job->getStartedAt()->format(DATE_ATOM),
'finishedAt' => $job->getFinishedAt()?->format(DATE_ATOM),
'errorMessage' => $job->getErrorMessage(),
]);
}
#[Route('/global-reindex', name: 'admin_global_reindex', methods: ['POST'])]
public function globalReindex(
IngestFlow $flow
): RedirectResponse {
$this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
$flow->globalReindex($this->getUser());
$flow->globalReindex();
return $this->redirectToRoute('admin_jobs');
}