src/Controller/FeedbackController.php line 71

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controller;
  4. use App\Entity\User;
  5. use App\Entity\FeedbackScreenshot;
  6. use App\Repository\FeedbackScreenshotRepository;
  7. use App\Service\FeedbackService;
  8. use App\Service\NotionFeedbackService;
  9. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  10. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  11. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  12. use Symfony\Component\HttpFoundation\JsonResponse;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. /**
  18.  * Contrôleur pour la gestion des tickets de feedback.
  19.  * 
  20.  * Expose les API pour la création de tickets et la récupération des screenshots.
  21.  * Accessible aux admins, et aux utilisateurs du client id=1 (Bambboo).
  22.  *
  23.  * @Route("/api/feedback")
  24.  */
  25. class FeedbackController extends AbstractController
  26. {
  27.     /**
  28.      * @var FeedbackService
  29.      */
  30.     private $feedbackService;
  31.     /**
  32.      * @var NotionFeedbackService
  33.      */
  34.     private $notionService;
  35.     /**
  36.      * @var FeedbackScreenshotRepository
  37.      */
  38.     private $screenshotRepository;
  39.     /**
  40.      * Constructeur du contrôleur.
  41.      *
  42.      * @param FeedbackService $feedbackService
  43.      * @param NotionFeedbackService $notionService
  44.      * @param FeedbackScreenshotRepository $screenshotRepository
  45.      */
  46.     public function __construct(
  47.         FeedbackService $feedbackService,
  48.         NotionFeedbackService $notionService,
  49.         FeedbackScreenshotRepository $screenshotRepository
  50.     ) {
  51.         $this->feedbackService $feedbackService;
  52.         $this->notionService $notionService;
  53.         $this->screenshotRepository $screenshotRepository;
  54.     }
  55.     /**
  56.      * Crée un nouveau ticket de feedback.
  57.      *
  58.      * @Route("", name="feedback.create", methods={"POST"})
  59.      *
  60.      * @param Request $request
  61.      * @return JsonResponse
  62.      */
  63.     public function create(Request $request): JsonResponse
  64.     {
  65.         try {
  66.             $user $this->getUser();
  67.             if (!$user instanceof User) {
  68.                 return $this->json(['error' => 'Utilisateur non authentifié'], Response::HTTP_UNAUTHORIZED);
  69.             }
  70.             if (!$this->isGranted('ROLE_ADMIN')) {
  71.                 if (!$user->getClient() || (int) $user->getClient()->getId() !== 1) {
  72.                     return $this->json(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN);
  73.                 }
  74.             }
  75.             // Récupération des données JSON
  76.             $content $request->getContent();
  77.             $data json_decode($contenttrue);
  78.             if (json_last_error() !== JSON_ERROR_NONE) {
  79.                 return $this->json(['error' => 'Payload JSON invalide'], Response::HTTP_BAD_REQUEST);
  80.             }
  81.             // Extraction des screenshots du payload
  82.             $screenshots $data['screenshots'] ?? [];
  83.             unset($data['screenshots']);
  84.             // Création du ticket
  85.             $ticket $this->feedbackService->createTicket($user$data$screenshots);
  86.             // Synchronisation avec Notion
  87.             $notionPageId null;
  88.             $warnings = [];
  89.             if ($this->notionService->isConfigured()) {
  90.                 $notionPageId $this->notionService->createFeedbackPage($ticket);
  91.                 
  92.                 if ($notionPageId) {
  93.                     $this->feedbackService->setNotionPageId($ticket$notionPageId);
  94.                 } else {
  95.                     $warnings[] = [
  96.                         'source' => 'notion',
  97.                         'message' => 'Ticket enregistré, mais la synchronisation Notion a échoué.',
  98.                     ];
  99.                 }
  100.             }
  101.             // Construction de la réponse
  102.             $response = [
  103.                 'data' => $this->serializeTicket($ticket),
  104.             ];
  105.             if (!empty($warnings)) {
  106.                 $response['warnings'] = $warnings;
  107.                 return $this->json($responseResponse::HTTP_ACCEPTED);
  108.             }
  109.             return $this->json($responseResponse::HTTP_CREATED);
  110.         } catch (\InvalidArgumentException $e) {
  111.             $errors json_decode($e->getMessage(), true);
  112.             return $this->json([
  113.                 'error' => 'Payload invalide',
  114.                 'details' => $errors,
  115.             ], Response::HTTP_BAD_REQUEST);
  116.         } catch (\Exception $e) {
  117.             return $this->json([
  118.                 'error' => 'Impossible d\'enregistrer le ticket',
  119.                 'message' => $e->getMessage(),
  120.             ], Response::HTTP_INTERNAL_SERVER_ERROR);
  121.         }
  122.     }
  123.     /**
  124.      * Récupère un screenshot par son ID.
  125.      * Accessible publiquement pour permettre l'affichage dans Notion.
  126.      *
  127.      * @Route("/screenshots/{id}", name="feedback.screenshot", methods={"GET"}, requirements={"id"="\d+"})
  128.      *
  129.      * @param int $id
  130.      * @return Response
  131.      */
  132.     public function getScreenshot(int $id): Response
  133.     {
  134.         $screenshot $this->screenshotRepository->find($id);
  135.         if (!$screenshot) {
  136.             return $this->json(['error' => 'Screenshot non trouvé'], Response::HTTP_NOT_FOUND);
  137.         }
  138.         // Récupération du fichier
  139.         $filePath $this->feedbackService->getScreenshotFullPath($screenshot);
  140.         if (!file_exists($filePath)) {
  141.             return $this->json(['error' => 'Fichier non trouvé'], Response::HTTP_NOT_FOUND);
  142.         }
  143.         // Envoi du fichier
  144.         $response = new BinaryFileResponse($filePath);
  145.         $response->headers->set('Content-Type'$screenshot->getMimeType());
  146.         $response->headers->set('Content-Length', (string) $screenshot->getSizeBytes());
  147.         $response->setContentDisposition(
  148.             ResponseHeaderBag::DISPOSITION_INLINE,
  149.             $screenshot->getFilename()
  150.         );
  151.         // Cache d'un an (fichiers immuables)
  152.         $response->setPublic();
  153.         $response->setMaxAge(31536000);
  154.         $response->setImmutable(true);
  155.         return $response;
  156.     }
  157.     /**
  158.      * Liste les tickets de feedback (pour les admins).
  159.      *
  160.      * @Route("", name="feedback.list", methods={"GET"})
  161.      * @IsGranted("ROLE_ADMIN")
  162.      *
  163.      * @param Request $request
  164.      * @return JsonResponse
  165.      */
  166.     public function list(Request $request): JsonResponse
  167.     {
  168.         $limit min((int) $request->query->get('limit'20), 100);
  169.         $offset = (int) $request->query->get('offset'0);
  170.         $ticketRepository $this->feedbackService->getTicket(0); // Hack pour accéder au repo via le service
  171.         // On utilise directement le repository injecté via le service
  172.         
  173.         // Récupération simplifiée via EntityManager
  174.         $em $this->getDoctrine()->getManager();
  175.         $tickets $em->getRepository(\App\Entity\FeedbackTicket::class)
  176.             ->findBy([], ['createdAt' => 'DESC'], $limit$offset);
  177.         $data = [];
  178.         foreach ($tickets as $ticket) {
  179.             $data[] = $this->serializeTicket($ticket);
  180.         }
  181.         return $this->json(['data' => $data]);
  182.     }
  183.     /**
  184.      * Sérialise un ticket pour la réponse JSON.
  185.      *
  186.      * @param \App\Entity\FeedbackTicket $ticket
  187.      * @return array
  188.      */
  189.     private function serializeTicket(\App\Entity\FeedbackTicket $ticket): array
  190.     {
  191.         $createdBy $ticket->getCreatedBy();
  192.         
  193.         $serialized = [
  194.             'id' => $ticket->getId(),
  195.             'type' => $ticket->getType(),
  196.             'status' => $ticket->getStatus(),
  197.             'priorityScore' => $ticket->getPriorityScore(),
  198.             'priorityLabel' => $ticket->getPriorityLabel(),
  199.             'title' => $ticket->getTitle(),
  200.             'issueDescription' => $ticket->getIssueDescription(),
  201.             'expectedBehavior' => $ticket->getExpectedBehavior(),
  202.             'pageUrl' => $ticket->getPageUrl(),
  203.             'notionPageId' => $ticket->getNotionPageId(),
  204.             'createdAt' => $ticket->getCreatedAt()->format(\DateTime::ATOM),
  205.             'updatedAt' => $ticket->getUpdatedAt()->format(\DateTime::ATOM),
  206.             'resolvedAt' => $ticket->getResolvedAt() ? $ticket->getResolvedAt()->format(\DateTime::ATOM) : null,
  207.             'reporter' => $createdBy ? [
  208.                 'id' => $createdBy->getId(),
  209.                 'email' => $createdBy->getEmail(),
  210.                 'firstName' => $createdBy->getFirstname(),
  211.                 'lastName' => $createdBy->getLastname(),
  212.             ] : null,
  213.             'screenshots' => [],
  214.         ];
  215.         // Ajout des screenshots
  216.         foreach ($ticket->getScreenshots() as $screenshot) {
  217.             $serialized['screenshots'][] = [
  218.                 'id' => $screenshot->getId(),
  219.                 'filename' => $screenshot->getFilename(),
  220.                 'mimeType' => $screenshot->getMimeType(),
  221.                 'sizeBytes' => $screenshot->getSizeBytes(),
  222.                 'formattedSize' => $screenshot->getFormattedSize(),
  223.                 'isCapture' => $screenshot->isCapture(),
  224.                 'pageUrl' => $screenshot->getPageUrl(),
  225.                 'createdAt' => $screenshot->getCreatedAt()->format(\DateTime::ATOM),
  226.             ];
  227.         }
  228.         return $serialized;
  229.     }
  230. }