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}