Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions backend/migrations/Version20260505100305.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260505100305 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add ip_address_id to sends table';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE sends ADD ip_address_id INT DEFAULT NULL REFERENCES ip_addresses(id) ON DELETE SET NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE sends DROP ip_address_id');
}
}
2 changes: 2 additions & 0 deletions backend/src/Api/Console/Object/SendObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class SendObject
public int $size_bytes;
public bool $queued;
public int $send_after;
public ?string $ip_address;

/**
* @var SendRecipientObject[]
Expand Down Expand Up @@ -61,6 +62,7 @@ public function __construct(
$this->size_bytes = $send->getSizeBytes();
$this->queued = $send->getQueued();
$this->send_after = $send->getSendAfter()->getTimestamp();
$this->ip_address = $send->getIpAddress()?->getIpAddress();

$this->recipients = array_map(fn($recipient) => new SendRecipientObject($recipient),
$send->getRecipients()->toArray());
Expand Down
15 changes: 15 additions & 0 deletions backend/src/Entity/Send.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class Send
#[ORM\JoinColumn]
private Queue $queue;

#[ORM\ManyToOne(targetEntity: IpAddress::class)]
#[ORM\JoinColumn(onDelete: "SET NULL")]
private ?IpAddress $ip_address = null;

#[ORM\Column()]
private string $queue_name; // denormalized for easier access

Expand Down Expand Up @@ -187,6 +191,17 @@ public function setQueue(Queue $queue): static
return $this;
}

public function getIpAddress(): ?IpAddress
{
return $this->ip_address;
}

public function setIpAddress(?IpAddress $ipAddress): static
{
$this->ip_address = $ipAddress;
return $this;
}

public function getQueueName(): string
{
return $this->queue_name;
Expand Down
17 changes: 17 additions & 0 deletions backend/src/Repository/IpAddressRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

use App\Entity\Queue;

/**
* @extends ServiceEntityRepository<IpAddress>
*/
Expand All @@ -15,4 +17,19 @@ public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, IpAddress::class);
}

public function getRandomIpForQueue(Queue $queue): ?IpAddress
{
$conn = $this->getEntityManager()->getConnection();
$sql = 'SELECT id FROM ip_addresses WHERE queue_id = :queue_id ORDER BY RANDOM() LIMIT 1';
$stmt = $conn->prepare($sql);
$result = $stmt->executeQuery(['queue_id' => $queue->getId()]);
$id = $result->fetchOne();

if ($id) {
return $this->find($id);
}

return null;
}
}
11 changes: 11 additions & 0 deletions backend/src/Repository/SendRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,15 @@ public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Send::class);
}

public function updateNullIpSendsForQueue(int $queueId, ?int $ipAddressId): int
{
$conn = $this->getEntityManager()->getConnection();
$sql = 'UPDATE sends SET ip_address_id = :ip_id, updated_at = NOW() WHERE queue_id = :queue_id AND ip_address_id IS NULL';
$stmt = $conn->prepare($sql);
return $stmt->executeStatement([
'ip_id' => $ipAddressId,
'queue_id' => $queueId,
]);
}
}
20 changes: 20 additions & 0 deletions backend/src/Service/Ip/Event/IpRemovedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Service\Ip\Event;

use App\Entity\IpAddress;

readonly class IpRemovedEvent
{

public function __construct(
private IpAddress $ipAddress,
) {
}

public function getIpAddress(): IpAddress
{
return $this->ipAddress;
}

}
40 changes: 40 additions & 0 deletions backend/src/Service/Ip/IpAddressQueueAssignedListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace App\Service\Ip;

use App\Service\Ip\Event\IpAddressUpdatedEvent;
use App\Service\Send\Message\RouteQueueNullIpsToIpMessage;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Messenger\MessageBusInterface;

#[AsEventListener(IpAddressUpdatedEvent::class, 'onIpAddressUpdated')]
class IpAddressQueueAssignedListener
{

public function __construct(
private MessageBusInterface $bus,
) {
}

public function onIpAddressUpdated(IpAddressUpdatedEvent $event): void
{
$updates = $event->getUpdates();

if (!$updates->queueSet) {
return;
}

$ipAddress = $event->getIpAddress();
$queue = $ipAddress->getQueue();

if ($queue === null) {
return;
}

$this->bus->dispatch(new RouteQueueNullIpsToIpMessage(
$queue->getId(),
$ipAddress->getId()
));
}

}
3 changes: 3 additions & 0 deletions backend/src/Service/Ip/IpAddressService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Service\Ip\Dto\PtrValidationDto;
use App\Service\Ip\Dto\UpdateIpAddressDto;
use App\Service\Ip\Event\IpAddressUpdatedEvent;
use App\Service\Ip\Event\IpRemovedEvent;
use App\Service\Queue\QueueService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Clock\ClockAwareTrait;
Expand Down Expand Up @@ -103,6 +104,8 @@ public function createIpAddress(Server $server, string $ipAddress): IpAddress

