diff --git a/src/Controller/Mail/MailLinkTaskController.php b/src/Controller/Mail/MailLinkTaskController.php new file mode 100644 index 0000000..86d96b8 --- /dev/null +++ b/src/Controller/Mail/MailLinkTaskController.php @@ -0,0 +1,69 @@ + '\d+'])] +#[IsGranted('IS_AUTHENTICATED_FULLY')] +class MailLinkTaskController extends AbstractController +{ + public function __construct( + private readonly MailMessageRepository $messageRepository, + private readonly TaskMailLinkRepository $linkRepository, + private readonly EntityManagerInterface $em, + private readonly MailAccessChecker $accessChecker, + ) {} + + public function __invoke(Request $request, int $id): JsonResponse + { + $this->accessChecker->ensureCanAccessMail($this->getUser()); + + $message = $this->messageRepository->find($id); + if (null === $message) { + throw new NotFoundHttpException('Message not found'); + } + + $body = json_decode($request->getContent(), true); + $taskId = $body['taskId'] ?? null; + + if (null === $taskId) { + throw new UnprocessableEntityHttpException('taskId is required'); + } + + $task = $this->em->getRepository(Task::class)->find($taskId); + if (null === $task) { + throw new NotFoundHttpException('Task not found'); + } + + $existing = $this->linkRepository->findByTaskAndMessage($task, $message); + if (null !== $existing) { + return $this->json(['message' => 'Already linked']); + } + + $link = new TaskMailLink(); + $link->setTask($task); + $link->setMailMessage($message); + $link->setLinkedAt(new DateTimeImmutable()); + $link->setLinkedBy($this->getUser()); + $this->em->persist($link); + $this->em->flush(); + + return $this->json(['linkId' => $link->getId(), 'taskId' => $task->getId(), 'messageId' => $message->getId()], 201); + } +} diff --git a/src/Controller/Mail/MailUnlinkTaskController.php b/src/Controller/Mail/MailUnlinkTaskController.php new file mode 100644 index 0000000..399959e --- /dev/null +++ b/src/Controller/Mail/MailUnlinkTaskController.php @@ -0,0 +1,54 @@ + '\d+', 'taskId' => '\d+'])] +#[IsGranted('IS_AUTHENTICATED_FULLY')] +class MailUnlinkTaskController extends AbstractController +{ + public function __construct( + private readonly MailMessageRepository $messageRepository, + private readonly TaskMailLinkRepository $linkRepository, + private readonly EntityManagerInterface $em, + private readonly MailAccessChecker $accessChecker, + ) {} + + public function __invoke(int $id, int $taskId): JsonResponse + { + $this->accessChecker->ensureCanAccessMail($this->getUser()); + + $message = $this->messageRepository->find($id); + if (null === $message) { + throw new NotFoundHttpException('Message not found'); + } + + $task = $this->em->getRepository(Task::class)->find($taskId); + if (null === $task) { + throw new NotFoundHttpException('Task not found'); + } + + $link = $this->linkRepository->findByTaskAndMessage($task, $message); + if (null === $link) { + throw new NotFoundHttpException('Link not found'); + } + + $this->em->remove($link); + $this->em->flush(); + + return $this->json(null, Response::HTTP_NO_CONTENT); + } +} diff --git a/src/Controller/Mail/TaskMailsListController.php b/src/Controller/Mail/TaskMailsListController.php new file mode 100644 index 0000000..6b2180e --- /dev/null +++ b/src/Controller/Mail/TaskMailsListController.php @@ -0,0 +1,54 @@ + '\d+'])] +#[IsGranted('IS_AUTHENTICATED_FULLY')] +class TaskMailsListController extends AbstractController +{ + public function __construct( + private readonly EntityManagerInterface $em, + private readonly TaskMailLinkRepository $linkRepository, + private readonly MailAccessChecker $accessChecker, + ) {} + + public function __invoke(int $id): JsonResponse + { + $this->accessChecker->ensureCanAccessMail($this->getUser()); + + $task = $this->em->getRepository(Task::class)->find($id); + if (null === $task) { + throw new NotFoundHttpException('Task not found'); + } + + $links = $this->linkRepository->findByTask($task); + + $data = array_map(static fn ($link) => [ + 'id' => $link->getMailMessage()->getId(), + 'messageId' => $link->getMailMessage()->getMessageId(), + 'subject' => $link->getMailMessage()->getSubject(), + 'fromAddress' => $link->getMailMessage()->getFromAddress(), + 'fromName' => $link->getMailMessage()->getFromName(), + 'sentAt' => $link->getMailMessage()->getSentAt()->format(DateTimeInterface::ATOM), + 'isRead' => $link->getMailMessage()->isRead(), + 'isFlagged' => $link->getMailMessage()->isFlagged(), + 'snippet' => $link->getMailMessage()->getSnippet(), + 'linkedAt' => $link->getLinkedAt()->format(DateTimeInterface::ATOM), + ], $links); + + return $this->json($data); + } +} diff --git a/tests/Functional/Controller/Mail/MailTaskIntegrationControllerTest.php b/tests/Functional/Controller/Mail/MailTaskIntegrationControllerTest.php new file mode 100644 index 0000000..83feeb5 --- /dev/null +++ b/tests/Functional/Controller/Mail/MailTaskIntegrationControllerTest.php @@ -0,0 +1,72 @@ +request('POST', '/api/mail/messages/1/link-task', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode(['taskId' => 1])); + + self::assertResponseStatusCodeSame(401); + } + + public function testLinkTaskReturns403ForRoleClient(): void + { + $client = static::createClient(); + $container = static::getContainer(); + $em = $container->get('doctrine.orm.entity_manager'); + + $clientUser = $em->getRepository(User::class)->findOneBy(['username' => 'client-liot']); + $client->loginUser($clientUser); + $client->request('POST', '/api/mail/messages/1/link-task', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode(['taskId' => 1])); + + self::assertResponseStatusCodeSame(403); + } + + public function testUnlinkTaskReturns401WhenNotAuthenticated(): void + { + $client = static::createClient(); + $client->request('DELETE', '/api/mail/messages/1/link-task/1'); + + self::assertResponseStatusCodeSame(401); + } + + public function testTaskMailsListReturns401WhenNotAuthenticated(): void + { + $client = static::createClient(); + $client->request('GET', '/api/tasks/1/mails'); + + self::assertResponseStatusCodeSame(401); + } + + public function testTaskMailsListReturns403ForRoleClient(): void + { + $client = static::createClient(); + $container = static::getContainer(); + $em = $container->get('doctrine.orm.entity_manager'); + + $clientUser = $em->getRepository(User::class)->findOneBy(['username' => 'client-liot']); + $client->loginUser($clientUser); + $client->request('GET', '/api/tasks/1/mails'); + + self::assertResponseStatusCodeSame(403); + } + + public function testCreateTaskReturns401WhenNotAuthenticated(): void + { + $client = static::createClient(); + $client->request('POST', '/api/mail/messages/1/create-task', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode(['projectId' => 1])); + + self::assertResponseStatusCodeSame(401); + } +}