[V1.0] Working application, added notifications.
Ready to upload to store.
This commit is contained in:
0
backend/modules/notifications/__init__.py
Normal file
0
backend/modules/notifications/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
111
backend/modules/notifications/service.py
Normal file
111
backend/modules/notifications/service.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import httpx
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def send_push_notification(
|
||||
push_token: str, title: str, body: str, data: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Sends a push notification to a specific Expo push token.
|
||||
|
||||
Args:
|
||||
push_token: The recipient's Expo push token.
|
||||
title: The title of the notification.
|
||||
body: The main message content of the notification.
|
||||
data: Optional dictionary containing extra data to send with the notification.
|
||||
|
||||
Returns:
|
||||
True if the notification was sent successfully (according to Expo API), False otherwise.
|
||||
"""
|
||||
if not push_token:
|
||||
logger.warning("Attempted to send notification but no push token provided.")
|
||||
return False
|
||||
|
||||
message = {
|
||||
"to": push_token,
|
||||
"sound": "default",
|
||||
"title": title,
|
||||
"body": body,
|
||||
"priority": "high",
|
||||
"channelId": "default",
|
||||
}
|
||||
if data:
|
||||
message["data"] = data
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
response = await client.post(
|
||||
settings.EXPO_PUSH_API_URL,
|
||||
headers={
|
||||
"Accept": "application/json",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json=message,
|
||||
timeout=10.0,
|
||||
)
|
||||
response.raise_for_status() # Raise exception for 4xx/5xx responses
|
||||
|
||||
response_data = response.json()
|
||||
logger.debug(f"Expo push API response: {response_data}")
|
||||
|
||||
# Check for top-level errors first
|
||||
if "errors" in response_data:
|
||||
error_messages = [
|
||||
err.get("message", "Unknown error")
|
||||
for err in response_data["errors"]
|
||||
]
|
||||
logger.error(
|
||||
f"Expo API returned errors for {push_token[:10]}...: {'; '.join(error_messages)}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Check the status in the data field
|
||||
receipt = response_data.get("data")
|
||||
|
||||
# if receipts is a list
|
||||
if receipt:
|
||||
status = receipt.get("status")
|
||||
|
||||
if status == "ok":
|
||||
logger.info(
|
||||
f"Successfully sent push notification to token: {push_token[:10]}..."
|
||||
)
|
||||
return True
|
||||
else:
|
||||
# Log details if the status is not 'ok'
|
||||
error_details = receipt.get("details")
|
||||
error_message = receipt.get("message")
|
||||
logger.error(
|
||||
f"Failed to send push notification to {push_token[:10]}... "
|
||||
f"Expo status: {status}, Message: {error_message}, Details: {error_details}"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
# Log if 'data' is missing, not a list, or an empty list
|
||||
logger.error(
|
||||
f"Unexpected Expo API response format or empty 'data' field for {push_token[:10]}... "
|
||||
f"Response: {response_data}"
|
||||
)
|
||||
return False
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(
|
||||
f"HTTP error sending push notification to {push_token[:10]}...: {e.response.status_code} - {e.response.text}"
|
||||
)
|
||||
return False
|
||||
except httpx.RequestError as e:
|
||||
logger.error(
|
||||
f"Network error sending push notification to {push_token[:10]}...: {e}"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"Unexpected error sending push notification to {push_token[:10]}...: {e}"
|
||||
)
|
||||
return False
|
||||
Reference in New Issue
Block a user