from fastapi import APIRouter, Depends, Query, Request from pydantic import BaseModel from sqlalchemy.orm import Session from app.core.deps import require_staff from app.db import get_db from app.services import rbac as rbac_service public_router = APIRouter() router = APIRouter(dependencies=[Depends(require_staff)]) class LoginIn(BaseModel): username: str password: str captchaId: str = "" captchaCode: str = "" class OwnPasswordIn(BaseModel): oldPassword: str newPassword: str class UserCreateIn(BaseModel): username: str password: str displayName: str mobile: str | None = None roleIds: list[int] = [] class UserUpdateIn(BaseModel): displayName: str mobile: str | None = None status: str roleIds: list[int] = [] class ResetPasswordIn(BaseModel): password: str | None = None newPassword: str | None = None class RoleCreateIn(BaseModel): code: str name: str description: str | None = None class RoleUpdateIn(BaseModel): name: str description: str | None = None status: str class RoleResourcesIn(BaseModel): resourceIds: list[int] = [] class ResourceIn(BaseModel): parentId: int | None = None type: str name: str title: str path: str | None = "" component: str | None = "" icon: str | None = "" permission: str | None = "" sort: int = 0 visible: bool = True status: str = "active" def _actor_id(principal: dict = Depends(require_staff)) -> int: try: return int(principal["sub"]) except (TypeError, ValueError): from fastapi import HTTPException raise HTTPException(status_code=401, detail="登录已失效") def _resource_payload(body: ResourceIn) -> dict: return { "parent_id": body.parentId, "type": body.type, "name": body.name, "title": body.title, "path": body.path, "component": body.component, "icon": body.icon, "permission": body.permission, "sort": body.sort, "visible": body.visible, "status": body.status, } @public_router.post("/auth/login") def login(body: LoginIn, request: Request, db: Session = Depends(get_db)) -> dict: request.state.audit_username = (body.username or "").strip()[:64] return rbac_service.authenticate(db, body.username, body.password, body.captchaId, body.captchaCode) @router.post("/auth/password") def change_password( body: OwnPasswordIn, actor_id: int = Depends(_actor_id), db: Session = Depends(get_db), ) -> dict: return rbac_service.change_own_password(db, actor_id, body.oldPassword, body.newPassword) @router.get("/users") def list_users( current: int = Query(1, ge=1), size: int = Query(10, ge=1, le=200), username: str | None = None, displayName: str | None = None, status: str | None = None, db: Session = Depends(get_db), ) -> dict: return rbac_service.query_users( db, current=current, size=size, username=username, display_name=displayName, status_value=status, ) @router.post("/users") def create_user(body: UserCreateIn, db: Session = Depends(get_db)) -> dict: return rbac_service.create_user( db, username=body.username, password=body.password, display_name=body.displayName, mobile=body.mobile, role_ids=body.roleIds, ) @router.put("/users/{user_id}") def update_user( user_id: int, body: UserUpdateIn, actor_id: int = Depends(_actor_id), db: Session = Depends(get_db), ) -> dict: return rbac_service.update_user( db, user_id, actor_id, display_name=body.displayName, status_value=body.status, role_ids=body.roleIds, mobile=body.mobile, mobile_provided="mobile" in body.model_fields_set, ) @router.post("/users/{user_id}/password") def reset_password(user_id: int, body: ResetPasswordIn, db: Session = Depends(get_db)) -> dict: plain = body.password if body.password is not None else body.newPassword if plain is None: from fastapi import HTTPException raise HTTPException(status_code=400, detail="请填写密码") return rbac_service.reset_user_password(db, user_id, plain) @router.get("/roles") def list_roles( current: int = Query(1, ge=1), size: int = Query(10, ge=1, le=200), name: str | None = None, code: str | None = None, status: str | None = None, db: Session = Depends(get_db), ) -> dict: return rbac_service.query_roles( db, current=current, size=size, name=name, code=code, status_value=status, ) @router.post("/roles") def create_role(body: RoleCreateIn, db: Session = Depends(get_db)) -> dict: return rbac_service.create_role(db, code=body.code, name=body.name, description=body.description) @router.put("/roles/{role_id}") def update_role(role_id: int, body: RoleUpdateIn, db: Session = Depends(get_db)) -> dict: return rbac_service.update_role( db, role_id, name=body.name, description=body.description, status_value=body.status, ) @router.put("/roles/{role_id}/resources") def replace_role_resources(role_id: int, body: RoleResourcesIn, db: Session = Depends(get_db)) -> dict: return rbac_service.replace_role_resources(db, role_id, body.resourceIds) @router.get("/resources") def list_resources(db: Session = Depends(get_db)) -> list[dict]: return rbac_service.list_resources(db) @router.post("/resources") def create_resource(body: ResourceIn, db: Session = Depends(get_db)) -> dict: return rbac_service.create_resource(db, _resource_payload(body)) @router.put("/resources/{resource_id}") def update_resource(resource_id: int, body: ResourceIn, db: Session = Depends(get_db)) -> dict: return rbac_service.update_resource(db, resource_id, _resource_payload(body)) @router.delete("/resources/{resource_id}") def delete_resource(resource_id: int, db: Session = Depends(get_db)) -> dict: return rbac_service.delete_resource(db, resource_id)