Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
456 changes: 244 additions & 212 deletions APIDocument.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class YourModel(Base):

```bash
uv run python -m grpc_tools.protoc \
-I. \
-Iapi/ \
--python_out=app/grpc \
--grpc_python_out=app/grpc \
--pyi_out=app/grpc \
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ uv run cims

```bash
uv run python -m grpc_tools.protoc \
-I. \
-Iapi/ \
--python_out=app/grpc \
--grpc_python_out=app/grpc \
--pyi_out=app/grpc \
Expand Down
137 changes: 0 additions & 137 deletions NewAPI.md

This file was deleted.

2 changes: 1 addition & 1 deletion app/api/admin/approval_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
_sa = require_role(100)


@router.post("/list", response_model=list[UserOut])
@router.get("/list", response_model=list[UserOut])
async def get_pending_users(
offset: int = 0,
limit: int = 50,
Expand Down
9 changes: 5 additions & 4 deletions app/api/admin/settings.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
"""系统设置路由。

提供平台级系统设置的读取和修改。
仅允许白名单内的配置项写入。
"""

from datetime import datetime, timezone

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.auth.dependencies import require_role
from app.models.session import get_db
from app.models.system_config import SystemConfig
from app.api.schemas.settings import SettingsUpdate

router = APIRouter()
_sa = require_role(100)
Expand All @@ -30,13 +31,13 @@ async def get_settings(

@router.post("")
async def update_settings(
body: dict,
body: SettingsUpdate,
db: AsyncSession = Depends(get_db),
_user=Depends(_sa),
):
"""修改系统设置(键值对)。"""
"""修改系统设置(仅白名单键)。"""
now = datetime.now(timezone.utc)
for key, value in body.items():
for key, value in body.items.items():
stmt = select(SystemConfig).where(SystemConfig.key == key)
cfg = (await db.execute(stmt)).scalar_one_or_none()
if cfg:
Expand Down
2 changes: 1 addition & 1 deletion app/api/admin/totp_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

from fastapi import APIRouter

router = APIRouter(prefix="/auth/2fa", tags=["2fa"])
router = APIRouter(tags=["2fa"])
26 changes: 25 additions & 1 deletion app/api/admin/user_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
_sa = require_role(100)


@router.post("/{user_id}/delete")
class RenameRequest(BaseModel):
"""重命名请求体。"""
name: str = Field(..., min_length=1, max_length=64)


@router.delete("/{user_id}")
async def delete_user(
user_id: str,
db: AsyncSession = Depends(get_db),
Expand All @@ -28,3 +33,22 @@ async def delete_user(
await db.delete(user)
await db.commit()
return {"message": "用户已删除"}


@router.post("/{user_id}/rename")
async def rename_user(
user_id: str,
body: RenameRequest,
db: AsyncSession = Depends(get_db),
_user=Depends(_sa),
):
"""重命名用户。"""
user = (
await db.execute(select(User).where(User.id == user_id))
).scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
user.username = body.name
await db.commit()
return {"message": "已重命名"}

20 changes: 20 additions & 0 deletions app/api/admin/user_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,23 @@ async def reset_password(
user.hashed_password = hash_password(body.new_password)
await db.commit()
return {"message": "密码已重置"}


@router.post("/{user_id}/password/change")
async def change_password(
user_id: str,
body: PasswordChange,
db: AsyncSession = Depends(get_db),
_user=Depends(_sa),
):
"""超管修改用户密码(需验证旧密码)。"""
user = (
await db.execute(select(User).where(User.id == user_id))
).scalar_one_or_none()
if not user:
raise HTTPException(404, "用户不存在")
if not verify_password(body.old_password, user.hashed_password):
raise HTTPException(400, "旧密码错误")
user.hashed_password = hash_password(body.new_password)
await db.commit()
return {"message": "密码已修改"}
4 changes: 2 additions & 2 deletions app/api/admin/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
_sa = require_role(100)


@router.post("/list", response_model=list[UserOut])
@router.get("/list", response_model=list[UserOut])
async def list_all_users(
offset: int = 0,
limit: int = 20,
Expand All @@ -29,7 +29,7 @@ async def list_all_users(
return [_to_out(u) for u in users]


@router.post("/search", response_model=list[UserOut])
@router.get("/search", response_model=list[UserOut])
async def search_users(
q: str = "",
db: AsyncSession = Depends(get_db),
Expand Down
8 changes: 4 additions & 4 deletions app/api/command/client_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""远程配置查询路由。

通过下发 gRPC GetClientConfig 指令,异步请求客户端回传其运行时内存配置。
按 NewAPI.md: GET /{client_id}/command/get-config
"""

import uuid
Expand All @@ -13,8 +13,8 @@
router = APIRouter()


@router.get("/client/{uid}/get_config")
async def fetch_runtime_config(uid: str, config_type: int, request: Request):
@router.get("/{client_id}/command/get-config")
async def fetch_runtime_config(client_id: str, config_type: int, request: Request):
"""请求终端上报其当前运行的配置快照。"""
servicer = getattr(request.app.state, "command_servicer", None)
if not servicer:
Expand All @@ -31,7 +31,7 @@ async def fetch_runtime_config(uid: str, config_type: int, request: Request):
Payload=config_req.SerializeToString(),
)

await servicer.send_command(get_tenant_id(), uid, cmd)
await servicer.send_command(get_tenant_id(), client_id, cmd)
return {
"status": "success",
"message": f"请求已下发,ID: {req_id}",
Expand Down
15 changes: 8 additions & 7 deletions app/api/command/client_control.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""客户端远程控制。

通过 gRPC 长连接向客户端下发实时控制指令,如应用重启或数据同步心跳。
通过 gRPC 长连接向客户端下发实时控制指令。
按 NewAPI.md: POST /{client_id}/command/restart, POST /{client_id}/command/update-data
"""

from fastapi import APIRouter, Request
Expand All @@ -25,13 +26,13 @@ async def _push_cmd(request: Request, uid: str, cmd_type: int) -> StatusResponse
return StatusResponse(status="success", message="指令已下发")


@router.get("/client/{uid}/restart", response_model=StatusResponse)
async def restart_app(uid: str, request: Request):
@router.post("/{client_id}/command/restart", response_model=StatusResponse)
async def restart_app(client_id: str, request: Request):
"""要求指定客户端重新启动应用。"""
return await _push_cmd(request, uid, CommandTypes_pb2.RestartApp)
return await _push_cmd(request, client_id, CommandTypes_pb2.RestartApp)


@router.get("/client/{uid}/update_data", response_model=StatusResponse)
async def force_sync(uid: str, request: Request):
@router.post("/{client_id}/command/update-data", response_model=StatusResponse)
async def force_sync(client_id: str, request: Request):
"""触发客户端立即拉取并刷新最新配置数据。"""
return await _push_cmd(request, uid, CommandTypes_pb2.DataUpdated)
return await _push_cmd(request, client_id, CommandTypes_pb2.DataUpdated)
9 changes: 4 additions & 5 deletions app/api/command/client_notification.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""实时通知下发服务。

支持向客户端发送带图标、TTS 语音以及紧急属性的即时通知弹窗。
按 NewAPI.md: POST /{client_id}/command/send-notification
"""

from fastapi import APIRouter, Request, Body
Expand All @@ -14,16 +14,15 @@
router = APIRouter()


@router.post("/client/{uid}/send_notification", response_model=StatusResponse)
@router.post("/{client_id}/command/send-notification", response_model=StatusResponse)
async def push_notify(
uid: str, request: Request, payload: NotificationPayload = Body(...)
client_id: str, request: Request, payload: NotificationPayload = Body(...)
):
"""下发格式化的桌面通知。"""
servicer = getattr(request.app.state, "command_servicer", None)
if not servicer:
return StatusResponse(status="error", message="gRPC 通道未开启")

# 构建 Protobuf 结构
notify = SendNotification_pb2.SendNotification(**payload.model_dump())

cmd = ClientCommandDeliverScRsp_pb2.ClientCommandDeliverScRsp(
Expand All @@ -32,5 +31,5 @@ async def push_notify(
Payload=notify.SerializeToString(),
)

await servicer.send_command(get_tenant_id(), uid, cmd)
await servicer.send_command(get_tenant_id(), client_id, cmd)
return StatusResponse(status="success", message="通知已递送")
Loading
Loading