Add admin user management with role-gated access
Backend: - schemas/user.py: is_admin (validation_alias=is_superuser) on UserOut and UserAdminOut; UserAdminCreate extends UserCreate with is_admin flag - deps.py: get_current_admin dependency — 403 for non-superusers - routers/admin.py: GET/POST /api/admin/users, DELETE and PATCH /active per user; self-delete and self-deactivate blocked - main.py: register /api/admin router - scripts/seed.py: seed test user with is_superuser=True; promotes existing user if already created without the flag Frontend: - api/client.ts: UserData type with is_admin, admin API functions - components/Nav.tsx: Admin link visible only when user.is_admin is true - pages/AdminPage.tsx: user table with add-user form, delete, toggle active - App.tsx: AdminRoute guard (403-redirects non-admins to /); /admin route Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,30 @@
|
||||
import { Routes, Route, Navigate } from "react-router-dom";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "./hooks/useAuth";
|
||||
import { getMe } from "./api/client";
|
||||
import LoginPage from "./pages/LoginPage";
|
||||
import DashboardPage from "./pages/DashboardPage";
|
||||
import ProfilePage from "./pages/ProfilePage";
|
||||
import AppsPage from "./pages/AppsPage";
|
||||
import SettingsPage from "./pages/SettingsPage";
|
||||
import AdminPage from "./pages/AdminPage";
|
||||
|
||||
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||
const { token } = useAuth();
|
||||
return token ? <>{children}</> : <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
function AdminRoute({ children }: { children: React.ReactNode }) {
|
||||
const { token } = useAuth();
|
||||
const { data: user, isLoading } = useQuery({ queryKey: ["me"], queryFn: getMe });
|
||||
|
||||
if (!token) return <Navigate to="/login" replace />;
|
||||
// Wait for the me query before deciding — prevents a flash redirect
|
||||
if (isLoading) return null;
|
||||
if (!user?.is_admin) return <Navigate to="/" replace />;
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Routes>
|
||||
@@ -20,6 +34,7 @@ export default function App() {
|
||||
<Route path="/apps" element={<PrivateRoute><AppsPage /></PrivateRoute>} />
|
||||
<Route path="/settings" element={<PrivateRoute><SettingsPage /></PrivateRoute>} />
|
||||
<Route path="/profile" element={<PrivateRoute><ProfilePage /></PrivateRoute>} />
|
||||
<Route path="/admin" element={<AdminRoute><AdminPage /></AdminRoute>} />
|
||||
|
||||
{/* Catch-all */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
|
||||
Reference in New Issue
Block a user