import html import json import uuid from typing import Any, List import exponent_server_sdk as expo from redis import Redis from sqlalchemy.orm import Session from apollo_main_db.push_notifications.models import PushMessageReceipt, UserDeviceToken from smelog.factory import BoundLogger from send_push_notifications import constants from send_push_notifications.main_db import session_scope from send_push_notifications.push_client import PushClient from send_push_notifications.redis_db import is_new_message_id, trim_redis_set def handle_error_message(response_details: dict, token: str, session: Session): """Method deactivate user devices if it isn't registered Args: response_details: response details dict token: Device token session: SqlAlchemy session. """ if isinstance(response_details, dict) and response_details.get('error') == constants.DEVICE_NOT_REGISTERED_ERROR: session.query(UserDeviceToken).filter_by( token=token).update({'is_active': False}) def send_messages( push_messages: List[expo.PushMessage], session: Session, logger: BoundLogger) -> None: """Method for sending messages to recipients using exponent_server_sdk. Args: push_messages: List of expo.PushMessage session: Session instance logger: Logger instance Returns: None Raises: ConnectionError: If PushClient().send() returns None or empty list """ result = [] receipts = PushClient(provider=expo.PushClient(), logger=logger).send(push_messages) logger.info(f"{len(receipts)} receipts were received from Expo.") for receipt in receipts: response_details = getattr(receipt, 'details', {}) response_message = getattr(receipt, 'message', '') status = getattr(receipt, 'status', '') push_message = getattr(receipt, 'push_message') token = getattr(push_message, 'to', '') data = getattr(push_message, 'data', {}) receipt_id = data.get('id') response_message_str = json.dumps(response_details) if response_details else str(response_message) result.append(PushMessageReceipt( message_id=uuid.UUID(receipt_id) if receipt_id else None, token=token, status=status, response_message=response_message_str, )) if status == expo.PushResponse.ERROR_STATUS: logger.debug('error') logger.debug(response_message) logger.debug(response_details) if response_details: handle_error_message(response_details, token, session) session.add_all(result) session.commit() logger.info(f"{len(result)} messages receipts were saved in DB.") def handler(logger: BoundLogger, event: dict, redis_client: Redis) -> Any: """Processing batch of messages from SQS and send them via expo Args: logger: Logger instance. event: Data from request. redis_client: Redis client. """ messages = [] for record in event.get('Records'): message_sqs_id = record['messageId'] body = json.loads(record['body']) message_inner_id = body.get('id') logger.info(f'RECEIVED FROM QUEUE: message sqs_id={message_sqs_id}, inner_id={message_inner_id}') if not is_new_message_id(message_sqs_id, redis_client): logger.info(f'SKIPPED: message sqs_id={message_sqs_id}, inner_id={message_inner_id}') continue push_tokens = set(body.get('tokens', [])) logger.info(f"PUSH TOKENS: {', '.join(push_tokens)}") for push_token in push_tokens: if not push_token: logger.warning(f'INVALID PUSH_TOKEN SKIPPED: {push_token}') continue messages.append(expo.PushMessage( to=push_token, body=html.unescape(body.get('message', '')), data=body.get('data', {}), sound='default', channel_id=constants.DEFAULT_CHANNEL_ID, display_in_foreground='true' )) if messages: with session_scope() as session: send_messages(messages, session, logger) logger.info(f"{len(messages)} messages were passed to Expo.") trim_redis_set(redis_client)