feat(documents) : bouton reload explorateur + liaison d'un fichier du partage SMB à un ticket
This commit is contained in:
@@ -8,13 +8,22 @@ use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\Task;
|
||||
use App\Entity\TaskDocument;
|
||||
use App\Service\Share\Exception\InvalidPathException;
|
||||
use App\Service\Share\Exception\ShareConnectionException;
|
||||
use App\Service\Share\Exception\ShareNotConfiguredException;
|
||||
use App\Service\Share\FileEntry;
|
||||
use App\Service\Share\FileSource;
|
||||
use App\Service\Share\SharePathResolver;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @implements ProcessorInterface<TaskDocument, TaskDocument>
|
||||
*/
|
||||
@@ -55,6 +64,8 @@ final readonly class TaskDocumentProcessor implements ProcessorInterface
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Security $security,
|
||||
private RequestStack $requestStack,
|
||||
private FileSource $fileSource,
|
||||
private SharePathResolver $pathResolver,
|
||||
private string $uploadDir,
|
||||
) {}
|
||||
|
||||
@@ -69,6 +80,44 @@ final readonly class TaskDocumentProcessor implements ProcessorInterface
|
||||
throw new BadRequestHttpException('No request available.');
|
||||
}
|
||||
|
||||
// Deux modes de création : upload d'un fichier (multipart) ou lien vers un fichier du partage SMB (JSON).
|
||||
$sharePath = $this->extractSharePath($request);
|
||||
|
||||
$document = null !== $sharePath
|
||||
? $this->createShareLink($request, $sharePath)
|
||||
: $this->createUpload($request);
|
||||
|
||||
$document->setCreatedAt(new DateTimeImmutable());
|
||||
$document->setUploadedBy($this->security->getUser());
|
||||
|
||||
$this->entityManager->persist($document);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
private function extractSharePath(Request $request): ?string
|
||||
{
|
||||
// Lien SMB : champ multipart/form OU corps JSON { "sharePath": "..." }
|
||||
$fromForm = $request->request->get('sharePath');
|
||||
|
||||
if (is_string($fromForm) && '' !== $fromForm) {
|
||||
return $fromForm;
|
||||
}
|
||||
|
||||
if (str_contains((string) $request->headers->get('Content-Type'), 'application/json')) {
|
||||
$payload = json_decode($request->getContent() ?: '{}', true);
|
||||
|
||||
if (is_array($payload) && isset($payload['sharePath']) && is_string($payload['sharePath']) && '' !== $payload['sharePath']) {
|
||||
return $payload['sharePath'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function createUpload(Request $request): TaskDocument
|
||||
{
|
||||
$file = $request->files->get('file');
|
||||
|
||||
if (null === $file || !$file->isValid()) {
|
||||
@@ -79,17 +128,7 @@ final readonly class TaskDocumentProcessor implements ProcessorInterface
|
||||
throw new BadRequestHttpException('File size exceeds 50 MB limit.');
|
||||
}
|
||||
|
||||
$taskIri = $request->request->get('task', '');
|
||||
|
||||
if ('' === $taskIri) {
|
||||
throw new BadRequestHttpException('A task IRI is required.');
|
||||
}
|
||||
|
||||
$task = $this->entityManager->getRepository(Task::class)->find((int) basename($taskIri));
|
||||
|
||||
if (null === $task) {
|
||||
throw new BadRequestHttpException('Task not found.');
|
||||
}
|
||||
$task = $this->resolveTask($request->request->get('task', ''));
|
||||
|
||||
// Use server-detected MIME type (finfo), not the client-supplied one
|
||||
$originalName = $file->getClientOriginalName();
|
||||
@@ -101,8 +140,7 @@ final readonly class TaskDocumentProcessor implements ProcessorInterface
|
||||
}
|
||||
|
||||
$extension = self::MIME_TO_EXTENSION[$mimeType] ?? 'bin';
|
||||
$uuid = Uuid::v4()->toRfc4122();
|
||||
$fileName = $uuid.'.'.$extension;
|
||||
$fileName = Uuid::v4()->toRfc4122().'.'.$extension;
|
||||
|
||||
if (!is_dir($this->uploadDir)) {
|
||||
mkdir($this->uploadDir, 0o775, true);
|
||||
@@ -116,12 +154,89 @@ final readonly class TaskDocumentProcessor implements ProcessorInterface
|
||||
$document->setFileName($fileName);
|
||||
$document->setMimeType($mimeType);
|
||||
$document->setSize($fileSize);
|
||||
$document->setCreatedAt(new DateTimeImmutable());
|
||||
$document->setUploadedBy($this->security->getUser());
|
||||
|
||||
$this->entityManager->persist($document);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
private function createShareLink(Request $request, string $rawSharePath): TaskDocument
|
||||
{
|
||||
$taskIri = $request->request->get('task');
|
||||
|
||||
if (!is_string($taskIri) || '' === $taskIri) {
|
||||
$payload = json_decode($request->getContent() ?: '{}', true);
|
||||
$taskIri = is_array($payload) ? ($payload['task'] ?? '') : '';
|
||||
}
|
||||
|
||||
$task = $this->resolveTask((string) $taskIri);
|
||||
|
||||
try {
|
||||
$path = $this->pathResolver->normalizeRelative($rawSharePath);
|
||||
} catch (InvalidPathException) {
|
||||
throw new BadRequestHttpException('Invalid share path.');
|
||||
}
|
||||
|
||||
if ('' === $path) {
|
||||
throw new BadRequestHttpException('A share path is required.');
|
||||
}
|
||||
|
||||
$entry = $this->findShareEntry($path);
|
||||
|
||||
if (null === $entry) {
|
||||
throw new BadRequestHttpException('File not found on the share.');
|
||||
}
|
||||
|
||||
if (!in_array($entry->mimeType, self::ALLOWED_MIME_TYPES, true)) {
|
||||
throw new BadRequestHttpException(sprintf('File type "%s" is not allowed.', $entry->mimeType));
|
||||
}
|
||||
|
||||
$document = new TaskDocument();
|
||||
$document->setTask($task);
|
||||
$document->setOriginalName($entry->name);
|
||||
$document->setSharePath($path);
|
||||
$document->setMimeType($entry->mimeType);
|
||||
$document->setSize($entry->size);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les métadonnées (taille, type) du fichier sur le partage en listant son dossier parent.
|
||||
*/
|
||||
private function findShareEntry(string $path): ?FileEntry
|
||||
{
|
||||
$parent = dirname($path);
|
||||
$parent = ('.' === $parent || '/' === $parent) ? '' : $parent;
|
||||
$name = basename($path);
|
||||
|
||||
try {
|
||||
$entries = $this->fileSource->dir($parent);
|
||||
} catch (ShareNotConfiguredException) {
|
||||
throw new BadRequestHttpException('Share not configured.');
|
||||
} catch (ShareConnectionException) {
|
||||
throw new BadRequestHttpException('Unable to reach the share.');
|
||||
}
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!$entry->isDir && $entry->name === $name) {
|
||||
return $entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveTask(string $taskIri): Task
|
||||
{
|
||||
if ('' === $taskIri) {
|
||||
throw new BadRequestHttpException('A task IRI is required.');
|
||||
}
|
||||
|
||||
$task = $this->entityManager->getRepository(Task::class)->find((int) basename($taskIri));
|
||||
|
||||
if (null === $task) {
|
||||
throw new BadRequestHttpException('Task not found.');
|
||||
}
|
||||
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user