import json from datetime import datetime import exponent_server_sdk as expo from marshmallow import EXCLUDE, Schema, ValidationError, fields, pre_load from send_push_messages.constants import RECEIPT_STATUS_MAPPING, PushMessageStatus class ExpoPushMessageSchema(Schema): class Meta: unknown = EXCLUDE to = fields.Str(required=True) data = fields.Dict(required=False, missing=None, allow_none=True) title = fields.Str(required=False, missing=None, allow_none=True) body = fields.Str(required=False, missing=None, allow_none=True) sound = fields.Str(required=False, missing=None, allow_none=True) ttl = fields.Int(required=False, missing=None, allow_none=True) expiration = fields.Float(required=False, missing=None, allow_none=True) priority = fields.Str(required=False, missing=None, allow_none=True) badge = fields.Int(required=False, missing=None, allow_none=True) category = fields.Str(required=False, missing=None, allow_none=True) channel_id = fields.Str(required=False, missing=None, allow_none=True) display_in_foreground = fields.Bool(required=False, missing=None, allow_none=True) @pre_load def validate_token(self, data, **kwargs): if not expo.PushClient.is_exponent_push_token(data.get("to", "")): raise ValidationError("Invalid Expo token.") return data class PushMessageReceiptSchema(Schema): class Meta: unknown = EXCLUDE id = fields.Method("get_id") status = fields.Method("get_status") details = fields.Method("get_details") receipt_id = fields.String(attribute="id") device_id = fields.Method("get_device_id") created_at = fields.Method("get_created_at") expo_token = fields.String(attribute="push_message.to") def get_id(self, receipt) -> str: # returns original message id return self.context["message_id_device_id_map"][id(receipt.push_message)][0] def get_status(self, receipt) -> str: return RECEIPT_STATUS_MAPPING[receipt.status] def get_details(self, receipt) -> str: details = receipt.details or {} details = details.get("error") if isinstance(details, dict) else details return json.dumps(details) if details else str(receipt.message) def get_device_id(self, receipt): return self.context["message_id_device_id_map"][id(receipt.push_message)][1] def get_created_at(self, receipt): return datetime.utcnow().isoformat() def format_failed_input_receipt(id, details): return {"id": id, "status": PushMessageStatus.FAILED, "details": details}