feat: 集成飞书通知和机器人对话系统
- 新增通知系统 (notifications 表、服务、API) - 新增飞书定时任务结果推送 (webhook + 应用消息) - 新增飞书应用消息发送服务 (feishu_app_service) - 新增飞书 WebSocket 长连接事件监听 (苹果应用) - 新增飞书账号绑定/解绑 API - 新增橙子飞书机器人 (独立 WS 连接,固定路由到橙子助手 Agent) - 执行记录添加 schedule_id,用户添加飞书绑定字段 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
115
backend/app/api/notifications.py
Normal file
115
backend/app/api/notifications.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""通知 API — 列表、已读、删除"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.auth import get_current_user
|
||||
from app.core.database import get_db
|
||||
from app.models.user import User
|
||||
from app.services.notification_service import (
|
||||
delete_notification,
|
||||
get_unread_count,
|
||||
get_user_notifications,
|
||||
mark_all_as_read,
|
||||
mark_as_read,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/api/v1/notifications", tags=["notifications"])
|
||||
|
||||
|
||||
# ─── Pydantic Schemas ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class NotificationResponse(BaseModel):
|
||||
id: str
|
||||
user_id: str
|
||||
title: str
|
||||
content: Optional[str] = None
|
||||
category: str
|
||||
ref_type: Optional[str] = None
|
||||
ref_id: Optional[str] = None
|
||||
is_read: bool
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UnreadCountResponse(BaseModel):
|
||||
count: int
|
||||
|
||||
|
||||
# ─── API Endpoints ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("", response_model=List[NotificationResponse])
|
||||
async def list_notifications(
|
||||
unread_only: bool = Query(False, description="仅未读"),
|
||||
category: Optional[str] = Query(None, description="按分类过滤"),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取当前用户的通知列表。"""
|
||||
return get_user_notifications(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
unread_only=unread_only,
|
||||
category=category,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/unread-count", response_model=UnreadCountResponse)
|
||||
async def unread_count(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取当前用户的未读通知数。"""
|
||||
count = get_unread_count(db, current_user.id)
|
||||
return {"count": count}
|
||||
|
||||
|
||||
@router.put("/{notification_id}/read")
|
||||
async def read_notification(
|
||||
notification_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""将一条通知标记为已读。"""
|
||||
result = mark_as_read(db, notification_id, current_user.id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="通知不存在")
|
||||
return {"message": "已标记为已读"}
|
||||
|
||||
|
||||
@router.put("/read-all")
|
||||
async def read_all_notifications(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""将所有通知标记为已读。"""
|
||||
count = mark_all_as_read(db, current_user.id)
|
||||
return {"message": f"已将 {count} 条通知标记为已读"}
|
||||
|
||||
|
||||
@router.delete("/{notification_id}")
|
||||
async def remove_notification(
|
||||
notification_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""删除一条通知。"""
|
||||
ok = delete_notification(db, notification_id, current_user.id)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=404, detail="通知不存在")
|
||||
return {"message": "通知已删除"}
|
||||
Reference in New Issue
Block a user