Files
Inventory/src/Controller/DocumentServeController.php
Matthieu 476060cf7d WIP
2026-03-31 17:57:59 +02:00

119 lines
4.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Controller;
use App\Repository\DocumentRepository;
use App\Service\DocumentStorageService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use function strlen;
#[Route('/api/documents')]
class DocumentServeController extends AbstractController
{
public function __construct(
private readonly DocumentRepository $documentRepository,
private readonly DocumentStorageService $storageService,
) {}
#[Route('/{id}/file', name: 'document_serve_file', methods: ['GET'])]
public function serve(string $id): Response
{
$this->denyAccessUnlessGranted('ROLE_VIEWER');
$document = $this->documentRepository->find($id);
if (!$document) {
return $this->json(['error' => 'Document not found.'], 404);
}
$path = $document->getPath();
// Backward compatibility: serve Base64 data URIs from DB
if ($this->storageService->isBase64DataUri($path)) {
$parts = explode(',', $path, 2);
$content = base64_decode($parts[1] ?? '', true);
if (false === $content) {
return $this->json(['error' => 'Invalid document data.'], 500);
}
$disposition = HeaderUtils::makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $document->getFilename());
return new Response($content, 200, [
'Content-Type' => $document->getMimeType(),
'Content-Disposition' => $disposition,
'Content-Length' => (string) strlen($content),
'Cache-Control' => 'private, max-age=3600',
'X-Content-Type-Options' => 'nosniff',
'Content-Security-Policy' => 'sandbox',
]);
}
// File-based path: serve from disk
$absolutePath = $this->storageService->getAbsolutePath($path);
if (!file_exists($absolutePath)) {
return $this->json(['error' => 'File not found on disk.'], 404);
}
$response = new BinaryFileResponse($absolutePath);
$response->headers->set('Content-Type', $document->getMimeType());
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_INLINE,
$document->getFilename()
);
$response->headers->set('Cache-Control', 'private, max-age=3600');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Content-Security-Policy', 'sandbox');
return $response;
}
#[Route('/{id}/download', name: 'document_download_file', methods: ['GET'])]
public function download(string $id): Response
{
$this->denyAccessUnlessGranted('ROLE_VIEWER');
$document = $this->documentRepository->find($id);
if (!$document) {
return $this->json(['error' => 'Document not found.'], 404);
}
$path = $document->getPath();
if ($this->storageService->isBase64DataUri($path)) {
$parts = explode(',', $path, 2);
$content = base64_decode($parts[1] ?? '', true);
if (false === $content) {
return $this->json(['error' => 'Invalid document data.'], 500);
}
$disposition = HeaderUtils::makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $document->getFilename());
return new Response($content, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => $disposition,
'Content-Length' => (string) strlen($content),
]);
}
$absolutePath = $this->storageService->getAbsolutePath($path);
if (!file_exists($absolutePath)) {
return $this->json(['error' => 'File not found on disk.'], 404);
}
$response = new BinaryFileResponse($absolutePath);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$document->getFilename()
);
return $response;
}
}