import json import logging import uuid from dataclasses import dataclass from datetime import date from typing import Any from httpx import HTTPStatusError from pydantic import TypeAdapter, ValidationError from app.adapters.aws.s3 import S3Client from app.adapters.ows_preference_center import OwsPreferenceCenterClient from app.auth import Auth from app.exceptions import AuthException from app.models import Event, EventType, Request EventsValidator = TypeAdapter(list[Event]) logger = logging.getLogger(__name__) @dataclass(kw_only=True) class SendgridEventHandler: auth: Auth s3_client: S3Client ows_preference_center_client: OwsPreferenceCenterClient failed_events_s3_bucket: str def handle(self, request_dict: dict[str, Any]) -> dict[str, int]: status_code = 200 write_to_s3 = False try: request = Request.model_validate(request_dict) self.auth.validate(request) events = EventsValidator.validate_json(request.body) for event in events: if event.event == EventType.spamreport: self.ows_preference_center_client.unsubscribe_from_all( email=event.email, origin_id=event.origin_id, origin_type=event.email_type, automated_trigger_id=event.automated_trigger_id, ) logger.info( "Events processing success", extra={"events_count": len(events)} ) except AuthException: logger.error("Auth error") status_code = 401 except ValidationError: logger.error("Events validation error") status_code = 400 except HTTPStatusError as exc: if exc.response.status_code == 404: logger.warning("Fan not found in OWS Preference Center") else: logger.error("Handler exception") status_code = 500 write_to_s3 = True except Exception: logger.error("Handler exception") status_code = 500 if write_to_s3 or status_code != 200: self.s3_client.put_object( bucket=self.failed_events_s3_bucket, key=f"custom-handlers/{date.today().isoformat()}/{uuid.uuid4()}.json", body=json.dumps(request_dict), ) return {"statusCode": status_code}