Dispatch events on relevant events
Aerendir opened this issue · comments
Events that will be dispatched:
- Recipient filtered out (before sending);
- Recipient filtered out (after sending);
- Bounce notification from AWS SES;
- Complaint notification from AWS SES;
- Delivery notification from AWS SES;
Requested in #31
I was missing this feature too and as a workaround, and copy/pasted the code into a controller since it was really hard, if even possible to actually inherit from Processor
s.
Since I create AWS identity and topic by other means I only need to listen for confirmation requests and notifications.
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Doctrine\ORM\EntityManagerInterface;
use Aws\Sns\SnsClient;
use SerendipityHQ\Bundle\AwsSesMonitorBundle\Helper\MessageHelper;
use SerendipityHQ\Bundle\AwsSesMonitorBundle\SnsTypes;
use SerendipityHQ\Bundle\AwsSesMonitorBundle\Entity\Topic;
class SnsController extends Controller
{
private $snsClient;
private $messageHelper;
private $entityManager;
/**
* @param EntityManagerInterface $entityManager
* @param MessageHelper $messageHelper
*/
public function __construct(SnsClient $snsClient, EntityManagerInterface $entityManager, MessageHelper $messageHelper) {
$this->snsClient = $snsClient;
$this->messageHelper = $messageHelper;
$this->entityManager = $entityManager;
}
/**
* Receive SNS events.
*
* @Route(name="webhook_sns", path="/hook/amazon-sns")
* @Method({"POST"})
*
* @param Request $request
* @return Response
*/
public function processRequest(Request $request): Response
{
$messageTypeHeader = $request->headers->get('x-amz-sns-message-type');
if (null === $messageTypeHeader) {
throw new BadRequestHttpException('This request is invalid');
}
switch ($messageTypeHeader) {
case SnsTypes::HEADER_TYPE_NOTIFICATION:
return $this->processNotification($request);
case SnsTypes::HEADER_TYPE_CONFIRM_SUBSCRIPTION:
return $this->processSubscription($request);
default:
throw new \RuntimeException('We received a request with header "%s" but are not able to handle it. Please, add an handler to manage it.');
}
}
public function processSubscription(Request $request): Response
{
$message = $this->messageHelper->buildMessageFromRequest($request);
if (false === $this->messageHelper->validateNotification($message)) {
return new Response('The message is invalid.', 403);
}
/** @var Topic|null $topic */
$topic = $this->entityManager->getRepository(Topic::class)->findOneBy(['arn' => $message->offsetGet('TopicArn')]);
if (null === $topic) {
return new Response('Topic not found', 404);
}
$this->snsClient->confirmSubscription(
[
'TopicArn' => $topic->getArn(),
'Token' => $message->offsetGet('Token'),
]
);
$this->entityManager->flush();
return new Response('OK', 200);
}
public function processNotification(Request $request): Response
{
$message = $this->messageHelper->buildMessageFromRequest($request);
if (false === $this->messageHelper->validateNotification($message)) {
return new Response('The message is invalid.', 403);
}
$notificationData = $this->messageHelper->extractMessageData($message);
if (false === isset($notificationData['notificationType'])) {
return new Response('Missed NotificationType.', 403);
}
if (SnsTypes::MESSAGE_TYPE_SUBSCRIPTION_SUCCESS === $notificationData['notificationType']) {
return new Response('OK', 200);
}
$messageId = $notificationType['xxxxxx'];
if (!$messageId) {
return new Response('Missed Notification Message-ID.', 403);
}
switch ($notificationData['notificationType']) {
case SnsTypes::MESSAGE_TYPE_BOUNCE:
return $this->processBounce($notificationData);
case SnsTypes::MESSAGE_TYPE_COMPLAINT:
return $this->processComplaint($notificationData);
case SnsTypes::MESSAGE_TYPE_DELIVERY:
return $this->processDelivery($notificationData);
default:
return new Response('Notification type not understood', 403);
}
}
public function processBounce(array $notification): Response
{
return new Response('', 200); // override
}
public function processComplaint(array $notification): Response
{
return new Response('', 200);
}
public function processDelivery(array $notification): Response
{
return new Response('', 200);
}
}
MessageHelper
andSnsTypes
are a couple of define/wrapping lines- and I found that the need of database almost exclusively serves identity/topic creation and I'd personally prefer to store them inside
config/
, by allowing anarn
yaml key under eachidentity
.
That would allow people that only need confirmation+listen events to have an almost self-contained base class under 150 LoC