AINELAQL-othaim-alpr-api / app /api /notifications.py
Pant0x's picture
Add HF Space Gradio SDK backend (free tier)
34a66f3
Raw
History Blame Contribute Delete
4.49 kB
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}