← Назад к вопросам
Как реализована админка на нынешней работе?
2.0 Middle🔥 121 комментариев
#JavaScript Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализована админка на нынешней работе?
Архитектура панели администратора
На текущей работе мы используем модульную архитектуру админки, которая встроена в основное Next.js приложение, но изолирована на отдельных маршрутах. Это позволяет иметь единую кодовую базу, но разные компоненты и логику для админов и обычных пользователей.
Структура админки:
- Маршруты под
/admin/*защищены middleware авторизацией - Отдельная папка
app/admin/с компонентами и страницами - API endpoints для администраторских операций под
/api/admin/* - Role-based access control (RBAC) на бэкенде
Основные функции админки:
- Управление пользователями (создание, редактирование, удаление, блокировка)
- Управление контентом (вопросы, ответы, модерация)
- Аналитика и статистика
- Управление подписками и платежами
- Логирование и аудит
Технологическая реализация
// Структура папок админки
// app/admin/
// ├── layout.tsx // Основной layout с sidebar
// ├── page.tsx // Dashboard
// ├── users/
// │ ├── page.tsx // Список пользователей
// │ ├── [id]/
// │ │ └── page.tsx // Редактирование пользователя
// ├── content/
// │ ├── questions/
// │ ├── answers/
// ├── analytics/
// │ └── page.tsx
// └── settings/
// 1. Middleware для защиты админки
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/admin")) {
const token = request.cookies.get("auth_token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Проверяем роль пользователя на бэкенде
try {
const response = await fetch("http://localhost:8000/api/v1/auth/me", {
headers: { Authorization: `Bearer ${token}` }
});
if (!response.ok) {
throw new Error("Unauthorized");
}
const user = await response.json();
// Проверяем что пользователь админ
if (!["admin", "moderator"].includes(user.role)) {
return NextResponse.redirect(new URL("/forbidden", request.url));
}
} catch (error) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/admin/:path*"]
};
// 2. Компонент Sidebar админки
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
const adminMenuItems = [
{ href: "/admin", label: "Dashboard", icon: "LayoutDashboard" },
{ href: "/admin/users", label: "Users", icon: "Users" },
{ href: "/admin/content", label: "Content", icon: "FileText" },
{ href: "/admin/analytics", label: "Analytics", icon: "BarChart3" },
{ href: "/admin/settings", label: "Settings", icon: "Settings" }
];
export function AdminSidebar() {
const pathname = usePathname();
return (
<aside className="w-64 bg-surface-secondary border-r border-border-default">
<div className="p-4">
<h1 className="text-lg font-bold">Admin Panel</h1>
</div>
<nav className="px-2">
{adminMenuItems.map((item) => (
<Link
key={item.href}
href={item.href}
className={cn(
"block px-4 py-2 rounded-lg mb-2",
pathname === item.href
? "bg-surface-primary text-link"
: "text-content-secondary hover:bg-surface-tertiary"
)}
>
{item.label}
</Link>
))}
</nav>
</aside>
);
}
// 3. Layout админки
// app/admin/layout.tsx
import { AdminSidebar } from "@/components/admin/Sidebar";
export default function AdminLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<div className="flex h-screen">
<AdminSidebar />
<main className="flex-1 overflow-auto">
<div className="p-8">
{children}
</div>
</main>
</div>
);
}
// 4. Страница управления пользователями
// app/admin/users/page.tsx
"use client";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/Button";
interface User {
id: string;
email: string;
name: string;
role: "admin" | "user";
status: "active" | "blocked";
}
export default function UsersPage() {
const router = useRouter();
const { data: users = [], isLoading } = useQuery({
queryKey: ["admin/users"],
queryFn: async () => {
const res = await fetch("/api/admin/users");
if (!res.ok) throw new Error("Failed to fetch users");
return res.json();
}
});
const handleBlockUser = async (userId: string) => {
try {
const res = await fetch(`/api/admin/users/${userId}/block`, {
method: "POST"
});
if (res.ok) {
router.refresh();
}
} catch (error) {
console.error("Failed to block user", error);
}
};
if (isLoading) return <div>Loading...</div>;
return (
<div>
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Users Management</h1>
<Button onClick={() => router.push("/admin/users/new")}>
Add User
</Button>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-border-default">
<th className="text-left p-4">Email</th>
<th className="text-left p-4">Name</th>
<th className="text-left p-4">Role</th>
<th className="text-left p-4">Status</th>
<th className="text-left p-4">Actions</th>
</tr>
</thead>
<tbody>
{users.map((user: User) => (
<tr key={user.id} className="border-b border-border-default">
<td className="p-4">{user.email}</td>
<td className="p-4">{user.name}</td>
<td className="p-4">{user.role}</td>
<td className="p-4">
<span className={user.status === "active" ? "text-green-600" : "text-red-600"}>
{user.status}
</span>
</td>
<td className="p-4 flex gap-2">
<Button
variant="secondary"
onClick={() => router.push(`/admin/users/${user.id}`)}
>
Edit
</Button>
<Button
variant="destructive"
onClick={() => handleBlockUser(user.id)}
>
Block
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
// 5. API endpoint админки
// app/api/admin/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { verifyAdminToken } from "@/lib/auth";
export async function GET(request: NextRequest) {
try {
// Проверяем авторизацию
const user = await verifyAdminToken(request);
if (user.role !== "admin") {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 403 }
);
}
// Запрашиваем пользователей с бэкенда
const response = await fetch("http://localhost:8000/api/v1/admin/users", {
headers: {
Authorization: `Bearer ${request.cookies.get("api_token")?.value}`
}
});
const users = await response.json();
return NextResponse.json(users);
} catch (error) {
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
// 6. Кастомный хук для админских операций
function useAdminAPI() {
const queryClient = useQueryClient();
const updateUser = useMutation({
mutationFn: async (data: { id: string; updates: Record<string, unknown> }) => {
const res = await fetch(`/api/admin/users/${data.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data.updates)
});
if (!res.ok) throw new Error("Failed to update user");
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["admin/users"] });
}
});
return { updateUser };
}
Ключевые особенности
- Разделение доступа — Middleware проверяет авторизацию перед доступом к админке
- Role-based контроль — На бэкенде проверяем роль пользователя для каждой операции
- Отдельная структура — Админка в отдельной папке, но единая кодовая база
- Real-time обновления — React Query для кэширования и синхронизации данных
- Аудит операций — Все действия админов логируются на бэкенде