feat: MailJet list handling with DOI, webhook receiver and API endpoint
addresses #869cut134
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\NewsletterConsentRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: NewsletterConsentRepository::class)]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_NEWSLETTER_CONSENT_EMAIL_LIST', columns: ['email', 'mailjet_list_id'])]
|
||||
class NewsletterConsent
|
||||
{
|
||||
private const int NAME_MAX_LENGTH = 255;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255)]
|
||||
private string $email;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $mailjetListId;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||
private ?\DateTimeImmutable $confirmedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||
private ?\DateTimeImmutable $revokedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||
private ?string $firstName = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||
private ?string $lastName = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private \DateTimeImmutable $updatedAt;
|
||||
|
||||
public function __construct(string $email, int $mailjetListId, ?string $firstName = null, ?string $lastName = null)
|
||||
{
|
||||
if ($mailjetListId <= 0) {
|
||||
throw new \InvalidArgumentException('Mailjet list ID must be a positive integer.');
|
||||
}
|
||||
|
||||
$this->email = mb_strtolower(trim($email));
|
||||
$this->mailjetListId = $mailjetListId;
|
||||
$this->setNames($firstName, $lastName);
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->updatedAt = $this->createdAt;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getMailjetListId(): int
|
||||
{
|
||||
return $this->mailjetListId;
|
||||
}
|
||||
|
||||
public function getConfirmedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->confirmedAt;
|
||||
}
|
||||
|
||||
public function getRevokedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->revokedAt;
|
||||
}
|
||||
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function isConfirmed(): bool
|
||||
{
|
||||
return null !== $this->confirmedAt && null === $this->revokedAt;
|
||||
}
|
||||
|
||||
public function isRevoked(): bool
|
||||
{
|
||||
return null !== $this->revokedAt;
|
||||
}
|
||||
|
||||
public function markConfirmed(?\DateTimeImmutable $now = null, ?string $firstName = null, ?string $lastName = null): void
|
||||
{
|
||||
$timestamp = $now ?? new \DateTimeImmutable();
|
||||
$this->confirmedAt = $timestamp;
|
||||
$this->revokedAt = null;
|
||||
$this->mergeNames($firstName, $lastName);
|
||||
$this->updatedAt = $timestamp;
|
||||
}
|
||||
|
||||
public function markRevoked(?\DateTimeImmutable $now = null): void
|
||||
{
|
||||
$timestamp = $now ?? new \DateTimeImmutable();
|
||||
$this->revokedAt = $timestamp;
|
||||
$this->updatedAt = $timestamp;
|
||||
}
|
||||
|
||||
public function setNames(?string $firstName, ?string $lastName): void
|
||||
{
|
||||
$this->firstName = self::normalizeName($firstName);
|
||||
$this->lastName = self::normalizeName($lastName);
|
||||
}
|
||||
|
||||
public function mergeNames(?string $firstName, ?string $lastName): void
|
||||
{
|
||||
if (null !== $firstName) {
|
||||
$this->firstName = self::normalizeName($firstName);
|
||||
}
|
||||
|
||||
if (null !== $lastName) {
|
||||
$this->lastName = self::normalizeName($lastName);
|
||||
}
|
||||
}
|
||||
|
||||
private static function normalizeName(?string $value): ?string
|
||||
{
|
||||
if (null === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trimmed = trim($value);
|
||||
if ('' === $trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mb_substr($trimmed, 0, self::NAME_MAX_LENGTH);
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\NewsletterOptInConfirmationRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: NewsletterOptInConfirmationRepository::class)]
|
||||
class NewsletterOptInConfirmation
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255)]
|
||||
private string $email;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 64, unique: true)]
|
||||
private string $tokenHash;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private \DateTimeImmutable $expiresAt;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||
private ?\DateTimeImmutable $confirmedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
public function __construct(
|
||||
string $email,
|
||||
string $tokenHash,
|
||||
\DateTimeImmutable $expiresAt,
|
||||
) {
|
||||
$this->email = mb_strtolower(trim($email));
|
||||
$this->tokenHash = $tokenHash;
|
||||
$this->expiresAt = $expiresAt;
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getTokenHash(): string
|
||||
{
|
||||
return $this->tokenHash;
|
||||
}
|
||||
|
||||
public function getExpiresAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
public function getConfirmedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->confirmedAt;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function isConfirmed(): bool
|
||||
{
|
||||
return null !== $this->confirmedAt;
|
||||
}
|
||||
|
||||
public function isExpired(?\DateTimeImmutable $now = null): bool
|
||||
{
|
||||
$reference = $now ?? new \DateTimeImmutable();
|
||||
|
||||
return $this->expiresAt <= $reference;
|
||||
}
|
||||
|
||||
public function markConfirmed(?\DateTimeImmutable $now = null): void
|
||||
{
|
||||
$this->confirmedAt = $now ?? new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function refreshRequest(string $tokenHash, \DateTimeImmutable $expiresAt): void
|
||||
{
|
||||
$this->tokenHash = $tokenHash;
|
||||
$this->expiresAt = $expiresAt;
|
||||
$this->confirmedAt = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\NewsletterOptInRequestRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: NewsletterOptInRequestRepository::class)]
|
||||
class NewsletterOptInRequest
|
||||
{
|
||||
private const int NAME_MAX_LENGTH = 255;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255)]
|
||||
private string $email;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 64, unique: true)]
|
||||
private string $tokenHash;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private \DateTimeImmutable $expiresAt;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||
private ?\DateTimeImmutable $confirmedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
||||
private ?\DateTimeImmutable $revokedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||
private ?string $firstName = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
||||
private ?string $lastName = null;
|
||||
|
||||
/**
|
||||
* @var list<int>|null
|
||||
*/
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $mailjetListIds = null;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
/**
|
||||
* @param list<int|string> $mailjetListIds
|
||||
*/
|
||||
public function __construct(
|
||||
string $email,
|
||||
string $tokenHash,
|
||||
\DateTimeImmutable $expiresAt,
|
||||
array $mailjetListIds = [],
|
||||
?string $firstName = null,
|
||||
?string $lastName = null,
|
||||
) {
|
||||
$this->email = mb_strtolower(trim($email));
|
||||
$this->tokenHash = $tokenHash;
|
||||
$this->expiresAt = $expiresAt;
|
||||
$this->setMailjetListIds($mailjetListIds);
|
||||
$this->setNames($firstName, $lastName);
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getTokenHash(): string
|
||||
{
|
||||
return $this->tokenHash;
|
||||
}
|
||||
|
||||
public function getExpiresAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
public function getConfirmedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->confirmedAt;
|
||||
}
|
||||
|
||||
public function getRevokedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->revokedAt;
|
||||
}
|
||||
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int>
|
||||
*/
|
||||
public function getMailjetListIds(): array
|
||||
{
|
||||
return $this->mailjetListIds ?? [];
|
||||
}
|
||||
|
||||
public function isConfirmed(): bool
|
||||
{
|
||||
return null !== $this->confirmedAt;
|
||||
}
|
||||
|
||||
public function isRevoked(): bool
|
||||
{
|
||||
return null !== $this->revokedAt;
|
||||
}
|
||||
|
||||
public function isExpired(?\DateTimeImmutable $now = null): bool
|
||||
{
|
||||
$reference = $now ?? new \DateTimeImmutable();
|
||||
|
||||
return $this->expiresAt <= $reference;
|
||||
}
|
||||
|
||||
public function markConfirmed(?\DateTimeImmutable $now = null): void
|
||||
{
|
||||
$this->confirmedAt = $now ?? new \DateTimeImmutable();
|
||||
$this->revokedAt = null;
|
||||
}
|
||||
|
||||
public function markRevoked(?\DateTimeImmutable $now = null): void
|
||||
{
|
||||
$this->revokedAt = $now ?? new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function clearRevokedAt(): void
|
||||
{
|
||||
$this->revokedAt = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int|string> $mailjetListIds
|
||||
*/
|
||||
public function refreshRequest(string $tokenHash, \DateTimeImmutable $expiresAt, array $mailjetListIds = [], ?string $firstName = null, ?string $lastName = null): void
|
||||
{
|
||||
$this->tokenHash = $tokenHash;
|
||||
$this->expiresAt = $expiresAt;
|
||||
$this->setMailjetListIds($mailjetListIds);
|
||||
$this->mergeNames($firstName, $lastName);
|
||||
$this->confirmedAt = null;
|
||||
$this->revokedAt = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int|string> $mailjetListIds
|
||||
*/
|
||||
public function setMailjetListIds(array $mailjetListIds): void
|
||||
{
|
||||
$normalizedListIds = self::normalizeMailjetListIds($mailjetListIds);
|
||||
$this->mailjetListIds = [] === $normalizedListIds ? null : $normalizedListIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int|string> $mailjetListIds
|
||||
*/
|
||||
public function mergeMailjetListIds(array $mailjetListIds): void
|
||||
{
|
||||
$this->setMailjetListIds(array_merge($this->getMailjetListIds(), $mailjetListIds));
|
||||
}
|
||||
|
||||
public function setNames(?string $firstName, ?string $lastName): void
|
||||
{
|
||||
$this->firstName = self::normalizeName($firstName);
|
||||
$this->lastName = self::normalizeName($lastName);
|
||||
}
|
||||
|
||||
public function mergeNames(?string $firstName, ?string $lastName): void
|
||||
{
|
||||
if (null !== $firstName) {
|
||||
$this->firstName = self::normalizeName($firstName);
|
||||
}
|
||||
|
||||
if (null !== $lastName) {
|
||||
$this->lastName = self::normalizeName($lastName);
|
||||
}
|
||||
}
|
||||
|
||||
private static function normalizeName(?string $value): ?string
|
||||
{
|
||||
if (null === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trimmed = trim($value);
|
||||
if ('' === $trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mb_substr($trimmed, 0, self::NAME_MAX_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<mixed> $mailjetListIds
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
private static function normalizeMailjetListIds(array $mailjetListIds): array
|
||||
{
|
||||
$normalizedListIds = [];
|
||||
|
||||
foreach ($mailjetListIds as $mailjetListId) {
|
||||
if (true === is_int($mailjetListId)) {
|
||||
$normalizedListId = $mailjetListId;
|
||||
} elseif (true === is_string($mailjetListId) && true === ctype_digit(trim($mailjetListId))) {
|
||||
$normalizedListId = (int) trim($mailjetListId);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Mailjet list IDs must be positive integers.');
|
||||
}
|
||||
|
||||
if ($normalizedListId <= 0) {
|
||||
throw new \InvalidArgumentException('Mailjet list IDs must be positive integers.');
|
||||
}
|
||||
|
||||
$normalizedListIds[] = $normalizedListId;
|
||||
}
|
||||
|
||||
$normalizedListIds = array_unique($normalizedListIds);
|
||||
sort($normalizedListIds, SORT_NUMERIC);
|
||||
|
||||
return $normalizedListIds;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user