feat(admin): add admin panel with user management, daily puzzle CRUD, and activity graph

- Add admin page at /admin (restricted to ADMIN_EMAIL via proxy)
- Implement user ban/unban and deletion
- Add daily puzzle creation, modification, deletion
- Include 14-day activity chart with hover details
- Add User.banned field to schema and migrate database
- Block banned users from logging in
- Add ADMIN_EMAIL to .env.local configuration
- Update Prisma client after schema changes
This commit is contained in:
jessy-david-dev
2026-04-11 15:10:08 +02:00
parent 5d8f7eae45
commit 8ff64b3470
22 changed files with 899 additions and 122 deletions
+25
View File
@@ -0,0 +1,25 @@
import { NextResponse } from "next/server";
import { prisma } from "../../../../../lib/prisma";
type Params = { params: Promise<{ id: string }> };
// PATCH /api/admin/daily/[id] — modifier un puzzle
export async function PATCH(req: Request, { params }: Params) {
const { id } = await params;
const { startArticle, targetArticle } = await req.json() as {
startArticle: string;
targetArticle: string;
};
const puzzle = await prisma.dailyPuzzle.update({
where: { id },
data: { startArticle, targetArticle },
});
return NextResponse.json(puzzle);
}
// DELETE /api/admin/daily/[id] — supprimer un puzzle
export async function DELETE(_req: Request, { params }: Params) {
const { id } = await params;
await prisma.dailyPuzzle.delete({ where: { id } });
return NextResponse.json({ ok: true });
}
+28
View File
@@ -0,0 +1,28 @@
import { NextResponse } from "next/server";
import { prisma } from "../../../../lib/prisma";
// GET /api/admin/daily — liste tous les puzzles
export async function GET() {
const puzzles = await prisma.dailyPuzzle.findMany({
orderBy: { date: "desc" },
take: 30,
include: { _count: { select: { results: true } } },
});
return NextResponse.json(puzzles);
}
// POST /api/admin/daily — créer un puzzle pour une date
export async function POST(req: Request) {
const { date, startArticle, targetArticle } = await req.json() as {
date: string;
startArticle: string;
targetArticle: string;
};
const puzzle = await prisma.dailyPuzzle.upsert({
where: { date },
create: { date, startArticle, targetArticle },
update: { startArticle, targetArticle },
});
return NextResponse.json(puzzle);
}