diff --git a/app/dependencies/param.py b/app/dependencies/param.py index 174adde..28a30c6 100644 --- a/app/dependencies/param.py +++ b/app/dependencies/param.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect from typing import Any from fastapi import Request @@ -36,4 +37,15 @@ def BodyOrForm[T: BaseModel](model: type[T]): except ValidationError as e: raise RequestValidationError(e.errors()) + dependency.__signature__ = inspect.signature( # pyright: ignore[reportFunctionMemberAccess] + lambda x: None + ).replace( + parameters=[ + inspect.Parameter( + name=model.__name__.lower(), + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, + annotation=model, + ) + ] + ) return dependency diff --git a/app/router/chat/__init__.py b/app/router/chat/__init__.py index 3ed7897..f9fc0b3 100644 --- a/app/router/chat/__init__.py +++ b/app/router/chat/__init__.py @@ -11,8 +11,15 @@ from fastapi import Query __all__ = ["chat_router"] -@router.get("/notifications") -async def get_notifications(max_id: int | None = Query(None)): +@router.get( + "/notifications", + tags=["通知", "聊天"], + name="获取通知", + description="获取当前用户未读通知。根据 ID 排序。同时返回通知服务器入口。", +) +async def get_notifications( + max_id: int | None = Query(None, description="获取 ID 小于此值的通知"), +): if settings.server_url is not None: notification_endpoint = f"{settings.server_url}notification-server".replace( "http://", "ws://" diff --git a/app/router/chat/channel.py b/app/router/chat/channel.py index 2b42dfe..a514c1d 100644 --- a/app/router/chat/channel.py +++ b/app/router/chat/channel.py @@ -18,7 +18,7 @@ from app.router.v2 import api_v2_router as router from .server import server -from fastapi import Depends, HTTPException, Query, Security +from fastapi import Depends, HTTPException, Path, Query, Security from pydantic import BaseModel, Field, model_validator from redis.asyncio import Redis from sqlmodel import col, select @@ -30,13 +30,23 @@ class UpdateResponse(BaseModel): silences: list[Any] = Field(default_factory=list) -@router.get("/chat/updates", response_model=UpdateResponse) +@router.get( + "/chat/updates", + response_model=UpdateResponse, + name="获取更新", + description="获取当前用户所在频道的最新的禁言情况。", + tags=["聊天"], +) async def get_update( - history_since: int | None = Query(None), - since: int | None = Query(None), + history_since: int | None = Query( + None, description="获取自此禁言 ID 之后的禁言记录" + ), + since: int | None = Query(None, description="获取自此消息 ID 之后的禁言记录"), + includes: list[str] = Query( + ["presence", "silences"], alias="includes[]", description="要包含的更新类型" + ), current_user: User = Security(get_current_user, scopes=["chat.read"]), session: AsyncSession = Depends(get_db), - includes: list[str] = Query(["presence"], alias="includes[]"), redis: Redis = Depends(get_redis), ): resp = UpdateResponse() @@ -83,10 +93,16 @@ async def get_update( return resp -@router.put("/chat/channels/{channel}/users/{user}", response_model=ChatChannelResp) +@router.put( + "/chat/channels/{channel}/users/{user}", + response_model=ChatChannelResp, + name="加入频道", + description="加入指定的公开/房间频道。", + tags=["聊天"], +) async def join_channel( - channel: str, - user: str, + channel: str = Path(..., description="频道 ID/名称"), + user: str = Path(..., description="用户 ID"), current_user: User = Security(get_current_user, scopes=["chat.write_manage"]), session: AsyncSession = Depends(get_db), ): @@ -100,10 +116,13 @@ async def join_channel( @router.delete( "/chat/channels/{channel}/users/{user}", status_code=204, + name="离开频道", + description="将用户移出指定的公开/房间频道。", + tags=["聊天"], ) async def leave_channel( - channel: str, - user: str, + channel: str = Path(..., description="频道 ID/名称"), + user: str = Path(..., description="用户 ID"), current_user: User = Security(get_current_user, scopes=["chat.write_manage"]), session: AsyncSession = Depends(get_db), ): @@ -115,7 +134,13 @@ async def leave_channel( return -@router.get("/chat/channels") +@router.get( + "/chat/channels", + response_model=list[ChatChannelResp], + name="获取频道列表", + description="获取所有公开频道。", + tags=["聊天"], +) async def get_channel_list( current_user: User = Security(get_current_user, scopes=["chat.read"]), session: AsyncSession = Depends(get_db), @@ -148,9 +173,15 @@ class GetChannelResp(BaseModel): users: list[UserResp] = Field(default_factory=list) -@router.get("/chat/channels/{channel}") +@router.get( + "/chat/channels/{channel}", + response_model=GetChannelResp, + name="获取频道信息", + description="获取指定频道的信息。", + tags=["聊天"], +) async def get_channel( - channel: str, + channel: str = Path(..., description="频道 ID/名称"), current_user: User = Security(get_current_user, scopes=["chat.read"]), session: AsyncSession = Depends(get_db), redis: Redis = Depends(get_redis), @@ -211,7 +242,13 @@ class CreateChannelReq(BaseModel): return self -@router.post("/chat/channels") +@router.post( + "/chat/channels", + response_model=ChatChannelResp, + name="创建频道", + description="创建一个新的私聊/通知频道。如果存在私聊频道则重新加入。", + tags=["聊天"], +) async def create_channel( req: CreateChannelReq = Depends(BodyOrForm(CreateChannelReq)), current_user: User = Security(get_current_user, scopes=["chat.write_manage"]), diff --git a/app/router/chat/message.py b/app/router/chat/message.py index 6a4a2e4..84f5c41 100644 --- a/app/router/chat/message.py +++ b/app/router/chat/message.py @@ -19,7 +19,7 @@ from app.router.v2 import api_v2_router as router from .banchobot import bot from .server import server -from fastapi import Depends, HTTPException, Query, Security +from fastapi import Depends, HTTPException, Path, Query, Security from pydantic import BaseModel, Field from redis.asyncio import Redis from sqlmodel import col, select @@ -30,10 +30,18 @@ class KeepAliveResp(BaseModel): silences: list[UserSilenceResp] = Field(default_factory=list) -@router.post("/chat/ack") +@router.post( + "/chat/ack", + name="保持连接", + response_model=KeepAliveResp, + description="保持公共频道的连接。同时返回最近的禁言列表。", + tags=["聊天"], +) async def keep_alive( - history_since: int | None = Query(None), - since: int | None = Query(None), + history_since: int | None = Query( + None, description="获取自此禁言 ID 之后的禁言记录" + ), + since: int | None = Query(None, description="获取自此消息 ID 之后的禁言记录"), current_user: User = Security(get_current_user, scopes=["chat.read"]), session: AsyncSession = Depends(get_db), ): @@ -68,9 +76,15 @@ class MessageReq(BaseModel): uuid: str | None = None -@router.post("/chat/channels/{channel}/messages", response_model=ChatMessageResp) +@router.post( + "/chat/channels/{channel}/messages", + response_model=ChatMessageResp, + name="发送消息", + description="发送消息到指定频道。", + tags=["聊天"], +) async def send_message( - channel: str, + channel: str = Path(..., description="频道 ID/名称"), req: MessageReq = Depends(BodyOrForm(MessageReq)), current_user: User = Security(get_current_user, scopes=["chat.write"]), session: AsyncSession = Depends(get_db), @@ -103,12 +117,18 @@ async def send_message( return resp -@router.get("/chat/channels/{channel}/messages", response_model=list[ChatMessageResp]) +@router.get( + "/chat/channels/{channel}/messages", + response_model=list[ChatMessageResp], + name="获取消息", + description="获取指定频道的消息列表。", + tags=["聊天"], +) async def get_message( channel: str, - limit: int = Query(50, ge=1, le=50), - since: int = Query(default=0, ge=0), - until: int | None = Query(None), + limit: int = Query(50, ge=1, le=50, description="获取消息的数量"), + since: int = Query(default=0, ge=0, description="获取自此消息 ID 之后的消息记录"), + until: int | None = Query(None, description="获取自此消息 ID 之前的消息记录"), current_user: User = Security(get_current_user, scopes=["chat.read"]), session: AsyncSession = Depends(get_db), ): @@ -130,10 +150,16 @@ async def get_message( return resp -@router.put("/chat/channels/{channel}/mark-as-read/{message}", status_code=204) +@router.put( + "/chat/channels/{channel}/mark-as-read/{message}", + status_code=204, + name="标记消息为已读", + description="标记指定消息为已读。", + tags=["聊天"], +) async def mark_as_read( - channel: str, - message: int, + channel: str = Path(..., description="频道 ID/名称"), + message: int = Path(..., description="消息 ID"), current_user: User = Security(get_current_user, scopes=["chat.read"]), session: AsyncSession = Depends(get_db), ): @@ -157,7 +183,12 @@ class NewPMResp(BaseModel): new_channel_id: int -@router.post("/chat/new") +@router.post( + "/chat/new", + name="创建私聊频道", + description="创建一个新的私聊频道。", + tags=["聊天"], +) async def create_new_pm( req: PMReq = Depends(BodyOrForm(PMReq)), current_user: User = Security(get_current_user, scopes=["chat.write"]),