Files
eryao/backend/src/v1/notifications/router.py
T

142 lines
4.5 KiB
Python
Raw Normal View History

2026-04-10 18:50:08 +08:00
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from core.logging import get_logger
2026-04-10 18:50:08 +08:00
from core.auth.models import CurrentUser
from v1.notifications.dependencies import get_notification_service
from v1.notifications.schemas import (
MarkAllReadResponse,
NotificationItemResponse,
NotificationListResponse,
UnreadCountResponse,
)
from v1.notifications.service import NotificationService
from v1.users.dependencies import get_current_user
router = APIRouter(prefix="/notifications", tags=["notifications"])
logger = get_logger("v1.notifications.router")
2026-04-10 18:50:08 +08:00
@router.get("", response_model=NotificationListResponse)
async def list_notifications(
service: Annotated[NotificationService, Depends(get_notification_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
limit: int = Query(default=20, ge=1, le=50),
cursor: str | None = Query(default=None),
) -> NotificationListResponse:
from datetime import datetime
parsed_cursor = None
if cursor is not None:
try:
parsed_cursor = datetime.fromisoformat(cursor.replace("Z", "+00:00"))
except (ValueError, AttributeError):
parsed_cursor = None
result = await service.list_notifications(
user_id=current_user.id,
limit=limit,
cursor=parsed_cursor,
)
logger.info(
"Notification list fetched",
user_id=str(current_user.id),
limit=limit,
item_count=len(result.items),
has_more=result.has_more,
)
2026-04-10 18:50:08 +08:00
items = []
for item in result.items:
items.append(
NotificationItemResponse(
id=str(item.id),
notificationId=str(item.notification_id),
type=item.type,
title=item.title,
body=item.body,
payload=item.payload,
isRead=item.is_read,
readAt=item.read_at,
createdAt=item.created_at,
)
)
return NotificationListResponse(
items=items,
nextCursor=result.next_cursor.isoformat() if result.next_cursor else None,
hasMore=result.has_more,
)
@router.get("/unread-count", response_model=UnreadCountResponse)
async def get_unread_count(
service: Annotated[NotificationService, Depends(get_notification_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> UnreadCountResponse:
count = await service.get_unread_count(user_id=current_user.id)
logger.info(
"Notification unread count fetched",
user_id=str(current_user.id),
count=count,
)
2026-04-10 18:50:08 +08:00
return UnreadCountResponse(count=count)
@router.patch("/{notification_id}/read", response_model=NotificationItemResponse)
async def mark_notification_read(
notification_id: str,
service: Annotated[NotificationService, Depends(get_notification_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> NotificationItemResponse:
from uuid import UUID
try:
uid = UUID(notification_id)
except ValueError:
from core.http.errors import ApiProblemError, problem_payload
raise ApiProblemError(
status_code=404,
detail=problem_payload(
code="NOTIFICATION_NOT_FOUND",
detail="Notification not found or not owned by current user",
),
)
item = await service.mark_read(
user_notification_id=uid,
user_id=current_user.id,
)
logger.info(
"Notification marked as read",
user_id=str(current_user.id),
user_notification_id=str(uid),
)
2026-04-10 18:50:08 +08:00
return NotificationItemResponse(
id=str(item.id),
notificationId=str(item.notification_id),
type=item.type,
title=item.title,
body=item.body,
payload=item.payload,
isRead=item.is_read,
readAt=item.read_at,
createdAt=item.created_at,
)
@router.patch("/mark-all-read", response_model=MarkAllReadResponse)
async def mark_all_read(
service: Annotated[NotificationService, Depends(get_notification_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> MarkAllReadResponse:
updated_count = await service.mark_all_read(user_id=current_user.id)
logger.info(
"All notifications marked as read",
user_id=str(current_user.id),
updated_count=updated_count,
)
2026-04-10 18:50:08 +08:00
return MarkAllReadResponse(updatedCount=updated_count)