feat(mail) : migrate Mail integration into module (back)

LST-67 (2.5) backend. Behaviour-preserving move of the IMAP mail integration
into src/Module/Mail/. All /api/mail/* routes, securities (ROLE_CLIENT still
excluded via MailAccessChecker) and the async sync are unchanged.

- 4 entities + 4 repositories (Domain interfaces + Doctrine impls, bound).
  TaskMailLink.task now references TaskInterface (contract) instead of the
  concrete PM Task. Link/unlink/list-mails controllers load tasks via
  TaskRepositoryInterface; MailCreateTaskController keeps the concrete Task
  (instantiation) — documented Mail->PM coupling.
- Domain (MailProviderInterface, exception), Application (5 DTOs, MailSyncService,
  MailSyncRequested message + handler), Infrastructure (ImapMailProvider +
  MimeHeaderDecoder, MailAccessChecker, 2 console commands, 12 controllers,
  ApiPlatform state + MailSettings resource). TokenEncryptor stays shared.
- doctrine mapping Mail; messenger routing repointed; services.yaml repo +
  provider bindings; MailModule registered (id mail, mail.access/configure).
- #[Auditable] + Timestampable on MailConfiguration only (additive migration);
  IMAP data entities keep their own sync timestamps.

163 tests green, mapping valid, no route regression, cs-fixer clean.
This commit is contained in:
Matthieu
2026-06-20 19:44:19 +02:00
parent 57ccd9a740
commit 25d3a693f9
55 changed files with 453 additions and 209 deletions
+1 -1
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\DataFixtures;
use App\Entity\MailConfiguration;
use App\Entity\ZimbraConfiguration;
use App\Enum\ContractType;
use App\Module\Absence\Domain\Entity\AbsenceBalance;
@@ -17,6 +16,7 @@ use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\Directory\Domain\Entity\Prospect;
use App\Module\Directory\Domain\Enum\ProspectStatus;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
final readonly class MailAttachmentDto
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
final readonly class MailFolderDto
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
final readonly class MailMessageDetailDto
{
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
use DateTimeImmutable;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Dto;
namespace App\Module\Mail\Application\Dto;
use DateTimeImmutable;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Message;
namespace App\Module\Mail\Application\Message;
final readonly class MailSyncRequested
{
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\MessageHandler;
namespace App\Module\Mail\Application\MessageHandler;
use App\Message\MailSyncRequested;
use App\Repository\MailFolderRepository;
use App\Service\MailSyncService;
use App\Module\Mail\Application\Message\MailSyncRequested;
use App\Module\Mail\Application\Service\MailSyncService;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Throwable;
@@ -16,7 +16,7 @@ final readonly class MailSyncRequestedHandler
{
public function __construct(
private MailSyncService $mailSyncService,
private MailFolderRepository $folderRepository,
private MailFolderRepositoryInterface $folderRepository,
private LoggerInterface $logger,
) {}
@@ -2,16 +2,16 @@
declare(strict_types=1);
namespace App\Service;
namespace App\Module\Mail\Application\Service;
use App\Entity\MailFolder;
use App\Entity\MailMessage;
use App\Mail\Dto\MailSyncReport;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailConfigurationRepository;
use App\Repository\MailFolderRepository;
use App\Repository\MailMessageRepository;
use App\Module\Mail\Application\Dto\MailSyncReport;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
@@ -27,9 +27,9 @@ final class MailSyncService
public function __construct(
private readonly MailProviderInterface $provider,
private readonly MailConfigurationRepository $configRepository,
private readonly MailFolderRepository $folderRepository,
private readonly MailMessageRepository $messageRepository,
private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $entityManager,
private readonly LockFactory $lockFactory,
private readonly LoggerInterface $logger,
@@ -2,15 +2,22 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailConfigurationRepository;
use App\Shared\Domain\Attribute\Auditable;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailConfigurationRepository::class)]
#[Auditable]
#[ORM\Entity(repositoryClass: DoctrineMailConfigurationRepository::class)]
#[ORM\Table(name: 'mail_configuration')]
class MailConfiguration
class MailConfiguration implements TimestampableInterface, BlamableInterface
{
use TimestampableBlamableTrait;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Repository\MailFolderRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailFolderRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailFolderRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineMailFolderRepository::class)]
#[ORM\Table(name: 'mail_folder')]
#[ORM\Index(columns: ['parent_path'], name: 'idx_mail_folder_parent_path')]
class MailFolder
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Repository\MailMessageRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailMessageRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailMessageRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineMailMessageRepository::class)]
#[ORM\Table(name: 'mail_message')]
#[ORM\UniqueConstraint(name: 'uq_mail_message_folder_uid', columns: ['folder_id', 'uid'])]
#[ORM\Index(columns: ['sent_at'], name: 'idx_mail_message_sent_at')]
@@ -2,15 +2,15 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Mail\Domain\Entity;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskMailLinkRepository;
use App\Module\Mail\Infrastructure\Doctrine\DoctrineTaskMailLinkRepository;
use App\Shared\Domain\Contract\TaskInterface;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TaskMailLinkRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineTaskMailLinkRepository::class)]
#[ORM\Table(name: 'task_mail_link')]
#[ORM\UniqueConstraint(name: 'uq_task_mail_link', columns: ['task_id', 'mail_message_id'])]
class TaskMailLink
@@ -20,9 +20,9 @@ class TaskMailLink
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Task::class)]
#[ORM\ManyToOne(targetEntity: TaskInterface::class)]
#[ORM\JoinColumn(name: 'task_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private Task $task;
private TaskInterface $task;
#[ORM\ManyToOne(targetEntity: MailMessage::class)]
#[ORM\JoinColumn(name: 'mail_message_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
@@ -40,12 +40,12 @@ class TaskMailLink
return $this->id;
}
public function getTask(): Task
public function getTask(): TaskInterface
{
return $this->task;
}
public function setTask(Task $task): static
public function setTask(TaskInterface $task): static
{
$this->task = $task;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail\Exception;
namespace App\Module\Mail\Domain\Exception;
use RuntimeException;
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Mail;
namespace App\Module\Mail\Domain\Provider;
use App\Mail\Dto\MailFolderDto;
use App\Mail\Dto\MailMessageDetailDto;
use App\Mail\Dto\MailMessageHeaderDto;
use App\Mail\Exception\MailProviderException;
use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Module\Mail\Application\Dto\MailMessageDetailDto;
use App\Module\Mail\Application\Dto\MailMessageHeaderDto;
use App\Module\Mail\Domain\Exception\MailProviderException;
interface MailProviderInterface
{
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailConfiguration;
interface MailConfigurationRepositoryInterface
{
public function findSingleton(): ?MailConfiguration;
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailFolder;
interface MailFolderRepositoryInterface
{
/**
* @return list<MailFolder>
*/
public function findAllOrderedByPath(): array;
public function findByPath(string $path): ?MailFolder;
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
interface MailMessageRepositoryInterface
{
public function findById(int $id): ?MailMessage;
/**
* @return list<MailMessage>
*/
public function findAll(): array;
public function findByMessageId(string $messageId): ?MailMessage;
public function findByFolderAndUid(MailFolder $folder, int $uid): ?MailMessage;
/**
* @return list<MailMessage>
*/
public function findByFolderPaginated(MailFolder $folder, int $limit, int $offset): array;
public function countUnreadByFolder(MailFolder $folder): int;
public function findMaxUidInFolder(MailFolder $folder): int;
/**
* @return list<MailMessage>
*/
public function findLastNByFolder(MailFolder $folder, int $limit): array;
/**
* @return list<int>
*/
public function findAllUidsByFolder(MailFolder $folder): array;
/**
* Pagination cursor : retourne $limit messages apres le cursor (sentAt DESC, id DESC).
* Cursor format : base64url(sentAt_iso8601:id) - null pour la premiere page.
*
* @return array{messages: list<MailMessage>, nextCursor: ?string}
*/
public function findByFolderCursor(MailFolder $folder, int $limit, ?string $cursor): array;
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail\Domain\Repository;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Shared\Domain\Contract\TaskInterface;
interface TaskMailLinkRepositoryInterface
{
/**
* @return list<TaskMailLink>
*/
public function findByTask(TaskInterface $task): array;
public function findByTaskAndMessage(TaskInterface $task, MailMessage $message): ?TaskMailLink;
/**
* @return list<TaskMailLink>
*/
public function findByMessage(MailMessage $message): array;
}
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\ApiResource;
namespace App\Module\Mail\Infrastructure\ApiPlatform\Resource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Patch;
use App\State\Mail\MailSettingsProcessor;
use App\State\Mail\MailSettingsProvider;
use App\Module\Mail\Infrastructure\ApiPlatform\State\MailSettingsProcessor;
use App\Module\Mail\Infrastructure\ApiPlatform\State\MailSettingsProvider;
use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource(
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\State\Mail;
namespace App\Module\Mail\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\ApiResource\MailSettings;
use App\Entity\MailConfiguration;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Infrastructure\ApiPlatform\Resource\MailSettings;
use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface;
@@ -16,7 +16,7 @@ final readonly class MailSettingsProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
private MailConfigurationRepository $configRepository,
private MailConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor,
) {}
@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace App\State\Mail;
namespace App\Module\Mail\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\MailSettings;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Infrastructure\ApiPlatform\Resource\MailSettings;
final readonly class MailSettingsProvider implements ProviderInterface
{
public function __construct(
private MailConfigurationRepository $configRepository,
private MailConfigurationRepositoryInterface $configRepository,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): MailSettings
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Command;
namespace App\Module\Mail\Infrastructure\Console;
use App\Mail\MimeHeaderDecoder;
use App\Repository\MailMessageRepository;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Imap\MimeHeaderDecoder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
@@ -21,7 +21,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class MailRedecodeHeadersCommand extends Command
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $entityManager,
) {
parent::__construct();
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Command;
namespace App\Module\Mail\Infrastructure\Console;
use App\Repository\MailConfigurationRepository;
use App\Repository\MailFolderRepository;
use App\Service\MailSyncService;
use App\Module\Mail\Application\Service\MailSyncService;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -22,8 +22,8 @@ final class MailSyncCommand extends Command
{
public function __construct(
private readonly MailSyncService $mailSyncService,
private readonly MailConfigurationRepository $configRepository,
private readonly MailFolderRepository $folderRepository,
private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
) {
parent::__construct();
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -20,7 +20,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailAttachmentDownloadController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -37,7 +37,7 @@ class MailAttachmentDownloadController extends AbstractController
[$messageDbIdStr, $partNumber] = explode(':', $decoded, 2);
$messageDbId = (int) $messageDbIdStr;
$message = $this->messageRepository->find($messageDbId);
$message = $this->messageRepository->findById($messageDbId);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Entity\TaskMailLink;
use App\Module\Core\Domain\Entity\User;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -28,7 +28,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailCreateTaskController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
private readonly TaskRepositoryInterface $taskRepository,
@@ -38,7 +38,7 @@ class MailCreateTaskController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Repository\MailFolderRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -17,7 +17,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailFoldersListController extends AbstractController
{
public function __construct(
private readonly MailFolderRepository $folderRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -2,13 +2,13 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\MailMessageRepository;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -24,8 +24,9 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailLinkTaskController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly TaskMailLinkRepository $linkRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -34,7 +35,7 @@ class MailLinkTaskController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -46,7 +47,7 @@ class MailLinkTaskController extends AbstractController
throw new UnprocessableEntityHttpException('taskId is required');
}
$task = $this->em->getRepository(Task::class)->find($taskId);
$task = $this->taskRepository->findById((int) $taskId);
if (null === $task) {
throw new NotFoundHttpException('Task not found');
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -22,7 +22,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageDetailController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly MailAccessChecker $accessChecker,
private readonly CacheItemPoolInterface $cache,
@@ -32,7 +32,7 @@ class MailMessageDetailController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,7 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageFlagController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
@@ -31,7 +31,7 @@ class MailMessageFlagController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,7 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageReadController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
@@ -31,7 +31,7 @@ class MailMessageReadController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Repository\MailFolderRepository;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -20,8 +20,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessagesListController extends AbstractController
{
public function __construct(
private readonly MailFolderRepository $folderRepository,
private readonly MailMessageRepository $messageRepository,
private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Message\MailSyncRequested;
use App\Security\MailAccessChecker;
use App\Module\Mail\Application\Message\MailSyncRequested;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -2,10 +2,10 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException;
use App\Mail\MailProviderInterface;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
@@ -2,12 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\MailMessageRepository;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,8 +21,9 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailUnlinkTaskController extends AbstractController
{
public function __construct(
private readonly MailMessageRepository $messageRepository,
private readonly TaskMailLinkRepository $linkRepository,
private readonly MailMessageRepositoryInterface $messageRepository,
private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -31,12 +32,12 @@ class MailUnlinkTaskController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id);
$message = $this->messageRepository->findById($id);
if (null === $message) {
throw new NotFoundHttpException('Message not found');
}
$task = $this->em->getRepository(Task::class)->find($taskId);
$task = $this->taskRepository->findById($taskId);
if (null === $task) {
throw new NotFoundHttpException('Task not found');
}
@@ -2,13 +2,12 @@
declare(strict_types=1);
namespace App\Controller\Mail;
namespace App\Module\Mail\Infrastructure\Controller;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Repository\TaskMailLinkRepository;
use App\Security\MailAccessChecker;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -20,8 +19,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class TaskMailsListController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly TaskMailLinkRepository $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly MailAccessChecker $accessChecker,
) {}
@@ -29,7 +28,7 @@ class TaskMailsListController extends AbstractController
{
$this->accessChecker->ensureCanAccessMail($this->getUser());
$task = $this->em->getRepository(Task::class)->find($id);
$task = $this->taskRepository->findById($id);
if (null === $task) {
throw new NotFoundHttpException('Task not found');
}
@@ -2,13 +2,17 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailConfiguration;
use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class MailConfigurationRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<MailConfiguration>
*/
class DoctrineMailConfigurationRepository extends ServiceEntityRepository implements MailConfigurationRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
@@ -2,13 +2,17 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class MailFolderRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<MailFolder>
*/
class DoctrineMailFolderRepository extends ServiceEntityRepository implements MailFolderRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
@@ -2,22 +2,31 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailFolder;
use App\Entity\MailMessage;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class MailMessageRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<MailMessage>
*/
class DoctrineMailMessageRepository extends ServiceEntityRepository implements MailMessageRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, MailMessage::class);
}
public function findById(int $id): ?MailMessage
{
return $this->find($id);
}
public function findByMessageId(string $messageId): ?MailMessage
{
return $this->findOneBy(['messageId' => $messageId]);
@@ -2,15 +2,19 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailMessage;
use App\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Shared\Domain\Contract\TaskInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class TaskMailLinkRepository extends ServiceEntityRepository
/**
* @extends ServiceEntityRepository<TaskMailLink>
*/
class DoctrineTaskMailLinkRepository extends ServiceEntityRepository implements TaskMailLinkRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
@@ -20,7 +24,7 @@ class TaskMailLinkRepository extends ServiceEntityRepository
/**
* @return list<TaskMailLink>
*/
public function findByTask(Task $task): array
public function findByTask(TaskInterface $task): array
{
return $this->createQueryBuilder('l')
->andWhere('l.task = :task')
@@ -31,7 +35,7 @@ class TaskMailLinkRepository extends ServiceEntityRepository
;
}
public function findByTaskAndMessage(Task $task, MailMessage $message): ?TaskMailLink
public function findByTaskAndMessage(TaskInterface $task, MailMessage $message): ?TaskMailLink
{
return $this->findOneBy(['task' => $task, 'mailMessage' => $message]);
}
@@ -2,14 +2,15 @@
declare(strict_types=1);
namespace App\Mail;
namespace App\Module\Mail\Infrastructure\Imap;
use App\Mail\Dto\MailAttachmentDto;
use App\Mail\Dto\MailFolderDto;
use App\Mail\Dto\MailMessageDetailDto;
use App\Mail\Dto\MailMessageHeaderDto;
use App\Mail\Exception\MailProviderException;
use App\Repository\MailConfigurationRepository;
use App\Module\Mail\Application\Dto\MailAttachmentDto;
use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Module\Mail\Application\Dto\MailMessageDetailDto;
use App\Module\Mail\Application\Dto\MailMessageHeaderDto;
use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Service\TokenEncryptor;
use DateTimeImmutable;
use Psr\Log\LoggerInterface;
@@ -24,7 +25,7 @@ final class ImapMailProvider implements MailProviderInterface
private ?Client $client = null;
public function __construct(
private readonly MailConfigurationRepository $configRepository,
private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly TokenEncryptor $tokenEncryptor,
private readonly LoggerInterface $logger,
) {}
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Mail;
namespace App\Module\Mail\Infrastructure\Imap;
use const ICONV_MIME_DECODE_CONTINUE_ON_ERROR;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\Security;
namespace App\Module\Mail\Infrastructure\Security;
use App\Shared\Domain\Contract\UserInterface as SharedUserInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Module\Mail;
use App\Shared\Domain\Module\ModuleInterface;
final class MailModule implements ModuleInterface
{
public static function id(): string
{
return 'mail';
}
public static function label(): string
{
return 'Messagerie';
}
public static function isRequired(): bool
{
return false;
}
/**
* Permissions RBAC fin du Module Mail.
*
* Additif : alimente le catalogue RBAC. La sécurité des opérations API
* reste en ROLE_USER/ROLE_ADMIN (non recâblée ici).
*
* @return list<array{code: string, label: string}>
*/
public static function permissions(): array
{
return [
['code' => 'mail.access', 'label' => 'Accéder à la messagerie'],
['code' => 'mail.configure', 'label' => 'Configurer la messagerie'],
];
}
}