public function deleteIpAddress(IpAddress $ipAddress): void
{
$this->ed->dispatch(new IpRemovedEvent($ipAddress));

$this->em->remove($ipAddress);
$this->em->flush();
}
Expand Down
30 changes: 30 additions & 0 deletions backend/src/Service/Ip/IpRemovedListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Service\Ip;

use App\Service\Ip\Event\IpRemovedEvent;
use App\Service\Send\Message\RouteNullIpsMessage;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Messenger\MessageBusInterface;

#[AsEventListener(IpRemovedEvent::class, 'onIpRemoved')]
class IpRemovedListener
{

public function __construct(
private MessageBusInterface $bus,
) {
}

public function onIpRemoved(IpRemovedEvent $event): void
{
$queue = $event->getIpAddress()->getQueue();

if ($queue === null) {
return;
}

$this->bus->dispatch(new RouteNullIpsMessage($queue->getId()));
}

}
16 changes: 16 additions & 0 deletions backend/src/Service/Send/Message/RouteNullIpsMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\Service\Send\Message;

use Symfony\Component\Messenger\Attribute\AsMessage;

#[AsMessage('async')]
readonly class RouteNullIpsMessage
{

public function __construct(
public int $queueId,
) {
}

}
17 changes: 17 additions & 0 deletions backend/src/Service/Send/Message/RouteQueueNullIpsToIpMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Service\Send\Message;

use Symfony\Component\Messenger\Attribute\AsMessage;

#[AsMessage('async')]
readonly class RouteQueueNullIpsToIpMessage
{

public function __construct(
public int $queueId,
public int $ipAddressId,
) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\Service\Send\MessageHandler;

use App\Repository\IpAddressRepository;
use App\Repository\SendRepository;
use App\Service\Queue\QueueService;
use App\Service\Send\Message\RouteNullIpsMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
class RouteNullIpsMessageHandler
{

public function __construct(
private SendRepository $sendRepository,
private IpAddressRepository $ipAddressRepository,
private QueueService $queueService,
) {
}

public function __invoke(RouteNullIpsMessage $message): void
{
$queue = $this->queueService->getQueueById($message->queueId);

if ($queue === null) {
return;
}

$newIp = $this->ipAddressRepository->getRandomIpForQueue($queue);

$this->sendRepository->updateNullIpSendsForQueue(
$queue->getId(),
$newIp?->getId()
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace App\Service\Send\MessageHandler;

use App\Repository\IpAddressRepository;
use App\Repository\SendRepository;
use App\Service\Send\Message\RouteQueueNullIpsToIpMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
class RouteQueueNullIpsToIpMessageHandler
{

public function __construct(
private SendRepository $sendRepository,
private IpAddressRepository $ipAddressRepository,
) {
}

public function __invoke(RouteQueueNullIpsToIpMessage $message): void
{
$ipAddress = $this->ipAddressRepository->find($message->ipAddressId);

if ($ipAddress === null) {
return;
}

$queue = $ipAddress->getQueue();

if ($queue === null || $queue->getId() !== $message->queueId) {
return;
}

$this->sendRepository->updateNullIpSendsForQueue(
$message->queueId,
$message->ipAddressId
);
}

}
3 changes: 3 additions & 0 deletions backend/src/Service/Send/SendService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Entity\SendRecipient;
use App\Entity\Type\SendRecipientStatus;
use App\Entity\Type\SendRecipientType;
use App\Repository\IpAddressRepository;
use App\Repository\SendRepository;
use App\Service\Send\Dto\SendingAttachment;
use App\Service\Send\Exception\EmailTooLargeException;
Expand All @@ -28,6 +29,7 @@ public function __construct(
private EmailBuilder $emailBuilder,
private SendRepository $sendRepository,
private RecipientFactory $recipientFactory,
private IpAddressRepository $ipAddressRepository,
) {
}

Expand Down Expand Up @@ -150,6 +152,7 @@ public function createSend(
$send->setDomain($domain);
$send->setQueue($queue);
$send->setQueueName($queue->getName());
$send->setIpAddress($this->ipAddressRepository->getRandomIpForQueue($queue));
$send->setFromAddress($from->getAddress());
$send->setFromName($from->getName());
$send->setSubject($subject);
Expand Down
1 change: 1 addition & 0 deletions backend/tests/Api/Console/Send/GetSendByUuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function test_get_specific_email(): void

$this->assertArrayHasKey('id', $json);
$this->assertSame($send->getId(), $json['id']);
$this->assertArrayHasKey('ip_address', $json);

$attempts = $json['attempts'];
$this->assertIsArray($attempts);
Expand Down
Loading
Loading