Spaces:
Running on Zero
Running on Zero
File size: 4,493 Bytes
34a66f3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Body, Header, HTTPException
import requests
from app.core.config import (
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_TIMEOUT_SECONDS,
supabase_configured, LOGGER,
)
from app.security.auth import require_authenticated_user, _supabase_headers, _normalize_spaces
router = APIRouter(tags=["notifications"])
def _upsert_notification_device_token(
*,
user_id: str,
fcm_token: str,
platform: Optional[str],
device_id: Optional[str],
) -> Optional[Dict[str, Any]]:
payload: Dict[str, Any] = {
"user_id": user_id,
"fcm_token": fcm_token,
"is_active": True,
"last_seen_at": datetime.now(timezone.utc).isoformat(),
}
if platform:
payload["platform"] = platform
if device_id:
payload["device_id"] = device_id
response = requests.post(
f"{SUPABASE_URL}/rest/v1/notification_device_tokens",
params={"on_conflict": "fcm_token"},
json=payload,
headers=_supabase_headers(
api_key=SUPABASE_SERVICE_ROLE_KEY,
bearer=SUPABASE_SERVICE_ROLE_KEY,
content_type="application/json",
prefer="resolution=merge-duplicates,return=representation",
),
timeout=SUPABASE_TIMEOUT_SECONDS,
)
if response.status_code not in {200, 201}:
raise HTTPException(status_code=502, detail=f"Failed to save device token: {response.text[:220]}")
rows = response.json() if response.text else []
if isinstance(rows, list) and rows and isinstance(rows[0], dict):
return rows[0]
return None
def _list_notification_events_for_user(user_id: str, limit: int) -> List[Dict[str, Any]]:
response = requests.get(
f"{SUPABASE_URL}/rest/v1/notification_events",
params={
"select": "id,event_type,title,body,data,read_at,created_at",
"user_id": f"eq.{user_id}",
"order": "created_at.desc",
"limit": str(limit),
},
headers=_supabase_headers(
api_key=SUPABASE_SERVICE_ROLE_KEY,
bearer=SUPABASE_SERVICE_ROLE_KEY,
),
timeout=SUPABASE_TIMEOUT_SECONDS,
)
if response.status_code != 200:
raise HTTPException(status_code=502, detail=f"Failed to load notifications: {response.text[:240]}")
rows = response.json()
if not isinstance(rows, list):
return []
return [row for row in rows if isinstance(row, dict)]
@router.post("/notifications/fcm-token")
def notifications_register_fcm_token(
payload: Dict[str, Any] = Body(...),
authorization: Optional[str] = Header(default=None),
) -> Dict[str, Any]:
if not supabase_configured():
raise HTTPException(status_code=503, detail="Supabase is not configured.")
request_user = require_authenticated_user(authorization)
if request_user is None:
raise HTTPException(status_code=401, detail="Authentication is required.")
requester_id = str(request_user.get("id") or "").strip()
if not requester_id:
raise HTTPException(status_code=401, detail="Authenticated user id is missing.")
fcm_token = _normalize_spaces(str(payload.get("fcm_token") or payload.get("token") or ""))
if not fcm_token:
raise HTTPException(status_code=400, detail="fcm_token is required.")
platform = _normalize_spaces(str(payload.get("platform") or "")) or None
device_id = _normalize_spaces(str(payload.get("device_id") or "")) or None
row = _upsert_notification_device_token(
user_id=requester_id, fcm_token=fcm_token, platform=platform, device_id=device_id,
)
return {"status": "ok", "device": row, "message": "FCM token registered."}
@router.get("/notifications/my")
def notifications_my(
limit: int = 50,
authorization: Optional[str] = Header(default=None),
) -> Dict[str, Any]:
request_user = require_authenticated_user(authorization)
if request_user is None:
raise HTTPException(status_code=401, detail="Authentication is required.")
requester_id = str(request_user.get("id") or "").strip()
if not requester_id:
raise HTTPException(status_code=401, detail="Authenticated user id is missing.")
resolved_limit = max(1, min(200, int(limit)))
rows = _list_notification_events_for_user(requester_id, resolved_limit)
return {"status": "ok", "count": len(rows), "notifications": rows}
|