feat(desktop): implement phase 2 native capabilities
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled

This commit is contained in:
Cheng Zhou
2026-06-30 21:21:55 +08:00
parent 7c721d4e5c
commit 628ff8828b
64 changed files with 3516 additions and 222 deletions
@@ -0,0 +1,86 @@
import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_user, get_db_session
from app.schemas.notification import (
DesktopNotificationAckRequest,
DesktopNotificationClaimRequest,
DesktopNotificationClaimResponse,
DesktopNotificationSubscriptionRead,
DesktopNotificationSubscriptionUpdate,
)
from app.services import desktop_notification_service
router = APIRouter()
@router.get("/subscription", response_model=DesktopNotificationSubscriptionRead)
async def read_subscription(
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DesktopNotificationSubscriptionRead:
subscription = await desktop_notification_service.get_subscription(db, current_user.id)
return DesktopNotificationSubscriptionRead(
enabled=bool(subscription and subscription.enabled),
enabled_at=subscription.enabled_at if subscription else None,
)
@router.put("/subscription", response_model=DesktopNotificationSubscriptionRead)
async def update_subscription(
payload: DesktopNotificationSubscriptionUpdate,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DesktopNotificationSubscriptionRead:
subscription = await desktop_notification_service.set_subscription(
db, current_user.id, payload.enabled
)
return DesktopNotificationSubscriptionRead(
enabled=subscription.enabled,
enabled_at=subscription.enabled_at,
)
@router.post("/claim", response_model=DesktopNotificationClaimResponse)
async def claim_notifications(
payload: DesktopNotificationClaimRequest,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DesktopNotificationClaimResponse:
if payload.limit < 1 or payload.limit > 50:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="limit 必须在 1 到 50 之间")
claim_token, lease_expires_at, items = await desktop_notification_service.claim_notifications(
db, current_user.id, payload.limit
)
return DesktopNotificationClaimResponse(
claim_token=claim_token,
lease_expires_at=lease_expires_at,
items=items,
)
@router.post("/ack", status_code=status.HTTP_204_NO_CONTENT)
async def acknowledge_notifications(
payload: DesktopNotificationAckRequest,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> None:
await desktop_notification_service.acknowledge_notifications(
db,
current_user.id,
payload.claim_token,
payload.delivered_ids,
)
@router.post("/{distribution_id}/read", status_code=status.HTTP_204_NO_CONTENT)
async def mark_notification_read(
distribution_id: uuid.UUID,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> None:
await desktop_notification_service.mark_notification_read(
db, current_user.id, distribution_id
)