diff --git a/backend/app/api/v1/users.py b/backend/app/api/v1/users.py index ea19aadd..1184f67d 100644 --- a/backend/app/api/v1/users.py +++ b/backend/app/api/v1/users.py @@ -4,8 +4,10 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import get_db_session, require_roles +from app.core.security import verify_password from app.schemas.common import PaginatedResponse from app.crud import user as user_crud +from app.crud import member as member_crud from app.utils.pagination import paginate from app.schemas.user import UserCreate, UserRead, UserUpdate @@ -52,3 +54,27 @@ async def update_user( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") user = await user_crud.update_user(db, db_user, user_in) return user + + +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_user( + user_id: uuid.UUID, + payload: dict, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(require_roles(["ADMIN"])), +): + admin_password = payload.get("admin_password") + if not admin_password or not verify_password(admin_password, current_user.hashed_password): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="管理员密码错误") + db_user = await user_crud.get_by_id(db, user_id) + if not db_user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + if db_user.id == current_user.id: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="不允许删除自己") + has_membership = await member_crud.user_has_memberships(db, db_user.id) + if has_membership: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="该账号仍在项目成员中,请先在项目成员配置中移除后再删除", + ) + await user_crud.delete_user(db, db_user) diff --git a/backend/app/crud/member.py b/backend/app/crud/member.py index 24db02a6..b3bf611a 100644 --- a/backend/app/crud/member.py +++ b/backend/app/crud/member.py @@ -62,3 +62,8 @@ async def remove_member(db: AsyncSession, member: StudyMember) -> StudyMember: await db.commit() await db.refresh(member) return member + + +async def user_has_memberships(db: AsyncSession, user_id: uuid.UUID) -> bool: + result = await db.execute(select(StudyMember.id).where(StudyMember.user_id == user_id)) + return result.first() is not None diff --git a/backend/app/crud/user.py b/backend/app/crud/user.py index 673851d3..4d2397b5 100644 --- a/backend/app/crud/user.py +++ b/backend/app/crud/user.py @@ -1,12 +1,13 @@ import uuid from typing import Sequence -from sqlalchemy import select, update +from sqlalchemy import select, update, delete from sqlalchemy.ext.asyncio import AsyncSession from app.core.security import hash_password from app.models.user import User from app.schemas.user import UserCreate, UserUpdate +from app.models.study_member import StudyMember async def get_by_username(db: AsyncSession, username: str) -> User | None: @@ -70,3 +71,9 @@ async def ensure_admin_exists(db: AsyncSession, *, default_password: str = "admi ) db.add(new_admin) await db.commit() + + +async def delete_user(db: AsyncSession, user: User) -> None: + await db.execute(delete(StudyMember).where(StudyMember.user_id == user.id)) + await db.delete(user) + await db.commit() diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts index 8cd02e9c..91806782 100644 --- a/frontend/src/api/users.ts +++ b/frontend/src/api/users.ts @@ -1,5 +1,6 @@ import { apiGet, apiPatch, apiPost } from "./axios"; import type { ApiListResponse, UserInfo } from "../types/api"; +import { apiDelete } from "./axios"; export const fetchUsers = (params?: Record) => apiGet>("/api/v1/users", { params }); @@ -9,3 +10,6 @@ export const createUser = (payload: { username: string; password: string; role: export const updateUser = (userId: string, payload: Partial<{ role: string; is_active: boolean; password: string }>) => apiPatch(`/api/v1/users/${userId}`, payload); + +export const deleteUser = (userId: string, payload: { admin_password: string }) => + apiDelete(`/api/v1/users/${userId}`, { data: payload }); diff --git a/frontend/src/views/admin/ProjectMembers.vue b/frontend/src/views/admin/ProjectMembers.vue index e80f535f..4c2f2dd1 100644 --- a/frontend/src/views/admin/ProjectMembers.vue +++ b/frontend/src/views/admin/ProjectMembers.vue @@ -23,7 +23,7 @@ - +