import math from datetime import datetime from typing import Any, Dict, List, Tuple from smelog.factory import BoundLogger from apollo_save_push_receipts.config import Config as config from apollo_save_push_receipts.constants import PushMessageStatus, APP_NAME from apollo_save_push_receipts.schemas import PushMessage from apollo_save_push_receipts.user_data_client import UserDataClient def _check_is_deactivate_receipt(receipt: Dict[str, Any]) -> bool: """Main check if the receipt is the DEACTIVATE one""" return receipt["status"] == PushMessageStatus.FAILED and ( receipt["receipt"] == "DeviceNotRegistered" or receipt["receipt"] in "is not a registered push notification recipient" ) def _check_deactivate_receipt_ttl(receipt: Dict[str, Any], logger: BoundLogger) -> bool: """Check receipt is actual and needs to be processed""" current_dt = datetime.utcnow() receipt_dt = receipt["created_at"] ttl_diff = (current_dt - receipt_dt).total_seconds() is_actual = ttl_diff <= config.DEACTIVATE_RECEIPT_TTL if not is_actual: logger.info(f"\nGot an outdated DEACTIVATE receipt\n" f"DEACTIVATE RECEIPT TTL: {config.DEACTIVATE_RECEIPT_TTL} seconds\n" f"Current datetime: {current_dt}\n" f"Receipt datetime: {receipt_dt}\n" f"TTL difference: {ttl_diff}\n" f"RECEIPT: {receipt}") return is_actual def _distribute_receipts( logger: BoundLogger, receipts: List[Dict[str, Any]], receipt_schema: PushMessage ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: """Distribute receipts between those to update records and to deactivate devices """ update_receipts, deactivate_receipts = [], [] for record in receipts: record_object = receipt_schema.ReceiptSchema().loads(record["body"]) if _check_is_deactivate_receipt(record_object): if _check_deactivate_receipt_ttl(record_object, logger): deactivate_receipts.append(receipt_schema.Deactivate().dump(record_object)) continue update_receipts.append(receipt_schema.Update().dump(record_object)) logger.info(f"\nGot: {len(update_receipts)} update receipts\n" f"UPDATE RECEIPTS: {update_receipts}\n" f"Got: {len(deactivate_receipts)} deactivate receipts\n" f"DEACTIVATE RECEIPTS: {deactivate_receipts}") return update_receipts, deactivate_receipts def handler(logger: BoundLogger, event: dict) -> Any: user_data_client = UserDataClient(logger) records = event.get("Records", []) schema = PushMessage() records_length = len(records) number_of_runs = math.ceil(records_length/config.BATCH_SIZE) logger.info(f"\nLambda: {APP_NAME} got {records_length} receipts to process\n" f"With BATCH SIZE: {config.BATCH_SIZE} items pre request" f"all events will be processed with {number_of_runs} run(s)\n" f"All events to process: {records}") for i in range(0, records_length, config.BATCH_SIZE): logger.info(f"\nStarting run number {i+1}/{number_of_runs}") update, deactivate = _distribute_receipts( logger=logger, receipts=records[i : i + config.BATCH_SIZE], receipt_schema=schema) if update: user_data_client.update_bulk(update) if deactivate: user_data_client.deactivate_bulk(deactivate)