Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispatch events on relevant events #64

Open
5 tasks
Aerendir opened this issue Jul 12, 2018 · 1 comment
Open
5 tasks

Dispatch events on relevant events #64

Aerendir opened this issue Jul 12, 2018 · 1 comment

Comments

@Aerendir
Copy link
Owner

Aerendir commented Jul 12, 2018

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

@drzraf
Copy link

drzraf commented Nov 13, 2018

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 Processors.

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 and SnsTypes 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 an arn yaml key under each identity.

That would allow people that only need confirmation+listen events to have an almost self-contained base class under 150 LoC

@Aerendir Aerendir removed the Feature label Jul 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants