""" 企业后台管理 API(需 role=admin,与主站共用 JWT:/api/auth/login) """ import asyncpg from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from admin.schemas import ( AdminUserCreate, AdminUserListItem, AdminUserListResponse, AdminUserUpdate, AuditLogItem, AuditLogListResponse, DepartmentCreate, DepartmentResponse, DepartmentSetLeader, DepartmentUpdate, EnterpriseResponse, EnterpriseUpdate, UserPermissionUpdate, ) from core.dependencies import get_db, get_current_admin_user from models.user import User from services.admin_user_service import AdminUserService from services.audit_service import AuditService from services.department_service import DepartmentService from services.enterprise_service import EnterpriseService from utils.helpers import BaseResponse admin_router = APIRouter(prefix="/api/admin", tags=["后台管理"]) @admin_router.get("/enterprise", response_model=BaseResponse, summary="当前企业信息") async def get_enterprise( admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): if admin.enterprise_id is None: raise HTTPException(status_code=400, detail="用户未关联企业") row = await EnterpriseService.get_by_id(conn, admin.enterprise_id) if not row: raise HTTPException(status_code=404, detail="企业不存在") return BaseResponse( code=200, msg="ok", data=EnterpriseResponse(**row).model_dump(), ) @admin_router.put("/enterprise", response_model=BaseResponse, summary="更新企业信息") async def update_enterprise( body: EnterpriseUpdate, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): if admin.enterprise_id is None: raise HTTPException(status_code=400, detail="用户未关联企业") row = await EnterpriseService.update_profile( conn, admin.enterprise_id, name=body.name, ai_display_name=body.ai_display_name, ) if not row: raise HTTPException(status_code=404, detail="企业不存在") return BaseResponse(code=200, msg="更新成功", data=EnterpriseResponse(**row).model_dump()) @admin_router.get("/departments", response_model=BaseResponse, summary="部门列表") async def list_departments( admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): rows = await DepartmentService.list_by_enterprise(conn, admin.enterprise_id) items = [ DepartmentResponse( id=r["id"], enterprise_id=r["enterprise_id"], name=r["name"], parent_id=r["parent_id"], leader_user_id=r.get("leader_user_id"), leader_name=r.get("leader_name"), created_at=r["created_at"], ).model_dump() for r in rows ] return BaseResponse(code=200, msg="ok", data={"items": items}) @admin_router.post("/departments", response_model=BaseResponse, summary="创建部门") async def create_department( body: DepartmentCreate, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): if body.parent_id is not None: parent = await DepartmentService.get_by_id(conn, body.parent_id, admin.enterprise_id) if not parent: raise HTTPException(status_code=400, detail="上级部门不存在") try: row = await DepartmentService.create( conn, admin.enterprise_id, body.name, body.parent_id ) return BaseResponse( code=200, msg="创建成功", data=DepartmentResponse( id=row["id"], enterprise_id=row["enterprise_id"], name=row["name"], parent_id=row["parent_id"], leader_user_id=None, created_at=row["created_at"], ).model_dump(), ) except asyncpg.UniqueViolationError: raise HTTPException(status_code=400, detail="同企业下部门名称已存在") @admin_router.put("/departments/{dept_id}", response_model=BaseResponse, summary="更新部门") async def update_department( dept_id: int, body: DepartmentUpdate, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): if body.parent_id is not None: parent = await DepartmentService.get_by_id(conn, body.parent_id, admin.enterprise_id) if not parent: raise HTTPException(status_code=400, detail="上级部门不存在") row = await DepartmentService.update( conn, dept_id, admin.enterprise_id, name=body.name, parent_id=body.parent_id ) if not row: raise HTTPException(status_code=404, detail="部门不存在") return BaseResponse( code=200, msg="更新成功", data=DepartmentResponse( id=row["id"], enterprise_id=row["enterprise_id"], name=row["name"], parent_id=row["parent_id"], leader_user_id=row.get("leader_user_id"), created_at=row["created_at"], ).model_dump(), ) @admin_router.delete("/departments/{dept_id}", response_model=BaseResponse, summary="删除部门") async def delete_department( dept_id: int, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): err = await DepartmentService.delete(conn, dept_id, admin.enterprise_id) if err: raise HTTPException(status_code=400, detail=err) return BaseResponse(code=200, msg="删除成功", data=None) @admin_router.get("/users", response_model=BaseResponse, summary="用户列表") async def list_users( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), username: Optional[str] = Query(None, description="用户名(模糊)"), email: Optional[str] = Query(None, description="邮箱(模糊)"), phone: Optional[str] = Query(None, description="手机号(模糊)"), display_name: Optional[str] = Query(None, description="显示名(模糊)"), department_id: Optional[int] = Query(None, description="按部门 ID 精确筛选"), admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): if department_id is not None: d = await DepartmentService.get_by_id(conn, department_id, admin.enterprise_id) if not d: raise HTTPException(status_code=400, detail="部门不存在") rows, total = await AdminUserService.list_users( conn, admin.enterprise_id, page, page_size, username=username, email=email, phone=phone, display_name=display_name, department_id=department_id, ) items = [AdminUserListItem(**r).model_dump() for r in rows] return BaseResponse( code=200, msg="ok", data=AdminUserListResponse(total=total, items=items).model_dump(), ) @admin_router.post("/users", response_model=BaseResponse, summary="创建用户") async def create_user( body: AdminUserCreate, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): if body.department_id is not None: d = await DepartmentService.get_by_id(conn, body.department_id, admin.enterprise_id) if not d: raise HTTPException(status_code=400, detail="部门不存在") try: row = await AdminUserService.create_user(conn, admin.enterprise_id, body) return BaseResponse( code=200, msg="创建成功", data=AdminUserListItem(**row).model_dump(), ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @admin_router.get("/users/{user_id}", response_model=BaseResponse, summary="用户详情") async def get_user( user_id: int, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): row = await AdminUserService.get_user(conn, admin.enterprise_id, user_id) if not row: raise HTTPException(status_code=404, detail="用户不存在") return BaseResponse( code=200, msg="ok", data=AdminUserListItem(**row).model_dump(), ) @admin_router.put("/users/{user_id}", response_model=BaseResponse, summary="更新用户") async def update_user( user_id: int, body: AdminUserUpdate, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): unset = body.model_dump(exclude_unset=True) if "department_id" in unset and unset["department_id"] is not None: d = await DepartmentService.get_by_id(conn, unset["department_id"], admin.enterprise_id) if not d: raise HTTPException(status_code=400, detail="部门不存在") try: row = await AdminUserService.update_user(conn, admin, user_id, body) if not row: raise HTTPException(status_code=404, detail="用户不存在") return BaseResponse( code=200, msg="更新成功", data=AdminUserListItem(**row).model_dump(), ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @admin_router.delete("/users/{user_id}", response_model=BaseResponse, summary="删除用户") async def delete_user( user_id: int, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): try: ok = await AdminUserService.delete_user(conn, admin, user_id) if not ok: raise HTTPException(status_code=404, detail="用户不存在") return BaseResponse(code=200, msg="删除成功", data=None) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except asyncpg.ForeignKeyViolationError: raise HTTPException( status_code=400, detail="该用户仍存在关联数据(如会话、知识库归属等),无法直接删除,请先禁用账号", ) # ──────────────────────────────────────────────────────────── # 部门负责人绑定(admin 专用) # ──────────────────────────────────────────────────────────── @admin_router.put( "/departments/{dept_id}/leader", response_model=BaseResponse, summary="设置/清除部门负责人", ) async def set_department_leader( dept_id: int, body: DepartmentSetLeader, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): # 若指定了 user_id,确认该用户属于本企业且是 leader if body.leader_user_id is not None: u = await conn.fetchrow( "SELECT id, role, enterprise_id FROM user_list WHERE id = $1 AND enterprise_id = $2", body.leader_user_id, admin.enterprise_id, ) if not u: raise HTTPException(status_code=400, detail="用户不存在或不属于本企业") if u["role"] not in ("leader", "admin"): raise HTTPException(status_code=400, detail="只能将 role=leader 或 admin 的用户设为负责人") row = await DepartmentService.set_leader(conn, dept_id, admin.enterprise_id, body.leader_user_id) if not row: raise HTTPException(status_code=404, detail="部门不存在") return BaseResponse(code=200, msg="设置成功", data=DepartmentResponse( id=row["id"], enterprise_id=row["enterprise_id"], name=row["name"], parent_id=row["parent_id"], leader_user_id=row.get("leader_user_id"), ).model_dump()) # ──────────────────────────────────────────────────────────── # 用户上传权限开关(admin 可设置任意用户) # ──────────────────────────────────────────────────────────── @admin_router.patch( "/users/{user_id}/permissions", response_model=BaseResponse, summary="设置用户知识库上传权限", ) async def set_user_permission( user_id: int, body: UserPermissionUpdate, admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): target = await conn.fetchrow( "SELECT id FROM user_list WHERE id = $1 AND enterprise_id = $2", user_id, admin.enterprise_id, ) if not target: raise HTTPException(status_code=404, detail="用户不存在") await conn.execute( "UPDATE user_list SET allow_kb_upload = $1 WHERE id = $2", body.allow_kb_upload, user_id, ) await AuditService.write( conn, enterprise_id=admin.enterprise_id or 0, actor_id=admin.id, action="permission_change", target_user_id=user_id, metadata={"field": "allow_kb_upload", "new": body.allow_kb_upload, "by": "admin"}, ) return BaseResponse(code=200, msg="权限已更新", data={"allow_kb_upload": body.allow_kb_upload}) # ──────────────────────────────────────────────────────────── # 全企业审计日志(admin 专用) # ──────────────────────────────────────────────────────────── @admin_router.get( "/audit-logs", response_model=BaseResponse, summary="查看全企业知识库操作日志", ) async def list_audit_logs( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), action: Optional[str] = Query(None, description="操作类型过滤"), actor_id: Optional[int] = Query(None, description="操作者 user_id"), kb_id: Optional[int] = Query(None, description="知识库 ID"), admin: User = Depends(get_current_admin_user), conn: asyncpg.Connection = Depends(get_db), ): rows, total = await AuditService.list_logs( conn, admin.enterprise_id, department_ids=None, # admin 查全量 actor_id=actor_id, kb_id=kb_id, action=action, page=page, page_size=page_size, ) items = [AuditLogItem(**r).model_dump() for r in rows] return BaseResponse( code=200, msg="ok", data=AuditLogListResponse(total=total, items=items).model_dump(), )