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)