Spaces:
Running on Zero
Running on Zero
| 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)] | |
| 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."} | |
| 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} | |