"""Notification API endpoints.""" import uuid from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query, status from pydantic import BaseModel, ConfigDict, Field from sqlalchemy.ext.asyncio import AsyncSession from src.auth.dependencies import get_current_user, get_db_session from src.models.user import User from src.models.user_config import UserConfig from src.services.notification_service import notification_service router = APIRouter(prefix="/notifications", tags=["notifications"]) class NotificationItem(BaseModel): model_config = ConfigDict(from_attributes=True) id: uuid.UUID user_id: uuid.UUID category: str severity: str title: str message: str | None source_type: str | None source_id: uuid.UUID | None notification_metadata: dict = Field(serialization_alias="metadata") read_at: datetime | None dismissed_at: datetime | None created_at: datetime class NotificationListResponse(BaseModel): items: list[NotificationItem] total: int limit: int offset: int class UnreadCountResponse(BaseModel): count: int class MarkAllReadResponse(BaseModel): marked_count: int class ClearAllResponse(BaseModel): cleared_count: int async def _get_mute_categories( session: AsyncSession, user_id: uuid.UUID, ) -> list[str]: """Read notification mute categories from user config.""" from sqlalchemy import select result = await session.execute( select(UserConfig).where(UserConfig.user_id == user_id) ) config = result.scalar_one_or_none() if config is None: return [] mute_categories = config.config.get("notification_mute_categories", []) if isinstance(mute_categories, list): return mute_categories return [] @router.get("", response_model=NotificationListResponse) async def list_notifications( limit: int = Query(20, ge=1, le=100), offset: int = Query(0, ge=0), unread_only: bool = Query(False), user: User = Depends(get_current_user), session: AsyncSession = Depends(get_db_session), ) -> NotificationListResponse: """List notifications for the authenticated user.""" mute_categories = await _get_mute_categories(session, user.id) items, total = await notification_service.list_notifications( session, user.id, limit=limit, offset=offset, unread_only=unread_only, mute_categories=mute_categories, ) return NotificationListResponse( items=[NotificationItem.model_validate(item) for item in items], total=total, limit=limit, offset=offset, ) @router.get("/unread", response_model=UnreadCountResponse) async def get_unread_count( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_db_session), ) -> UnreadCountResponse: """Get unread notification count for the authenticated user.""" count = await notification_service.get_unread_count(session, user.id) return UnreadCountResponse(count=count) @router.patch("/{notification_id}/read", response_model=NotificationItem) async def mark_notification_read( notification_id: uuid.UUID, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_db_session), ) -> NotificationItem: """Mark a single notification as read.""" try: notification = await notification_service.mark_read( session, notification_id, user.id ) except ValueError as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Notification not found", ) from exc return NotificationItem.model_validate(notification) @router.post("/mark-all-read", response_model=MarkAllReadResponse) async def mark_all_read( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_db_session), ) -> MarkAllReadResponse: """Mark all unread notifications as read.""" marked = await notification_service.mark_all_read(session, user.id) return MarkAllReadResponse(marked_count=marked) @router.delete("", status_code=status.HTTP_200_OK) async def clear_all_notifications( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_db_session), ) -> ClearAllResponse: """Dismiss all notifications for the authenticated user.""" cleared = await notification_service.dismiss_all(session, user.id) return ClearAllResponse(cleared_count=cleared) @router.delete("/{notification_id}", status_code=status.HTTP_204_NO_CONTENT) async def dismiss_notification( notification_id: uuid.UUID, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_db_session), ) -> None: """Soft-delete (dismiss) a single notification.""" try: await notification_service.dismiss(session, notification_id, user.id) except ValueError as exc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Notification not found", ) from exc