96 lines
3.2 KiB
PHP
96 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service;
|
|
|
|
use App\Payload\SignalPayload;
|
|
use App\Entity\Signal;
|
|
use App\Exception\SubmissionRateLimitedException;
|
|
use App\Message\SignalCreatedMessage;
|
|
use App\Repository\SignalRepository;
|
|
use DateTimeImmutable;
|
|
use DateTimeZone;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
use Symfony\Component\Messenger\MessageBusInterface;
|
|
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
|
|
|
final class SignalSubmissionService
|
|
{
|
|
public function __construct(
|
|
private readonly SignalRepository $signals,
|
|
#[Autowire(service: 'limiter.signal_submission')]
|
|
private readonly RateLimiterFactory $submissionLimiter,
|
|
private readonly PointProximityValidator $proximityValidator,
|
|
private readonly MessageBusInterface $bus,
|
|
#[Autowire(service: 'monolog.logger.signals')]
|
|
private readonly LoggerInterface $logger,
|
|
) {
|
|
}
|
|
|
|
public function submit(string $clientKey, SignalPayload $payload): Signal
|
|
{
|
|
$this->enforceRateLimit($clientKey);
|
|
$signalLocation = $payload->signalLocation;
|
|
$userLocation = $payload->userLocation;
|
|
|
|
$this->proximityValidator->assertWithinRange($userLocation, $signalLocation);
|
|
|
|
$this->logger->info('Storing new signal submission.', [
|
|
'client_key' => $clientKey,
|
|
'signal_location' => [
|
|
'lat' => round($signalLocation->getLat(), 5),
|
|
'lng' => round($signalLocation->getLng(), 5),
|
|
],
|
|
'user_location' => [
|
|
'lat' => round($userLocation->getLat(), 5),
|
|
'lng' => round($userLocation->getLng(), 5),
|
|
],
|
|
]);
|
|
|
|
$signal = (new Signal())
|
|
->setUserKey($clientKey)
|
|
->setUserLocation($userLocation)
|
|
->setSignalLocation($signalLocation)
|
|
->setCreatedAt(new DateTimeImmutable('now', new DateTimeZone('UTC')));
|
|
|
|
$this->signals->save($signal);
|
|
|
|
$signalId = $signal->getId();
|
|
if ($signalId !== null) {
|
|
$this->logger->debug('Dispatching signal created message.', [
|
|
'signal_id' => $signalId,
|
|
]);
|
|
$this->bus->dispatch(new SignalCreatedMessage($signalId));
|
|
$this->logger->info('Signal stored successfully.', [
|
|
'signal_id' => $signalId,
|
|
'client_key' => $clientKey,
|
|
]);
|
|
}
|
|
|
|
return $signal;
|
|
}
|
|
|
|
private function enforceRateLimit(string $clientKey): void
|
|
{
|
|
$rateLimiter = $this->submissionLimiter->create($clientKey);
|
|
$limit = $rateLimiter->consume(1);
|
|
|
|
if (! $limit->isAccepted()) {
|
|
$retryAfter = $limit->getRetryAfter();
|
|
$this->logger->warning('Signal submission rejected due to rate limiting.', [
|
|
'client_key' => $clientKey,
|
|
'retry_after' => $retryAfter?->format(DATE_ATOM),
|
|
]);
|
|
throw new SubmissionRateLimitedException($limit->getRetryAfter());
|
|
}
|
|
|
|
$this->logger->debug('Signal submission passed rate limiting.', [
|
|
'client_key' => $clientKey,
|
|
'remaining_tokens' => $limit->getRemainingTokens(),
|
|
]);
|
|
}
|
|
|
|
}
|