293 lines
12 KiB
PHP
293 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Entity\Comment;
|
|
use App\Entity\Document;
|
|
use App\Enum\DocumentType;
|
|
use App\Repository\ProfileRepository;
|
|
use App\Service\DocumentStorageService;
|
|
use DateTimeImmutable;
|
|
use DateTimeInterface;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
|
|
#[Route('/api/comments')]
|
|
final class CommentController extends AbstractController
|
|
{
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $entityManager,
|
|
private readonly ProfileRepository $profiles,
|
|
private readonly DocumentStorageService $storageService,
|
|
) {}
|
|
|
|
#[Route('', name: 'api_comments_create', methods: ['POST'])]
|
|
public function create(Request $request): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$session = $request->getSession();
|
|
$profileId = $session->get('profileId');
|
|
if (!$profileId) {
|
|
return $this->json(['message' => 'Aucun profil actif.'], 401);
|
|
}
|
|
|
|
$profile = $this->profiles->find($profileId);
|
|
if (!$profile) {
|
|
return $this->json(['message' => 'Profil introuvable.'], 401);
|
|
}
|
|
|
|
// Parse fields from JSON or form-data
|
|
$contentType = $request->headers->get('Content-Type', '');
|
|
$isFormData = str_contains($contentType, 'multipart/form-data') || $request->files->count() > 0 || $request->request->has('content');
|
|
if ($isFormData) {
|
|
$content = trim((string) $request->request->get('content', ''));
|
|
$entityType = trim((string) $request->request->get('entityType', ''));
|
|
$entityId = trim((string) $request->request->get('entityId', ''));
|
|
$entityName = $request->request->get('entityName') ? trim((string) $request->request->get('entityName')) : null;
|
|
} else {
|
|
$payload = json_decode($request->getContent(), true);
|
|
if (!is_array($payload)) {
|
|
return $this->json(['message' => 'Payload JSON invalide.'], 400);
|
|
}
|
|
$content = trim((string) ($payload['content'] ?? ''));
|
|
$entityType = trim((string) ($payload['entityType'] ?? ''));
|
|
$entityId = trim((string) ($payload['entityId'] ?? ''));
|
|
$entityName = isset($payload['entityName']) ? trim((string) $payload['entityName']) : null;
|
|
}
|
|
|
|
if ('' === $content) {
|
|
return $this->json(['message' => 'Le contenu est requis.'], 400);
|
|
}
|
|
|
|
$allowedTypes = ['machine', 'piece', 'composant', 'product', 'piece_category', 'component_category', 'product_category', 'machine_skeleton'];
|
|
if (!in_array($entityType, $allowedTypes, true)) {
|
|
return $this->json(['message' => 'Type d\'entité invalide.'], 400);
|
|
}
|
|
|
|
if ('' === $entityId) {
|
|
return $this->json(['message' => 'L\'identifiant de l\'entité est requis.'], 400);
|
|
}
|
|
|
|
$authorName = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
|
if ('' === $authorName) {
|
|
$authorName = $profile->getEmail() ?? 'Inconnu';
|
|
}
|
|
|
|
$comment = new Comment();
|
|
$comment->setContent($content);
|
|
$comment->setEntityType($entityType);
|
|
$comment->setEntityId($entityId);
|
|
$comment->setEntityName($entityName);
|
|
$comment->setAuthorId($profileId);
|
|
$comment->setAuthorName($authorName);
|
|
|
|
$this->entityManager->persist($comment);
|
|
|
|
// Handle file uploads
|
|
$allowedMimeTypes = [
|
|
'application/pdf',
|
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp',
|
|
'text/plain', 'text/csv',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'application/msword', 'application/vnd.ms-excel',
|
|
'application/zip',
|
|
];
|
|
|
|
$files = $request->files->all('files');
|
|
foreach ($files as $file) {
|
|
if (!$file instanceof UploadedFile || !$file->isValid()) {
|
|
continue;
|
|
}
|
|
|
|
$detectedMime = $file->getMimeType() ?: 'application/octet-stream';
|
|
if (!in_array($detectedMime, $allowedMimeTypes, true)) {
|
|
return $this->json([
|
|
'message' => sprintf('Type de fichier non autorisé : %s', $detectedMime),
|
|
], 400);
|
|
}
|
|
|
|
$document = new Document();
|
|
$documentId = 'cl'.bin2hex(random_bytes(12));
|
|
$document->setId($documentId);
|
|
$document->setName($file->getClientOriginalName());
|
|
$document->setFilename($file->getClientOriginalName());
|
|
$document->setMimeType($file->getMimeType() ?: 'application/octet-stream');
|
|
$document->setSize((int) $file->getSize());
|
|
$document->setType(DocumentType::DOCUMENTATION);
|
|
$document->setComment($comment);
|
|
$comment->getDocuments()->add($document);
|
|
|
|
$extension = $this->storageService->extensionFromFilename($file->getClientOriginalName());
|
|
$relativePath = $this->storageService->storeFromPath(
|
|
$file->getPathname(),
|
|
$documentId,
|
|
$extension,
|
|
);
|
|
$document->setPath($relativePath);
|
|
|
|
$this->entityManager->persist($document);
|
|
}
|
|
|
|
$this->entityManager->flush();
|
|
|
|
return $this->json($this->normalize($comment), 201);
|
|
}
|
|
|
|
#[Route('/{id}/resolve', name: 'api_comments_resolve', methods: ['PATCH'])]
|
|
public function resolve(string $id, Request $request): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
|
|
|
|
$comment = $this->entityManager->getRepository(Comment::class)->find($id);
|
|
if (!$comment) {
|
|
return $this->json(['message' => 'Commentaire introuvable.'], 404);
|
|
}
|
|
|
|
$session = $request->getSession();
|
|
$profileId = $session->get('profileId');
|
|
$profile = $profileId ? $this->profiles->find($profileId) : null;
|
|
|
|
$resolverName = 'Inconnu';
|
|
if ($profile) {
|
|
$resolverName = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
|
if ('' === $resolverName) {
|
|
$resolverName = $profile->getEmail() ?? 'Inconnu';
|
|
}
|
|
}
|
|
|
|
$comment->setStatus('resolved');
|
|
$comment->setResolvedById($profileId);
|
|
$comment->setResolvedByName($resolverName);
|
|
$comment->setResolvedAt(new DateTimeImmutable());
|
|
|
|
$this->entityManager->flush();
|
|
|
|
return $this->json($this->normalize($comment));
|
|
}
|
|
|
|
#[Route('/search/list', name: 'api_comments_list', methods: ['GET'])]
|
|
public function list(Request $request): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$qb = $this->entityManager->getRepository(Comment::class)->createQueryBuilder('c');
|
|
|
|
$status = $request->query->get('status');
|
|
if ($status) {
|
|
$qb->andWhere('c.status = :status')->setParameter('status', $status);
|
|
}
|
|
|
|
$entityType = $request->query->get('entityType');
|
|
if ($entityType) {
|
|
$qb->andWhere('c.entityType = :entityType')->setParameter('entityType', $entityType);
|
|
}
|
|
|
|
$entityName = $request->query->get('entityName');
|
|
if ($entityName) {
|
|
$qb->andWhere('LOWER(c.entityName) LIKE LOWER(:entityName)')->setParameter('entityName', '%'.$entityName.'%');
|
|
}
|
|
|
|
// Count total before pagination
|
|
$countQb = clone $qb;
|
|
$total = (int) $countQb->select('COUNT(c.id)')->getQuery()->getSingleScalarResult();
|
|
|
|
// Sorting
|
|
$sortField = $request->query->get('sort', 'createdAt');
|
|
$sortDir = strtoupper($request->query->get('direction', 'DESC'));
|
|
$allowedSortFields = ['createdAt', 'authorName', 'status'];
|
|
if (!in_array($sortField, $allowedSortFields, true)) {
|
|
$sortField = 'createdAt';
|
|
}
|
|
if (!in_array($sortDir, ['ASC', 'DESC'], true)) {
|
|
$sortDir = 'DESC';
|
|
}
|
|
$qb->orderBy('c.'.$sortField, $sortDir);
|
|
|
|
// Pagination
|
|
$itemsPerPage = min((int) $request->query->get('itemsPerPage', '30'), 200);
|
|
$page = max((int) $request->query->get('page', '1'), 1);
|
|
$qb->setMaxResults($itemsPerPage)->setFirstResult(($page - 1) * $itemsPerPage);
|
|
|
|
$comments = $qb->getQuery()->getResult();
|
|
|
|
return $this->json([
|
|
'items' => array_map(fn (Comment $c) => $this->normalize($c), $comments),
|
|
'total' => $total,
|
|
]);
|
|
}
|
|
|
|
#[Route('/by-entity/{entityType}/{entityId}', name: 'api_comments_by_entity', methods: ['GET'])]
|
|
public function listByEntity(string $entityType, string $entityId, Request $request): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$criteria = ['entityType' => $entityType, 'entityId' => $entityId];
|
|
|
|
$status = $request->query->get('status');
|
|
if ($status) {
|
|
$criteria['status'] = $status;
|
|
}
|
|
|
|
$comments = $this->entityManager->getRepository(Comment::class)
|
|
->findBy($criteria, ['createdAt' => 'DESC'])
|
|
;
|
|
|
|
return $this->json(array_map(fn (Comment $c) => $this->normalize($c), $comments));
|
|
}
|
|
|
|
#[Route('/stats/unresolved-count', name: 'api_comments_unresolved_count', methods: ['GET'])]
|
|
public function unresolvedCount(): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$count = $this->entityManager->getRepository(Comment::class)
|
|
->count(['status' => 'open'])
|
|
;
|
|
|
|
return $this->json(['count' => $count]);
|
|
}
|
|
|
|
private function normalize(Comment $comment): array
|
|
{
|
|
$documents = [];
|
|
foreach ($comment->getDocuments() as $document) {
|
|
$documents[] = [
|
|
'id' => $document->getId(),
|
|
'name' => $document->getName(),
|
|
'filename' => $document->getFilename(),
|
|
'mimeType' => $document->getMimeType(),
|
|
'size' => $document->getSize(),
|
|
'type' => $document->getType()->value,
|
|
'fileUrl' => '/api/documents/'.$document->getId().'/file',
|
|
'downloadUrl' => '/api/documents/'.$document->getId().'/download',
|
|
'createdAt' => $document->getCreatedAt()->format(DateTimeInterface::ATOM),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'id' => $comment->getId(),
|
|
'content' => $comment->getContent(),
|
|
'entityType' => $comment->getEntityType(),
|
|
'entityId' => $comment->getEntityId(),
|
|
'entityName' => $comment->getEntityName(),
|
|
'authorId' => $comment->getAuthorId(),
|
|
'authorName' => $comment->getAuthorName(),
|
|
'status' => $comment->getStatus(),
|
|
'resolvedById' => $comment->getResolvedById(),
|
|
'resolvedByName' => $comment->getResolvedByName(),
|
|
'resolvedAt' => $comment->getResolvedAt()?->format(DateTimeInterface::ATOM),
|
|
'createdAt' => $comment->getCreatedAt()->format(DateTimeInterface::ATOM),
|
|
'updatedAt' => $comment->getUpdatedAt()->format(DateTimeInterface::ATOM),
|
|
'documents' => $documents,
|
|
];
|
|
}
|
|
}
|