mirror of
https://github.com/furyhawk/agent_alpha.git
synced 2026-07-21 02:25:34 +00:00
feat(admin): implement user deletion functionality in admin dashboard
This commit is contained in:
@@ -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,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),
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user