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:
@@ -1,193 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\MailConfigurationRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: MailConfigurationRepository::class)]
|
||||
#[ORM\Table(name: 'mail_configuration')]
|
||||
class MailConfiguration
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 10)]
|
||||
private string $protocol = 'imap';
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $imapHost = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private int $imapPort = 993;
|
||||
|
||||
#[ORM\Column(length: 10)]
|
||||
private string $imapEncryption = 'ssl';
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $smtpHost = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private int $smtpPort = 465;
|
||||
|
||||
#[ORM\Column(length: 10)]
|
||||
private string $smtpEncryption = 'ssl';
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $username = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $encryptedPassword = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private string $sentFolderPath = 'Sent';
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $enabled = false;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getProtocol(): string
|
||||
{
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
public function setProtocol(string $protocol): static
|
||||
{
|
||||
$this->protocol = $protocol;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImapHost(): ?string
|
||||
{
|
||||
return $this->imapHost;
|
||||
}
|
||||
|
||||
public function setImapHost(?string $imapHost): static
|
||||
{
|
||||
$this->imapHost = $imapHost;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImapPort(): int
|
||||
{
|
||||
return $this->imapPort;
|
||||
}
|
||||
|
||||
public function setImapPort(int $imapPort): static
|
||||
{
|
||||
$this->imapPort = $imapPort;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImapEncryption(): string
|
||||
{
|
||||
return $this->imapEncryption;
|
||||
}
|
||||
|
||||
public function setImapEncryption(string $imapEncryption): static
|
||||
{
|
||||
$this->imapEncryption = $imapEncryption;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSmtpHost(): ?string
|
||||
{
|
||||
return $this->smtpHost;
|
||||
}
|
||||
|
||||
public function setSmtpHost(?string $smtpHost): static
|
||||
{
|
||||
$this->smtpHost = $smtpHost;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSmtpPort(): int
|
||||
{
|
||||
return $this->smtpPort;
|
||||
}
|
||||
|
||||
public function setSmtpPort(int $smtpPort): static
|
||||
{
|
||||
$this->smtpPort = $smtpPort;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSmtpEncryption(): string
|
||||
{
|
||||
return $this->smtpEncryption;
|
||||
}
|
||||
|
||||
public function setSmtpEncryption(string $smtpEncryption): static
|
||||
{
|
||||
$this->smtpEncryption = $smtpEncryption;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function setUsername(?string $username): static
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEncryptedPassword(): ?string
|
||||
{
|
||||
return $this->encryptedPassword;
|
||||
}
|
||||
|
||||
public function setEncryptedPassword(?string $encryptedPassword): static
|
||||
{
|
||||
$this->encryptedPassword = $encryptedPassword;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSentFolderPath(): string
|
||||
{
|
||||
return $this->sentFolderPath;
|
||||
}
|
||||
|
||||
public function setSentFolderPath(string $sentFolderPath): static
|
||||
{
|
||||
$this->sentFolderPath = $sentFolderPath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function setEnabled(bool $enabled): static
|
||||
{
|
||||
$this->enabled = $enabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasPassword(): bool
|
||||
{
|
||||
return null !== $this->encryptedPassword;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\MailFolderRepository;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: MailFolderRepository::class)]
|
||||
#[ORM\Table(name: 'mail_folder')]
|
||||
#[ORM\Index(columns: ['parent_path'], name: 'idx_mail_folder_parent_path')]
|
||||
class MailFolder
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 500, unique: true)]
|
||||
private string $path;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private string $displayName;
|
||||
|
||||
#[ORM\Column(length: 500, nullable: true)]
|
||||
private ?string $parentPath = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private int $unreadCount = 0;
|
||||
|
||||
#[ORM\Column]
|
||||
private int $totalCount = 0;
|
||||
|
||||
#[ORM\Column(type: 'datetimetz_immutable', nullable: true)]
|
||||
private ?DateTimeImmutable $lastSyncedAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function setPath(string $path): static
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDisplayName(): string
|
||||
{
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function setDisplayName(string $displayName): static
|
||||
{
|
||||
$this->displayName = $displayName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParentPath(): ?string
|
||||
{
|
||||
return $this->parentPath;
|
||||
}
|
||||
|
||||
public function setParentPath(?string $parentPath): static
|
||||
{
|
||||
$this->parentPath = $parentPath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUnreadCount(): int
|
||||
{
|
||||
return $this->unreadCount;
|
||||
}
|
||||
|
||||
public function setUnreadCount(int $unreadCount): static
|
||||
{
|
||||
$this->unreadCount = $unreadCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTotalCount(): int
|
||||
{
|
||||
return $this->totalCount;
|
||||
}
|
||||
|
||||
public function setTotalCount(int $totalCount): static
|
||||
{
|
||||
$this->totalCount = $totalCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastSyncedAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->lastSyncedAt;
|
||||
}
|
||||
|
||||
public function setLastSyncedAt(?DateTimeImmutable $lastSyncedAt): static
|
||||
{
|
||||
$this->lastSyncedAt = $lastSyncedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\MailMessageRepository;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: MailMessageRepository::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')]
|
||||
#[ORM\Index(columns: ['is_read'], name: 'idx_mail_message_is_read')]
|
||||
#[ORM\Index(columns: ['message_id'], name: 'idx_mail_message_message_id')]
|
||||
class MailMessage
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 500)]
|
||||
private string $messageId;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: MailFolder::class)]
|
||||
#[ORM\JoinColumn(name: 'folder_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private MailFolder $folder;
|
||||
|
||||
#[ORM\Column]
|
||||
private int $uid;
|
||||
|
||||
#[ORM\Column(length: 500, nullable: true)]
|
||||
private ?string $subject = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private string $fromAddress;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $fromName = null;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $toAddresses = [];
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $ccAddresses = null;
|
||||
|
||||
#[ORM\Column(type: 'datetimetz_immutable')]
|
||||
private DateTimeImmutable $sentAt;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $isRead = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $isFlagged = false;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $hasAttachments = false;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $snippet = null;
|
||||
|
||||
#[ORM\Column(type: 'datetimetz_immutable')]
|
||||
private DateTimeImmutable $syncedAt;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getMessageId(): string
|
||||
{
|
||||
return $this->messageId;
|
||||
}
|
||||
|
||||
public function setMessageId(string $messageId): static
|
||||
{
|
||||
$this->messageId = $messageId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFolder(): MailFolder
|
||||
{
|
||||
return $this->folder;
|
||||
}
|
||||
|
||||
public function setFolder(MailFolder $folder): static
|
||||
{
|
||||
$this->folder = $folder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUid(): int
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function setUid(int $uid): static
|
||||
{
|
||||
$this->uid = $uid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSubject(): ?string
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
public function setSubject(?string $subject): static
|
||||
{
|
||||
$this->subject = $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFromAddress(): string
|
||||
{
|
||||
return $this->fromAddress;
|
||||
}
|
||||
|
||||
public function setFromAddress(string $fromAddress): static
|
||||
{
|
||||
$this->fromAddress = $fromAddress;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFromName(): ?string
|
||||
{
|
||||
return $this->fromName;
|
||||
}
|
||||
|
||||
public function setFromName(?string $fromName): static
|
||||
{
|
||||
$this->fromName = $fromName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getToAddresses(): array
|
||||
{
|
||||
return $this->toAddresses;
|
||||
}
|
||||
|
||||
public function setToAddresses(array $toAddresses): static
|
||||
{
|
||||
$this->toAddresses = $toAddresses;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCcAddresses(): ?array
|
||||
{
|
||||
return $this->ccAddresses;
|
||||
}
|
||||
|
||||
public function setCcAddresses(?array $ccAddresses): static
|
||||
{
|
||||
$this->ccAddresses = $ccAddresses;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSentAt(): DateTimeImmutable
|
||||
{
|
||||
return $this->sentAt;
|
||||
}
|
||||
|
||||
public function setSentAt(DateTimeImmutable $sentAt): static
|
||||
{
|
||||
$this->sentAt = $sentAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isRead(): bool
|
||||
{
|
||||
return $this->isRead;
|
||||
}
|
||||
|
||||
public function setIsRead(bool $isRead): static
|
||||
{
|
||||
$this->isRead = $isRead;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isFlagged(): bool
|
||||
{
|
||||
return $this->isFlagged;
|
||||
}
|
||||
|
||||
public function setIsFlagged(bool $isFlagged): static
|
||||
{
|
||||
$this->isFlagged = $isFlagged;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasAttachments(): bool
|
||||
{
|
||||
return $this->hasAttachments;
|
||||
}
|
||||
|
||||
public function setHasAttachments(bool $hasAttachments): static
|
||||
{
|
||||
$this->hasAttachments = $hasAttachments;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSnippet(): ?string
|
||||
{
|
||||
return $this->snippet;
|
||||
}
|
||||
|
||||
public function setSnippet(?string $snippet): static
|
||||
{
|
||||
$this->snippet = $snippet;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSyncedAt(): DateTimeImmutable
|
||||
{
|
||||
return $this->syncedAt;
|
||||
}
|
||||
|
||||
public function setSyncedAt(DateTimeImmutable $syncedAt): static
|
||||
{
|
||||
$this->syncedAt = $syncedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Module\ProjectManagement\Domain\Entity\Task;
|
||||
use App\Repository\TaskMailLinkRepository;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: TaskMailLinkRepository::class)]
|
||||
#[ORM\Table(name: 'task_mail_link')]
|
||||
#[ORM\UniqueConstraint(name: 'uq_task_mail_link', columns: ['task_id', 'mail_message_id'])]
|
||||
class TaskMailLink
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Task::class)]
|
||||
#[ORM\JoinColumn(name: 'task_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private Task $task;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: MailMessage::class)]
|
||||
#[ORM\JoinColumn(name: 'mail_message_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private MailMessage $mailMessage;
|
||||
|
||||
#[ORM\Column(type: 'datetimetz_immutable')]
|
||||
private DateTimeImmutable $linkedAt;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: UserInterface::class)]
|
||||
#[ORM\JoinColumn(name: 'linked_by_id', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
||||
private ?UserInterface $linkedBy = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTask(): Task
|
||||
{
|
||||
return $this->task;
|
||||
}
|
||||
|
||||
public function setTask(Task $task): static
|
||||
{
|
||||
$this->task = $task;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMailMessage(): MailMessage
|
||||
{
|
||||
return $this->mailMessage;
|
||||
}
|
||||
|
||||
public function setMailMessage(MailMessage $mailMessage): static
|
||||
{
|
||||
$this->mailMessage = $mailMessage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLinkedAt(): DateTimeImmutable
|
||||
{
|
||||
return $this->linkedAt;
|
||||
}
|
||||
|
||||
public function setLinkedAt(DateTimeImmutable $linkedAt): static
|
||||
{
|
||||
$this->linkedAt = $linkedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLinkedBy(): ?UserInterface
|
||||
{
|
||||
return $this->linkedBy;
|
||||
}
|
||||
|
||||
public function setLinkedBy(?UserInterface $linkedBy): static
|
||||
{
|
||||
$this->linkedBy = $linkedBy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user