"""In-app notification inbox API.""" from __future__ import annotations import logging from typing import Any from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel, Field from app.gateway.deps import get_notification_store from deerflow.persistence.notifications import NotificationStore from deerflow.runtime.user_context import get_effective_user_id logger = logging.getLogger(__name__) router = APIRouter(prefix="/api", tags=["notifications"]) def _current_user_id(request: Request) -> str: user = getattr(request.state, "user", None) if user is not None: return str(user.id) return get_effective_user_id() class NotificationItem(BaseModel): id: str user_id: str type: str title: str body: str payload: dict[str, Any] read: bool created_at: str | None class NotificationListResponse(BaseModel): items: list[NotificationItem] class NotificationUnreadCountResponse(BaseModel): unread: int = Field(..., description="Number of unread notifications for the current user") class MarkReadResponse(BaseModel): success: bool class MarkAllReadResponse(BaseModel): updated: int @router.get( "/notifications", response_model=NotificationListResponse, summary="List Notifications", ) async def list_notifications( request: Request, unread_only: bool = Query(default=False), limit: int = Query(default=50, ge=1, le=200), store: NotificationStore = Depends(get_notification_store), ) -> NotificationListResponse: try: items = await store.list_for_user( _current_user_id(request), unread_only=unread_only, limit=limit, ) return NotificationListResponse(items=[NotificationItem(**item) for item in items]) except Exception as e: logger.error("Failed to list notifications: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to list notifications: {e}") @router.get( "/notifications/unread_count", response_model=NotificationUnreadCountResponse, summary="Unread Notification Count", ) async def unread_count( request: Request, store: NotificationStore = Depends(get_notification_store), ) -> NotificationUnreadCountResponse: try: count = await store.unread_count(_current_user_id(request)) return NotificationUnreadCountResponse(unread=count) except Exception as e: logger.error("Failed to read unread notification count: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to read unread count: {e}") @router.post( "/notifications/{notification_id}/read", response_model=MarkReadResponse, summary="Mark Notification Read", ) async def mark_read( notification_id: str, request: Request, store: NotificationStore = Depends(get_notification_store), ) -> MarkReadResponse: try: ok = await store.mark_read(notification_id, _current_user_id(request)) if not ok: raise HTTPException(status_code=404, detail="Notification not found") return MarkReadResponse(success=True) except HTTPException: raise except Exception as e: logger.error("Failed to mark notification read: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to mark read: {e}") @router.post( "/notifications/read_all", response_model=MarkAllReadResponse, summary="Mark All Notifications Read", ) async def mark_all_read( request: Request, store: NotificationStore = Depends(get_notification_store), ) -> MarkAllReadResponse: try: count = await store.mark_all_read(_current_user_id(request)) return MarkAllReadResponse(updated=count) except Exception as e: logger.error("Failed to mark all notifications read: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to mark all read: {e}") @router.delete( "/notifications/{notification_id}", response_model=MarkReadResponse, summary="Delete Notification", ) async def delete_notification( notification_id: str, request: Request, store: NotificationStore = Depends(get_notification_store), ) -> MarkReadResponse: try: ok = await store.delete(notification_id, _current_user_id(request)) if not ok: raise HTTPException(status_code=404, detail="Notification not found") return MarkReadResponse(success=True) except HTTPException: raise except Exception as e: logger.error("Failed to delete notification: %s", e, exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to delete: {e}")