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
+2
View File
@@ -10,6 +10,7 @@ declare(strict_types=1);
use App\Module\Absence\AbsenceModule; use App\Module\Absence\AbsenceModule;
use App\Module\Core\CoreModule; use App\Module\Core\CoreModule;
use App\Module\Directory\DirectoryModule; use App\Module\Directory\DirectoryModule;
use App\Module\Mail\MailModule;
use App\Module\ProjectManagement\ProjectManagementModule; use App\Module\ProjectManagement\ProjectManagementModule;
use App\Module\TimeTracking\TimeTrackingModule; use App\Module\TimeTracking\TimeTrackingModule;
@@ -19,4 +20,5 @@ return [
ProjectManagementModule::class, ProjectManagementModule::class,
AbsenceModule::class, AbsenceModule::class,
DirectoryModule::class, DirectoryModule::class,
MailModule::class,
]; ];
+5
View File
@@ -58,6 +58,11 @@ doctrine:
is_bundle: false is_bundle: false
dir: '%kernel.project_dir%/src/Module/Directory/Domain/Entity' dir: '%kernel.project_dir%/src/Module/Directory/Domain/Entity'
prefix: 'App\Module\Directory\Domain\Entity' prefix: 'App\Module\Directory\Domain\Entity'
Mail:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Module/Mail/Domain/Entity'
prefix: 'App\Module\Mail\Domain\Entity'
controller_resolver: controller_resolver:
auto_mapping: false auto_mapping: false
+1 -1
View File
@@ -23,7 +23,7 @@ framework:
# messenger:consume à maintenir. La sync de fond reste assurée par le cron OS # messenger:consume à maintenir. La sync de fond reste assurée par le cron OS
# (app:mail:sync, synchrone, indépendant du bus). Repasser à `async` + worker si # (app:mail:sync, synchrone, indépendant du bus). Repasser à `async` + worker si
# la boîte grossit au point que la sync à la demande approche le timeout PHP. # la boîte grossit au point que la sync à la demande approche le timeout PHP.
'App\Message\MailSyncRequested': sync 'App\Module\Mail\Application\Message\MailSyncRequested': sync
when@test: when@test:
framework: framework:
+10
View File
@@ -103,4 +103,14 @@ services:
App\Module\Directory\Domain\Repository\ProspectRepositoryInterface: '@App\Module\Directory\Infrastructure\Doctrine\DoctrineProspectRepository' App\Module\Directory\Domain\Repository\ProspectRepositoryInterface: '@App\Module\Directory\Infrastructure\Doctrine\DoctrineProspectRepository'
App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineMailConfigurationRepository'
App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineMailFolderRepository'
App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineMailMessageRepository'
App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface: '@App\Module\Mail\Infrastructure\Doctrine\DoctrineTaskMailLinkRepository'
App\Module\Mail\Domain\Provider\MailProviderInterface: '@App\Module\Mail\Infrastructure\Imap\ImapMailProvider'
App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier' App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier'
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Mail module: add Timestampable + Blamable tracking on mail_configuration.
*
* Purely additive — adds nullable audit columns to an existing table:
* created_at / updated_at (Timestampable)
* created_by / updated_by -> "user"(id) ON DELETE SET NULL (Blamable)
* No DROP/ALTER on existing data. Columns are lowercase snake_case.
* Hand-written to mirror the schema dump and guarantee zero destructive
* instruction. down() drops the new columns and their FKs/indexes.
*/
final class Version20260620200000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Mail: add Timestampable/Blamable columns on mail_configuration (additive)';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE mail_configuration ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD created_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD updated_by INT DEFAULT NULL');
$this->addSql('ALTER TABLE mail_configuration ADD CONSTRAINT FK_BFC0A7DBDE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('ALTER TABLE mail_configuration ADD CONSTRAINT FK_BFC0A7DB16FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
$this->addSql('CREATE INDEX IDX_BFC0A7DBDE12AB56 ON mail_configuration (created_by)');
$this->addSql('CREATE INDEX IDX_BFC0A7DB16FE72E1 ON mail_configuration (updated_by)');
$this->addSql("COMMENT ON COLUMN mail_configuration.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
$this->addSql("COMMENT ON COLUMN mail_configuration.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE mail_configuration DROP CONSTRAINT FK_BFC0A7DBDE12AB56');
$this->addSql('ALTER TABLE mail_configuration DROP CONSTRAINT FK_BFC0A7DB16FE72E1');
$this->addSql('DROP INDEX IDX_BFC0A7DBDE12AB56');
$this->addSql('DROP INDEX IDX_BFC0A7DB16FE72E1');
$this->addSql('ALTER TABLE mail_configuration DROP created_at');
$this->addSql('ALTER TABLE mail_configuration DROP updated_at');
$this->addSql('ALTER TABLE mail_configuration DROP created_by');
$this->addSql('ALTER TABLE mail_configuration DROP updated_by');
}
}
+1 -1
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\DataFixtures; namespace App\DataFixtures;
use App\Entity\MailConfiguration;
use App\Entity\ZimbraConfiguration; use App\Entity\ZimbraConfiguration;
use App\Enum\ContractType; use App\Enum\ContractType;
use App\Module\Absence\Domain\Entity\AbsenceBalance; 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\Client;
use App\Module\Directory\Domain\Entity\Prospect; use App\Module\Directory\Domain\Entity\Prospect;
use App\Module\Directory\Domain\Enum\ProspectStatus; 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\Project;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskEffort; use App\Module\ProjectManagement\Domain\Entity\TaskEffort;
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail\Dto; namespace App\Module\Mail\Application\Dto;
final readonly class MailAttachmentDto final readonly class MailAttachmentDto
{ {
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail\Dto; namespace App\Module\Mail\Application\Dto;
final readonly class MailFolderDto final readonly class MailFolderDto
{ {
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail\Dto; namespace App\Module\Mail\Application\Dto;
final readonly class MailMessageDetailDto final readonly class MailMessageDetailDto
{ {
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail\Dto; namespace App\Module\Mail\Application\Dto;
use DateTimeImmutable; use DateTimeImmutable;
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail\Dto; namespace App\Module\Mail\Application\Dto;
use DateTimeImmutable; use DateTimeImmutable;
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Message; namespace App\Module\Mail\Application\Message;
final readonly class MailSyncRequested final readonly class MailSyncRequested
{ {
@@ -2,11 +2,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\MessageHandler; namespace App\Module\Mail\Application\MessageHandler;
use App\Message\MailSyncRequested; use App\Module\Mail\Application\Message\MailSyncRequested;
use App\Repository\MailFolderRepository; use App\Module\Mail\Application\Service\MailSyncService;
use App\Service\MailSyncService; use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler; use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Throwable; use Throwable;
@@ -16,7 +16,7 @@ final readonly class MailSyncRequestedHandler
{ {
public function __construct( public function __construct(
private MailSyncService $mailSyncService, private MailSyncService $mailSyncService,
private MailFolderRepository $folderRepository, private MailFolderRepositoryInterface $folderRepository,
private LoggerInterface $logger, private LoggerInterface $logger,
) {} ) {}
@@ -2,16 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Service; namespace App\Module\Mail\Application\Service;
use App\Entity\MailFolder; use App\Module\Mail\Application\Dto\MailSyncReport;
use App\Entity\MailMessage; use App\Module\Mail\Domain\Entity\MailFolder;
use App\Mail\Dto\MailSyncReport; use App\Module\Mail\Domain\Entity\MailMessage;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Repository\MailFolderRepository; use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
@@ -27,9 +27,9 @@ final class MailSyncService
public function __construct( public function __construct(
private readonly MailProviderInterface $provider, private readonly MailProviderInterface $provider,
private readonly MailConfigurationRepository $configRepository, private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly MailFolderRepository $folderRepository, private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly LockFactory $lockFactory, private readonly LockFactory $lockFactory,
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
@@ -2,15 +2,22 @@
declare(strict_types=1); 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; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailConfigurationRepository::class)] #[Auditable]
#[ORM\Entity(repositoryClass: DoctrineMailConfigurationRepository::class)]
#[ORM\Table(name: 'mail_configuration')] #[ORM\Table(name: 'mail_configuration')]
class MailConfiguration class MailConfiguration implements TimestampableInterface, BlamableInterface
{ {
use TimestampableBlamableTrait;
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
@@ -2,13 +2,13 @@
declare(strict_types=1); 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 DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailFolderRepository::class)] #[ORM\Entity(repositoryClass: DoctrineMailFolderRepository::class)]
#[ORM\Table(name: 'mail_folder')] #[ORM\Table(name: 'mail_folder')]
#[ORM\Index(columns: ['parent_path'], name: 'idx_mail_folder_parent_path')] #[ORM\Index(columns: ['parent_path'], name: 'idx_mail_folder_parent_path')]
class MailFolder class MailFolder
@@ -2,13 +2,13 @@
declare(strict_types=1); 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 DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MailMessageRepository::class)] #[ORM\Entity(repositoryClass: DoctrineMailMessageRepository::class)]
#[ORM\Table(name: 'mail_message')] #[ORM\Table(name: 'mail_message')]
#[ORM\UniqueConstraint(name: 'uq_mail_message_folder_uid', columns: ['folder_id', 'uid'])] #[ORM\UniqueConstraint(name: 'uq_mail_message_folder_uid', columns: ['folder_id', 'uid'])]
#[ORM\Index(columns: ['sent_at'], name: 'idx_mail_message_sent_at')] #[ORM\Index(columns: ['sent_at'], name: 'idx_mail_message_sent_at')]
@@ -2,15 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Entity; namespace App\Module\Mail\Domain\Entity;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\Mail\Infrastructure\Doctrine\DoctrineTaskMailLinkRepository;
use App\Repository\TaskMailLinkRepository; use App\Shared\Domain\Contract\TaskInterface;
use App\Shared\Domain\Contract\UserInterface; use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TaskMailLinkRepository::class)] #[ORM\Entity(repositoryClass: DoctrineTaskMailLinkRepository::class)]
#[ORM\Table(name: 'task_mail_link')] #[ORM\Table(name: 'task_mail_link')]
#[ORM\UniqueConstraint(name: 'uq_task_mail_link', columns: ['task_id', 'mail_message_id'])] #[ORM\UniqueConstraint(name: 'uq_task_mail_link', columns: ['task_id', 'mail_message_id'])]
class TaskMailLink class TaskMailLink
@@ -20,9 +20,9 @@ class TaskMailLink
#[ORM\Column] #[ORM\Column]
private ?int $id = null; 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')] #[ORM\JoinColumn(name: 'task_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private Task $task; private TaskInterface $task;
#[ORM\ManyToOne(targetEntity: MailMessage::class)] #[ORM\ManyToOne(targetEntity: MailMessage::class)]
#[ORM\JoinColumn(name: 'mail_message_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] #[ORM\JoinColumn(name: 'mail_message_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
@@ -40,12 +40,12 @@ class TaskMailLink
return $this->id; return $this->id;
} }
public function getTask(): Task public function getTask(): TaskInterface
{ {
return $this->task; return $this->task;
} }
public function setTask(Task $task): static public function setTask(TaskInterface $task): static
{ {
$this->task = $task; $this->task = $task;
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail\Exception; namespace App\Module\Mail\Domain\Exception;
use RuntimeException; use RuntimeException;
@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail; namespace App\Module\Mail\Domain\Provider;
use App\Mail\Dto\MailFolderDto; use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Mail\Dto\MailMessageDetailDto; use App\Module\Mail\Application\Dto\MailMessageDetailDto;
use App\Mail\Dto\MailMessageHeaderDto; use App\Module\Mail\Application\Dto\MailMessageHeaderDto;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
interface MailProviderInterface 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); declare(strict_types=1);
namespace App\ApiResource; namespace App\Module\Mail\Infrastructure\ApiPlatform\Resource;
use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Patch;
use App\State\Mail\MailSettingsProcessor; use App\Module\Mail\Infrastructure\ApiPlatform\State\MailSettingsProcessor;
use App\State\Mail\MailSettingsProvider; use App\Module\Mail\Infrastructure\ApiPlatform\State\MailSettingsProvider;
use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource( #[ApiResource(
@@ -2,13 +2,13 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\State\Mail; namespace App\Module\Mail\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\ProcessorInterface;
use App\ApiResource\MailSettings; use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Entity\MailConfiguration; use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Infrastructure\ApiPlatform\Resource\MailSettings;
use App\Service\TokenEncryptor; use App\Service\TokenEncryptor;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@@ -16,7 +16,7 @@ final readonly class MailSettingsProcessor implements ProcessorInterface
{ {
public function __construct( public function __construct(
private EntityManagerInterface $em, private EntityManagerInterface $em,
private MailConfigurationRepository $configRepository, private MailConfigurationRepositoryInterface $configRepository,
private TokenEncryptor $tokenEncryptor, private TokenEncryptor $tokenEncryptor,
) {} ) {}
@@ -2,17 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\State\Mail; namespace App\Module\Mail\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface; use ApiPlatform\State\ProviderInterface;
use App\ApiResource\MailSettings; use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Infrastructure\ApiPlatform\Resource\MailSettings;
final readonly class MailSettingsProvider implements ProviderInterface final readonly class MailSettingsProvider implements ProviderInterface
{ {
public function __construct( public function __construct(
private MailConfigurationRepository $configRepository, private MailConfigurationRepositoryInterface $configRepository,
) {} ) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): MailSettings public function provide(Operation $operation, array $uriVariables = [], array $context = []): MailSettings
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Command; namespace App\Module\Mail\Infrastructure\Console;
use App\Mail\MimeHeaderDecoder; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Infrastructure\Imap\MimeHeaderDecoder;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
@@ -21,7 +21,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class MailRedecodeHeadersCommand extends Command final class MailRedecodeHeadersCommand extends Command
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
) { ) {
parent::__construct(); parent::__construct();
@@ -2,11 +2,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Command; namespace App\Module\Mail\Infrastructure\Console;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Application\Service\MailSyncService;
use App\Repository\MailFolderRepository; use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Service\MailSyncService; use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@@ -22,8 +22,8 @@ final class MailSyncCommand extends Command
{ {
public function __construct( public function __construct(
private readonly MailSyncService $mailSyncService, private readonly MailSyncService $mailSyncService,
private readonly MailConfigurationRepository $configRepository, private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly MailFolderRepository $folderRepository, private readonly MailFolderRepositoryInterface $folderRepository,
) { ) {
parent::__construct(); parent::__construct();
} }
@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -20,7 +20,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailAttachmentDownloadController extends AbstractController class MailAttachmentDownloadController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider, private readonly MailProviderInterface $mailProvider,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
) {} ) {}
@@ -37,7 +37,7 @@ class MailAttachmentDownloadController extends AbstractController
[$messageDbIdStr, $partNumber] = explode(':', $decoded, 2); [$messageDbIdStr, $partNumber] = explode(':', $decoded, 2);
$messageDbId = (int) $messageDbIdStr; $messageDbId = (int) $messageDbIdStr;
$message = $this->messageRepository->find($messageDbId); $message = $this->messageRepository->findById($messageDbId);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
@@ -2,17 +2,17 @@
declare(strict_types=1); 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\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\Project;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Module\ProjectManagement\Domain\Entity\TaskGroup; use App\Module\ProjectManagement\Domain\Entity\TaskGroup;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus; use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface; use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use App\Repository\MailMessageRepository;
use App\Security\MailAccessChecker;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -28,7 +28,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailCreateTaskController extends AbstractController class MailCreateTaskController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
private readonly TaskRepositoryInterface $taskRepository, private readonly TaskRepositoryInterface $taskRepository,
@@ -38,7 +38,7 @@ class MailCreateTaskController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id); $message = $this->messageRepository->findById($id);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Repository\MailFolderRepository; use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface; use DateTimeInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@@ -17,7 +17,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailFoldersListController extends AbstractController class MailFoldersListController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailFolderRepository $folderRepository, private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
) {} ) {}
@@ -2,13 +2,13 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Entity\TaskMailLink; use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Repository\TaskMailLinkRepository; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Security\MailAccessChecker; use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -24,8 +24,9 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailLinkTaskController extends AbstractController class MailLinkTaskController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly TaskMailLinkRepository $linkRepository, private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
) {} ) {}
@@ -34,7 +35,7 @@ class MailLinkTaskController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id); $message = $this->messageRepository->findById($id);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
@@ -46,7 +47,7 @@ class MailLinkTaskController extends AbstractController
throw new UnprocessableEntityHttpException('taskId is required'); throw new UnprocessableEntityHttpException('taskId is required');
} }
$task = $this->em->getRepository(Task::class)->find($taskId); $task = $this->taskRepository->findById((int) $taskId);
if (null === $task) { if (null === $task) {
throw new NotFoundHttpException('Task not found'); throw new NotFoundHttpException('Task not found');
} }
@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface; use DateTimeInterface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -22,7 +22,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageDetailController extends AbstractController class MailMessageDetailController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider, private readonly MailProviderInterface $mailProvider,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
private readonly CacheItemPoolInterface $cache, private readonly CacheItemPoolInterface $cache,
@@ -32,7 +32,7 @@ class MailMessageDetailController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id); $message = $this->messageRepository->findById($id);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,7 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageFlagController extends AbstractController class MailMessageFlagController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider, private readonly MailProviderInterface $mailProvider,
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
@@ -31,7 +31,7 @@ class MailMessageFlagController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id); $message = $this->messageRepository->findById($id);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,7 +21,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessageReadController extends AbstractController class MailMessageReadController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailProviderInterface $mailProvider, private readonly MailProviderInterface $mailProvider,
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
@@ -31,7 +31,7 @@ class MailMessageReadController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id); $message = $this->messageRepository->findById($id);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
@@ -2,11 +2,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Repository\MailFolderRepository; use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use DateTimeInterface; use DateTimeInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@@ -20,8 +20,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailMessagesListController extends AbstractController class MailMessagesListController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailFolderRepository $folderRepository, private readonly MailFolderRepositoryInterface $folderRepository,
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
) {} ) {}
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Message\MailSyncRequested; use App\Module\Mail\Application\Message\MailSyncRequested;
use App\Security\MailAccessChecker; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Repository\TaskMailLinkRepository; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Security\MailAccessChecker; use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
@@ -21,8 +21,9 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MailUnlinkTaskController extends AbstractController class MailUnlinkTaskController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly MailMessageRepository $messageRepository, private readonly MailMessageRepositoryInterface $messageRepository,
private readonly TaskMailLinkRepository $linkRepository, private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly TaskRepositoryInterface $taskRepository,
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
) {} ) {}
@@ -31,12 +32,12 @@ class MailUnlinkTaskController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$message = $this->messageRepository->find($id); $message = $this->messageRepository->findById($id);
if (null === $message) { if (null === $message) {
throw new NotFoundHttpException('Message not found'); throw new NotFoundHttpException('Message not found');
} }
$task = $this->em->getRepository(Task::class)->find($taskId); $task = $this->taskRepository->findById($taskId);
if (null === $task) { if (null === $task) {
throw new NotFoundHttpException('Task not found'); throw new NotFoundHttpException('Task not found');
} }
@@ -2,13 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Controller\Mail; namespace App\Module\Mail\Infrastructure\Controller;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Repository\TaskMailLinkRepository; use App\Module\Mail\Infrastructure\Security\MailAccessChecker;
use App\Security\MailAccessChecker; use App\Module\ProjectManagement\Domain\Repository\TaskRepositoryInterface;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -20,8 +19,8 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class TaskMailsListController extends AbstractController class TaskMailsListController extends AbstractController
{ {
public function __construct( public function __construct(
private readonly EntityManagerInterface $em, private readonly TaskRepositoryInterface $taskRepository,
private readonly TaskMailLinkRepository $linkRepository, private readonly TaskMailLinkRepositoryInterface $linkRepository,
private readonly MailAccessChecker $accessChecker, private readonly MailAccessChecker $accessChecker,
) {} ) {}
@@ -29,7 +28,7 @@ class TaskMailsListController extends AbstractController
{ {
$this->accessChecker->ensureCanAccessMail($this->getUser()); $this->accessChecker->ensureCanAccessMail($this->getUser());
$task = $this->em->getRepository(Task::class)->find($id); $task = $this->taskRepository->findById($id);
if (null === $task) { if (null === $task) {
throw new NotFoundHttpException('Task not found'); throw new NotFoundHttpException('Task not found');
} }
@@ -2,13 +2,17 @@
declare(strict_types=1); 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\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
class MailConfigurationRepository extends ServiceEntityRepository /**
* @extends ServiceEntityRepository<MailConfiguration>
*/
class DoctrineMailConfigurationRepository extends ServiceEntityRepository implements MailConfigurationRepositoryInterface
{ {
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
@@ -2,13 +2,17 @@
declare(strict_types=1); 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\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
class MailFolderRepository extends ServiceEntityRepository /**
* @extends ServiceEntityRepository<MailFolder>
*/
class DoctrineMailFolderRepository extends ServiceEntityRepository implements MailFolderRepositoryInterface
{ {
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
@@ -2,22 +2,31 @@
declare(strict_types=1); 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\Entity\MailMessage; use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
class MailMessageRepository extends ServiceEntityRepository /**
* @extends ServiceEntityRepository<MailMessage>
*/
class DoctrineMailMessageRepository extends ServiceEntityRepository implements MailMessageRepositoryInterface
{ {
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
parent::__construct($registry, MailMessage::class); parent::__construct($registry, MailMessage::class);
} }
public function findById(int $id): ?MailMessage
{
return $this->find($id);
}
public function findByMessageId(string $messageId): ?MailMessage public function findByMessageId(string $messageId): ?MailMessage
{ {
return $this->findOneBy(['messageId' => $messageId]); return $this->findOneBy(['messageId' => $messageId]);
@@ -2,15 +2,19 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Repository; namespace App\Module\Mail\Infrastructure\Doctrine;
use App\Entity\MailMessage; use App\Module\Mail\Domain\Entity\MailMessage;
use App\Entity\TaskMailLink; use App\Module\Mail\Domain\Entity\TaskMailLink;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\Mail\Domain\Repository\TaskMailLinkRepositoryInterface;
use App\Shared\Domain\Contract\TaskInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
class TaskMailLinkRepository extends ServiceEntityRepository /**
* @extends ServiceEntityRepository<TaskMailLink>
*/
class DoctrineTaskMailLinkRepository extends ServiceEntityRepository implements TaskMailLinkRepositoryInterface
{ {
public function __construct(ManagerRegistry $registry) public function __construct(ManagerRegistry $registry)
{ {
@@ -20,7 +24,7 @@ class TaskMailLinkRepository extends ServiceEntityRepository
/** /**
* @return list<TaskMailLink> * @return list<TaskMailLink>
*/ */
public function findByTask(Task $task): array public function findByTask(TaskInterface $task): array
{ {
return $this->createQueryBuilder('l') return $this->createQueryBuilder('l')
->andWhere('l.task = :task') ->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]); return $this->findOneBy(['task' => $task, 'mailMessage' => $message]);
} }
@@ -2,14 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail; namespace App\Module\Mail\Infrastructure\Imap;
use App\Mail\Dto\MailAttachmentDto; use App\Module\Mail\Application\Dto\MailAttachmentDto;
use App\Mail\Dto\MailFolderDto; use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Mail\Dto\MailMessageDetailDto; use App\Module\Mail\Application\Dto\MailMessageDetailDto;
use App\Mail\Dto\MailMessageHeaderDto; use App\Module\Mail\Application\Dto\MailMessageHeaderDto;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Service\TokenEncryptor; use App\Service\TokenEncryptor;
use DateTimeImmutable; use DateTimeImmutable;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@@ -24,7 +25,7 @@ final class ImapMailProvider implements MailProviderInterface
private ?Client $client = null; private ?Client $client = null;
public function __construct( public function __construct(
private readonly MailConfigurationRepository $configRepository, private readonly MailConfigurationRepositoryInterface $configRepository,
private readonly TokenEncryptor $tokenEncryptor, private readonly TokenEncryptor $tokenEncryptor,
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
) {} ) {}
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Mail; namespace App\Module\Mail\Infrastructure\Imap;
use const ICONV_MIME_DECODE_CONTINUE_ON_ERROR; use const ICONV_MIME_DECODE_CONTINUE_ON_ERROR;
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Security; namespace App\Module\Mail\Infrastructure\Security;
use App\Shared\Domain\Contract\UserInterface as SharedUserInterface; use App\Shared\Domain\Contract\UserInterface as SharedUserInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; 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'],
];
}
}
@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace App\Tests\Functional\Controller\Mail; namespace App\Tests\Functional\Controller\Mail;
use App\Entity\MailFolder;
use App\Entity\MailMessage;
use App\Module\Core\Domain\Entity\User; use App\Module\Core\Domain\Entity\User;
use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\ProjectManagement\Domain\Entity\Project; use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task; use App\Module\ProjectManagement\Domain\Entity\Task;
use DateTimeImmutable; use DateTimeImmutable;
+6 -6
View File
@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace App\Tests\Unit\Mail; namespace App\Tests\Unit\Mail;
use App\Entity\MailConfiguration; use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Mail\Exception\MailProviderException; use App\Module\Mail\Domain\Exception\MailProviderException;
use App\Mail\ImapMailProvider; use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Infrastructure\Imap\ImapMailProvider;
use App\Service\TokenEncryptor; use App\Service\TokenEncryptor;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
@@ -22,7 +22,7 @@ class ImapMailProviderTest extends TestCase
$config = new MailConfiguration(); $config = new MailConfiguration();
$config->setEnabled(false); $config->setEnabled(false);
$repo = $this->createMock(MailConfigurationRepository::class); $repo = $this->createMock(MailConfigurationRepositoryInterface::class);
$repo->method('findSingleton')->willReturn($config); $repo->method('findSingleton')->willReturn($config);
$provider = new ImapMailProvider($repo, $this->makeEncryptor(), new NullLogger()); $provider = new ImapMailProvider($repo, $this->makeEncryptor(), new NullLogger());
@@ -33,7 +33,7 @@ class ImapMailProviderTest extends TestCase
public function testThrowsWhenConfigMissing(): void public function testThrowsWhenConfigMissing(): void
{ {
$repo = $this->createMock(MailConfigurationRepository::class); $repo = $this->createMock(MailConfigurationRepositoryInterface::class);
$repo->method('findSingleton')->willReturn(null); $repo->method('findSingleton')->willReturn(null);
$provider = new ImapMailProvider($repo, $this->makeEncryptor(), new NullLogger()); $provider = new ImapMailProvider($repo, $this->makeEncryptor(), new NullLogger());
+1 -1
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Tests\Unit\Mail; namespace App\Tests\Unit\Mail;
use App\Mail\Dto\MailSyncReport; use App\Module\Mail\Application\Dto\MailSyncReport;
use DateTimeImmutable; use DateTimeImmutable;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
+1 -1
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Tests\Unit\Mail; namespace App\Tests\Unit\Mail;
use App\Mail\MimeHeaderDecoder; use App\Module\Mail\Infrastructure\Imap\MimeHeaderDecoder;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Tests\Unit\Repository; namespace App\Tests\Unit\Repository;
use App\Entity\MailConfiguration; use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Infrastructure\Doctrine\DoctrineMailConfigurationRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
@@ -14,14 +14,14 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
*/ */
class MailConfigurationRepositoryTest extends KernelTestCase class MailConfigurationRepositoryTest extends KernelTestCase
{ {
private MailConfigurationRepository $repository; private DoctrineMailConfigurationRepository $repository;
private EntityManagerInterface $em; private EntityManagerInterface $em;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$container = static::getContainer(); $container = static::getContainer();
$this->repository = $container->get(MailConfigurationRepository::class); $this->repository = $container->get(DoctrineMailConfigurationRepository::class);
$this->em = $container->get('doctrine.orm.entity_manager'); $this->em = $container->get('doctrine.orm.entity_manager');
$this->em->getConnection()->executeStatement('TRUNCATE TABLE mail_configuration RESTART IDENTITY CASCADE'); $this->em->getConnection()->executeStatement('TRUNCATE TABLE mail_configuration RESTART IDENTITY CASCADE');
} }
+20 -20
View File
@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace App\Tests\Unit\Service; namespace App\Tests\Unit\Service;
use App\Entity\MailConfiguration; use App\Module\Mail\Application\Dto\MailFolderDto;
use App\Entity\MailFolder; use App\Module\Mail\Application\Service\MailSyncService;
use App\Mail\Dto\MailFolderDto; use App\Module\Mail\Domain\Entity\MailConfiguration;
use App\Mail\MailProviderInterface; use App\Module\Mail\Domain\Entity\MailFolder;
use App\Repository\MailConfigurationRepository; use App\Module\Mail\Domain\Provider\MailProviderInterface;
use App\Repository\MailFolderRepository; use App\Module\Mail\Domain\Repository\MailConfigurationRepositoryInterface;
use App\Repository\MailMessageRepository; use App\Module\Mail\Domain\Repository\MailFolderRepositoryInterface;
use App\Service\MailSyncService; use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -29,12 +29,12 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration(); $config = new MailConfiguration();
$config->setEnabled(false); $config->setEnabled(false);
$configRepo = $this->createMock(MailConfigurationRepository::class); $configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config); $configRepo->method('findSingleton')->willReturn($config);
$provider = $this->createMock(MailProviderInterface::class); $provider = $this->createMock(MailProviderInterface::class);
$folderRepo = $this->createMock(MailFolderRepository::class); $folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$messageRepo = $this->createMock(MailMessageRepository::class); $messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class); $em = $this->createMock(EntityManagerInterface::class);
$lockFactory = $this->makeLockFactory(); $lockFactory = $this->makeLockFactory();
@@ -62,12 +62,12 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration(); $config = new MailConfiguration();
$config->setEnabled(true); $config->setEnabled(true);
$configRepo = $this->createMock(MailConfigurationRepository::class); $configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config); $configRepo->method('findSingleton')->willReturn($config);
$provider = $this->createMock(MailProviderInterface::class); $provider = $this->createMock(MailProviderInterface::class);
$folderRepo = $this->createMock(MailFolderRepository::class); $folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$messageRepo = $this->createMock(MailMessageRepository::class); $messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class); $em = $this->createMock(EntityManagerInterface::class);
$lockFactory = $this->makeLockFactory(false); $lockFactory = $this->makeLockFactory(false);
@@ -93,7 +93,7 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration(); $config = new MailConfiguration();
$config->setEnabled(true); $config->setEnabled(true);
$configRepo = $this->createMock(MailConfigurationRepository::class); $configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config); $configRepo->method('findSingleton')->willReturn($config);
$folderDto = new MailFolderDto( $folderDto = new MailFolderDto(
@@ -107,11 +107,11 @@ class MailSyncServiceTest extends TestCase
$provider = $this->createMock(MailProviderInterface::class); $provider = $this->createMock(MailProviderInterface::class);
$provider->method('listFolders')->willReturn([$folderDto]); $provider->method('listFolders')->willReturn([$folderDto]);
$folderRepo = $this->createMock(MailFolderRepository::class); $folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$folderRepo->method('findByPath')->willReturn(null); $folderRepo->method('findByPath')->willReturn(null);
$folderRepo->method('findAllOrderedByPath')->willReturn([]); $folderRepo->method('findAllOrderedByPath')->willReturn([]);
$messageRepo = $this->createMock(MailMessageRepository::class); $messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class); $em = $this->createMock(EntityManagerInterface::class);
$em->expects(self::once())->method('persist'); $em->expects(self::once())->method('persist');
$em->expects(self::once())->method('flush'); $em->expects(self::once())->method('flush');
@@ -137,13 +137,13 @@ class MailSyncServiceTest extends TestCase
$config = new MailConfiguration(); $config = new MailConfiguration();
$config->setEnabled(true); $config->setEnabled(true);
$configRepo = $this->createMock(MailConfigurationRepository::class); $configRepo = $this->createMock(MailConfigurationRepositoryInterface::class);
$configRepo->method('findSingleton')->willReturn($config); $configRepo->method('findSingleton')->willReturn($config);
$folder = new MailFolder(); $folder = new MailFolder();
$folder->setPath('INBOX'); $folder->setPath('INBOX');
$messageRepo = $this->createMock(MailMessageRepository::class); $messageRepo = $this->createMock(MailMessageRepositoryInterface::class);
$messageRepo->method('findMaxUidInFolder')->willReturn(10); $messageRepo->method('findMaxUidInFolder')->willReturn(10);
$messageRepo->method('findAllUidsByFolder')->willReturn([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); $messageRepo->method('findAllUidsByFolder')->willReturn([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$messageRepo->method('findLastNByFolder')->willReturn([]); $messageRepo->method('findLastNByFolder')->willReturn([]);
@@ -151,7 +151,7 @@ class MailSyncServiceTest extends TestCase
$provider = $this->createMock(MailProviderInterface::class); $provider = $this->createMock(MailProviderInterface::class);
$provider->method('listMessages')->willReturn([]); $provider->method('listMessages')->willReturn([]);
$folderRepo = $this->createMock(MailFolderRepository::class); $folderRepo = $this->createMock(MailFolderRepositoryInterface::class);
$em = $this->createMock(EntityManagerInterface::class); $em = $this->createMock(EntityManagerInterface::class);
$em->expects(self::never())->method('remove'); $em->expects(self::never())->method('remove');