import json from typing import Any, List import boto3 import exponent_server_sdk as expo from marshmallow import ValidationError from smelog.factory import BoundLogger from send_push_messages.config import Config as config from send_push_messages.constants import MAX_EXPO_MESSAGES_CHUNK_SIZE, PushMessageErrors from send_push_messages.push_client import PushClient from send_push_messages.schemas import ExpoPushMessageSchema, PushMessageReceiptSchema, format_failed_input_receipt from send_push_messages.sqs_client import SQSClient def send_and_save_receipts( logger: BoundLogger, records: List[dict], messages_chunk_size: int, push_client: PushClient, sqs_client: SQSClient, push_message_schema_cls=ExpoPushMessageSchema, push_receipt_schema_cls=PushMessageReceiptSchema, ): """Main function to validate input messages, send them to Expo and send got receipts to SQS.""" push_messages = [] invalid_messages_receipts = [] push_to_message_id_device_id_map = {} push_message_schema = push_message_schema_cls() receipts_schema = push_receipt_schema_cls(many=True) number_of_records = len(records) logger.info(f"1. Got {number_of_records} records: {records}") for i, record in enumerate(records): body = json.loads(record["body"]) message = json.loads(body.get("Message", {})) message_id = message.get("id") device_id = message.get("device_id") if message_id is None: logger.error(f"2.0 Got message without id, impossible to save report, skipped: {message}.") continue logger.info(f"[{i}/{number_of_records}][{message_id}] 2.1 Start processing message.\nMessage: {message}") try: push_data = push_message_schema.load(message) except ValidationError as ex: invalid_messages_receipts.append( format_failed_input_receipt(message_id, f"{PushMessageErrors.INVALID_INPUT_DATA_ERROR}. {ex.messages}") ) logger.error(f"Got invalid message: {message}.\nError: {ex.messages}.") continue push_message = expo.PushMessage(**push_data) push_to_message_id_device_id_map[id(push_message)] = (message_id, device_id) push_messages.append(push_message) logger.info( f"[{i}/{number_of_records}][{message_id}] 2.2 Finish dumping message.\n" f"Prepared {len(push_messages)} push messages: {push_messages}.\n" f"Prepared {len(invalid_messages_receipts)} receipts: {invalid_messages_receipts}." ) expo_responses = push_client.send_by_chunks(push_messages, chunk_size=messages_chunk_size) logger.info(f"3. Got {len(expo_responses)} after sending to Expo: {expo_responses}") receipts_schema.context["message_id_device_id_map"] = push_to_message_id_device_id_map receipts = invalid_messages_receipts + receipts_schema.dump(expo_responses) logger.info(f"4. Finally prepared {len(receipts)} receipts: {receipts}.") sqs_client.send(config.RECEIPTS_QUEUE, receipts) logger.info("5. Successfully sent to SQS.") def handler(logger: BoundLogger, event: dict) -> Any: """Processing batch of messages from SQS and send them via expo Args: logger: Logger instance. event: Data from request. """ messages_chunk_size = config.MESSAGES_CHUNK_SIZE if messages_chunk_size >= MAX_EXPO_MESSAGES_CHUNK_SIZE: messages_chunk_size = MAX_EXPO_MESSAGES_CHUNK_SIZE logger.warning( f"Invalid config: 'MESSAGES_CHUNK_SIZE' {messages_chunk_size} " f"should be less or equal {MAX_EXPO_MESSAGES_CHUNK_SIZE}. " f"{MAX_EXPO_MESSAGES_CHUNK_SIZE} was used." ) logger.info( f"Lambda has started with configuration:" f"\nMESSAGES_CHUNK_SIZE={config.MESSAGES_CHUNK_SIZE}" f"\nEXPO_RETRY_COUNT={config.EXPO_RETRY_COUNT}" f"\nEXPO_RETRY_DELAY={config.EXPO_RETRY_DELAY}" f"\nRECEIPTS_SQS_RETRY_COUNT={config.RECEIPTS_SQS_RETRY_COUNT}" f"\nRECEIPTS_SQS_RETRY_DELAY={config.RECEIPTS_SQS_RETRY_DELAY}" ) records = event.get("Records", []) if len(records) > messages_chunk_size: logger.error( f"We strongly recommend you do not set a SQS maximum message count {len(records)} " f"greater than 'MESSAGES_CHUNK_SIZE' {messages_chunk_size}, this may lead to re-sending messages." ) expo_client = expo.PushClient() push_client = PushClient(provider=expo_client, logger=logger) sqs_client = SQSClient(provider=boto3.client("sqs"), logger=logger) send_and_save_receipts(logger, records, messages_chunk_size, push_client, sqs_client)