48 lines
1.6 KiB
PHP
48 lines
1.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Box\Mod\Mailgateway;
|
|
|
|
use Doctrine\DBAL\Connection;
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
|
|
class NotificationCooldown
|
|
{
|
|
private int $seconds;
|
|
|
|
public function __construct(private Connection $connection, ?int $seconds = null)
|
|
{
|
|
$this->seconds = $seconds ?? RuntimeConfig::get('notification_cooldown_seconds');
|
|
}
|
|
|
|
/** Atomically reserve a sender's notification slot and queue the email. */
|
|
public function run(string $sender, callable $queue): bool
|
|
{
|
|
$key = hash('sha256', strtolower(trim($sender)));
|
|
$now = time();
|
|
|
|
return $this->connection->transactional(function () use ($key, $now, $queue): bool {
|
|
try {
|
|
$this->connection->insert('mailgateway_notification', ['sender_key' => $key, 'last_notified_at' => 0]);
|
|
} catch (UniqueConstraintViolationException) {
|
|
// Existing sender; the conditional update decides who owns the slot.
|
|
}
|
|
$claimed = $this->connection->executeStatement(
|
|
'UPDATE mailgateway_notification SET last_notified_at = ? WHERE sender_key = ? AND last_notified_at <= ?',
|
|
[$now, $key, $now - $this->seconds],
|
|
);
|
|
if ($claimed !== 1) {
|
|
return false;
|
|
}
|
|
if ($queue() === false) {
|
|
// Disabled templates do not consume the sender's notification slot.
|
|
$this->connection->delete('mailgateway_notification', ['sender_key' => $key]);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
}
|