166 lines
5.6 KiB
Python
166 lines
5.6 KiB
Python
"""routers/admin.py —— 管理后台端点(登录/用户管理/日志查看)。"""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, Query, Request
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
|
|
from ..auth import require_admin
|
|
from ..config import get_settings
|
|
from ..db import Database, get_db
|
|
from ..logwriter import log_event
|
|
from ..services.auth import (
|
|
AUTHORIZATION_UNITS,
|
|
verify_login,
|
|
issue_token,
|
|
revoke_token,
|
|
list_users,
|
|
get_user_by_username,
|
|
create_user,
|
|
update_user,
|
|
delete_user,
|
|
)
|
|
from ..services.logview import query_logs
|
|
from ..services.settings import get_hub_settings, set_hub_settings
|
|
from ..upstream import envelope
|
|
|
|
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
|
|
|
|
|
class LoginBody(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
username: str
|
|
password: str
|
|
role: str = "user"
|
|
authorizations: list[str] = []
|
|
|
|
|
|
class UserPatch(BaseModel):
|
|
role: str | None = None
|
|
authorizations: list[str] | None = None
|
|
password: str | None = None
|
|
|
|
|
|
class HubSettingsBody(BaseModel):
|
|
hub_id: str | None = None
|
|
hub_name: str | None = None
|
|
|
|
|
|
@router.post("/login")
|
|
async def login(body: LoginBody, db: Database = Depends(get_db)):
|
|
user = await verify_login(db, body.username, body.password)
|
|
if user is None:
|
|
log_event("admin_login_fail", detail={"username": body.username})
|
|
content, status = envelope("UNAUTHORIZED", "用户名或密码错误", status=401)
|
|
return JSONResponse(status_code=status, content=content)
|
|
if user["role"] != "admin":
|
|
log_event("admin_login_fail", detail={"username": body.username, "reason": "not_admin"})
|
|
content, status = envelope("FORBIDDEN", "非管理员账号", status=403)
|
|
return JSONResponse(status_code=status, content=content)
|
|
token = await issue_token(db, user["id"])
|
|
log_event("admin_login_ok", user_id=user["id"], detail={"username": body.username})
|
|
return {"ok": True, "token": token, "user": user}
|
|
|
|
|
|
@router.post("/logout")
|
|
async def logout(request: Request, db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
header = request.headers.get("Authorization", "")
|
|
token = header[7:] if header.startswith("Bearer ") else ""
|
|
if token:
|
|
await revoke_token(db, token)
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/me")
|
|
async def me(_user: dict = Depends(require_admin)):
|
|
return {"ok": True, "user": _user}
|
|
|
|
|
|
@router.get("/units")
|
|
async def units(_user: dict = Depends(require_admin)):
|
|
return {"ok": True, "units": list(AUTHORIZATION_UNITS)}
|
|
|
|
|
|
@router.get("/settings")
|
|
async def get_settings_api(db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
return {"ok": True, "settings": await get_hub_settings(db)}
|
|
|
|
|
|
@router.put("/settings")
|
|
async def put_settings_api(body: HubSettingsBody, db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
return {"ok": True, "settings": await set_hub_settings(db, body.hub_id, body.hub_name)}
|
|
|
|
|
|
@router.get("/users")
|
|
async def users(db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
return {"ok": True, "users": await list_users(db)}
|
|
|
|
|
|
@router.post("/users")
|
|
async def create_user_api(body: UserCreate, db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
username = body.username.strip()
|
|
if not username:
|
|
content, _ = envelope("VALIDATION", "用户名不能为空", status=422)
|
|
return JSONResponse(status_code=422, content=content)
|
|
if not body.password:
|
|
content, _ = envelope("VALIDATION", "密码不能为空", status=422)
|
|
return JSONResponse(status_code=422, content=content)
|
|
if await get_user_by_username(db, username):
|
|
return JSONResponse(
|
|
status_code=409,
|
|
content={"ok": False, "error": {"code": "CONFLICT", "message": "用户名已存在", "degraded": False}},
|
|
)
|
|
user = await create_user(db, username, body.password, body.role, body.authorizations)
|
|
return {"ok": True, "user": user}
|
|
|
|
|
|
@router.patch("/users/{user_id}")
|
|
async def patch_user(user_id: str, body: UserPatch, db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
user = await update_user(db, user_id, role=body.role, authorizations=body.authorizations, password=body.password)
|
|
if user is None:
|
|
content, _ = envelope("NOT_FOUND", status=404)
|
|
return JSONResponse(status_code=404, content=content)
|
|
return {"ok": True, "user": user}
|
|
|
|
|
|
@router.delete("/users/{user_id}")
|
|
async def delete_user_api(user_id: str, db: Database = Depends(get_db), _user: dict = Depends(require_admin)):
|
|
if user_id == _user["id"]:
|
|
return JSONResponse(
|
|
status_code=422,
|
|
content={"ok": False, "error": {"code": "VALIDATION", "message": "不能删除当前登录账号", "degraded": False}},
|
|
)
|
|
await delete_user(db, user_id)
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/logs")
|
|
async def logs(
|
|
from_date: str | None = Query(None),
|
|
to_date: str | None = Query(None),
|
|
event: str | None = Query(None),
|
|
user_id: str | None = Query(None),
|
|
q: str | None = Query(None),
|
|
status: str | None = Query(None),
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(50, ge=1, le=200),
|
|
_user: dict = Depends(require_admin),
|
|
):
|
|
settings = get_settings()
|
|
result = await query_logs(
|
|
settings.log_dir,
|
|
from_date=from_date,
|
|
to_date=to_date,
|
|
event=event,
|
|
user_id=user_id,
|
|
q=q,
|
|
status=status,
|
|
page=page,
|
|
page_size=page_size,
|
|
)
|
|
return {"ok": True, **result}
|