feat(admin): implement user deletion functionality in admin dashboard

This commit is contained in:
2026-06-12 22:05:20 +08:00
parent 0fde3e9490
commit bb83cd2970
5 changed files with 64 additions and 3 deletions
+13
View File
@@ -233,6 +233,19 @@ async def update_user(
return user
async def delete_user(
user_id: uuid.UUID,
session: AsyncSession,
) -> bool:
"""Delete a user by their UUID. Returns ``True`` if deleted, ``False`` if not found."""
user = await session.get(User, user_id)
if user is None:
return False
await session.delete(user)
await session.flush()
return True
# ── Authentication (Valkey tokens + bcrypt) ───────────────────────────────
_AUTH_TOKEN_KEY = "auth_token:{token}"
+13
View File
@@ -13,6 +13,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.core.database import (
delete_session,
delete_user as db_delete_user,
get_admin_stats,
get_session_messages,
get_session_user_id,
@@ -171,6 +172,18 @@ async def admin_update_user(
return _user_to_admin_out(user, session_count=len(sids))
@router.delete("/users/{user_id}", status_code=204)
async def admin_delete_user(
user_id: uuid.UUID,
_admin_id: str = Depends(require_admin),
session: AsyncSession = Depends(get_db_session),
) -> None:
"""Permanently delete a user and all their data."""
deleted = await db_delete_user(user_id, session=session)
if not deleted:
raise HTTPException(status_code=404, detail="User not found")
@router.get("/sessions", response_model=list[AdminSessionOut])
async def admin_list_sessions(
_admin_id: str = Depends(require_admin),
+2 -2
View File
@@ -143,8 +143,8 @@ async def me_endpoint(
Requires ``Authorization: Bearer <token>`` header.
"""
from backend.core.database import get_session as _get_db
from backend.core.database import get_user
from backend.core.database import open_session as _open_db
user_id = await resolve_auth_token(token)
if user_id is None:
@@ -153,7 +153,7 @@ async def me_endpoint(
detail="Invalid or expired token",
)
async with _get_db() as session:
async with _open_db() as session:
user = await get_user(uuid_obj(user_id), session=session)
if user is None or not user.is_active:
+25 -1
View File
@@ -5,6 +5,7 @@ import {
adminUpdateUser,
adminListSessions,
adminDeleteSession,
adminDeleteUser,
type AdminStats,
type AdminUserData,
type AdminSessionData,
@@ -82,6 +83,16 @@ export default function AdminDashboard({ onBack }: AdminProps) {
}
};
const handleDeleteUser = async (userId: string) => {
if (!confirm("Permanently delete this user and all their data?")) return;
try {
await adminDeleteUser(userId);
await loadAll();
} catch (err: unknown) {
alert(err instanceof Error ? err.message : "Delete failed");
}
};
const handleDeleteSession = async (sessionId: string) => {
if (!confirm("Delete this session and all its messages?")) return;
try {
@@ -172,6 +183,7 @@ export default function AdminDashboard({ onBack }: AdminProps) {
users={users}
onRoleChange={handleRoleChange}
onToggleActive={handleToggleActive}
onDeleteUser={handleDeleteUser}
/>
) : (
<SessionsTab sessions={sessions} onDelete={handleDeleteSession} />
@@ -307,10 +319,12 @@ function UsersTab({
users,
onRoleChange,
onToggleActive,
onDeleteUser,
}: {
users: AdminUserData[];
onRoleChange: (id: string, role: string) => void;
onToggleActive: (id: string, current: boolean) => void;
onDeleteUser: (id: string) => void;
}) {
return (
<div className="overflow-x-auto">
@@ -323,6 +337,7 @@ function UsersTab({
<th className="pb-3 pr-4">Status</th>
<th className="pb-3 pr-4">Sessions</th>
<th className="pb-3 pr-4">Created</th>
<th className="pb-3 pr-2" />
<th className="pb-3" />
</tr>
</thead>
@@ -366,13 +381,22 @@ function UsersTab({
<td className="py-3 pr-4 text-xs text-gray-500">
{new Date(u.created_at).toLocaleDateString()}
</td>
<td className="py-3">
<td className="py-3 pr-2">
<span
className={`inline-block h-2 w-2 rounded-full ${
ROLE_COLORS[u.role]?.replace("text-", "bg-") ?? "bg-gray-500"
}`}
/>
</td>
<td className="py-3">
<button
onClick={() => onDeleteUser(u.id)}
className="rounded-lg px-2 py-1 text-xs text-red-400 transition hover:bg-red-900/30"
title="Delete user"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
+11
View File
@@ -294,6 +294,17 @@ export async function adminListSessions(): Promise<AdminSessionData[]> {
return res.json();
}
export async function adminDeleteUser(userId: string): Promise<void> {
const res = await fetch(`${API_BASE}/admin/users/${userId}`, {
method: "DELETE",
headers: { ...authHeaders() },
});
if (!res.ok) {
const body = await res.json().catch(() => null);
throw new Error(body?.detail ?? `HTTP ${res.status}`);
}
}
export async function adminDeleteSession(sessionId: string): Promise<void> {
const res = await fetch(`${API_BASE}/admin/sessions/${sessionId}`, {
method: "DELETE",