diff --git a/app/api/account/route.ts b/app/api/account/route.ts index 9669289..65d107e 100644 --- a/app/api/account/route.ts +++ b/app/api/account/route.ts @@ -16,8 +16,15 @@ export async function GET() { const games = await prisma.game.findMany({ where: { userId: session.user.id }, select: { - id: true, mode: true, startArticle: true, targetArticle: true, - path: true, clicks: true, timeSeconds: true, won: true, playedAt: true, + id: true, + mode: true, + startArticle: true, + targetArticle: true, + path: true, + clicks: true, + timeSeconds: true, + won: true, + playedAt: true, }, orderBy: { playedAt: "desc" }, }); @@ -25,8 +32,13 @@ export async function GET() { const dailyResults = await prisma.dailyResult.findMany({ where: { userId: session.user.id }, select: { - id: true, puzzleId: true, path: true, clicks: true, - timeSeconds: true, won: true, playedAt: true, + id: true, + puzzleId: true, + path: true, + clicks: true, + timeSeconds: true, + won: true, + playedAt: true, }, orderBy: { playedAt: "desc" }, }); @@ -34,7 +46,10 @@ export async function GET() { const data = { profile: user, games: games.map((g) => ({ ...g, path: JSON.parse(g.path) as string[] })), - dailyResults: dailyResults.map((d) => ({ ...d, path: JSON.parse(d.path) as string[] })), + dailyResults: dailyResults.map((d) => ({ + ...d, + path: JSON.parse(d.path) as string[], + })), exportedAt: new Date().toISOString(), }; diff --git a/app/api/admin/daily/[id]/route.ts b/app/api/admin/daily/[id]/route.ts index 79ba26c..907eb70 100644 --- a/app/api/admin/daily/[id]/route.ts +++ b/app/api/admin/daily/[id]/route.ts @@ -3,10 +3,10 @@ import { prisma } from "../../../../../lib/prisma"; type Params = { params: Promise<{ id: string }> }; -// PATCH /api/admin/daily/[id] — modifier un puzzle +// 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 { + const { startArticle, targetArticle } = (await req.json()) as { startArticle: string; targetArticle: string; }; @@ -17,7 +17,7 @@ export async function PATCH(req: Request, { params }: Params) { return NextResponse.json(puzzle); } -// DELETE /api/admin/daily/[id] — supprimer un 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 } }); diff --git a/app/api/admin/daily/route.ts b/app/api/admin/daily/route.ts index f8c4077..9a0965f 100644 --- a/app/api/admin/daily/route.ts +++ b/app/api/admin/daily/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { prisma } from "../../../../lib/prisma"; -// GET /api/admin/daily — liste tous les puzzles +// GET /api/admin/daily - liste tous les puzzles export async function GET() { const puzzles = await prisma.dailyPuzzle.findMany({ orderBy: { date: "desc" }, @@ -11,9 +11,9 @@ export async function GET() { return NextResponse.json(puzzles); } -// POST /api/admin/daily — créer un puzzle pour une date +// 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 { + const { date, startArticle, targetArticle } = (await req.json()) as { date: string; startArticle: string; targetArticle: string; diff --git a/app/api/admin/stats/route.ts b/app/api/admin/stats/route.ts index 8df3777..765426c 100644 --- a/app/api/admin/stats/route.ts +++ b/app/api/admin/stats/route.ts @@ -10,48 +10,55 @@ export async function GET() { const since14 = new Date(todayStart); since14.setDate(since14.getDate() - 13); - const [totalUsers, totalGames, todayGames, recentUsers, recentGames, modeStats, activityRaw] = - await Promise.all([ - prisma.user.count(), - prisma.game.count(), - prisma.game.count({ where: { playedAt: { gte: todayStart } } }), - prisma.user.findMany({ - orderBy: { createdAt: "desc" }, - take: 50, - select: { - id: true, - name: true, - email: true, - banned: true, - createdAt: true, - _count: { select: { games: true } }, - }, - }), - prisma.game.findMany({ - orderBy: { playedAt: "desc" }, - take: 50, - select: { - id: true, - mode: true, - startArticle: true, - targetArticle: true, - clicks: true, - timeSeconds: true, - won: true, - playedAt: true, - user: { select: { name: true } }, - }, - }), - prisma.game.groupBy({ - by: ["mode"], - _count: { id: true }, - _avg: { clicks: true, timeSeconds: true }, - }), - prisma.game.findMany({ - where: { playedAt: { gte: since14 } }, - select: { playedAt: true }, - }), - ]); + const [ + totalUsers, + totalGames, + todayGames, + recentUsers, + recentGames, + modeStats, + activityRaw, + ] = await Promise.all([ + prisma.user.count(), + prisma.game.count(), + prisma.game.count({ where: { playedAt: { gte: todayStart } } }), + prisma.user.findMany({ + orderBy: { createdAt: "desc" }, + take: 50, + select: { + id: true, + name: true, + email: true, + banned: true, + createdAt: true, + _count: { select: { games: true } }, + }, + }), + prisma.game.findMany({ + orderBy: { playedAt: "desc" }, + take: 50, + select: { + id: true, + mode: true, + startArticle: true, + targetArticle: true, + clicks: true, + timeSeconds: true, + won: true, + playedAt: true, + user: { select: { name: true } }, + }, + }), + prisma.game.groupBy({ + by: ["mode"], + _count: { id: true }, + _avg: { clicks: true, timeSeconds: true }, + }), + prisma.game.findMany({ + where: { playedAt: { gte: since14 } }, + select: { playedAt: true }, + }), + ]); // Agréger l'activité par jour const activityMap = new Map(); diff --git a/app/api/admin/users/[id]/route.ts b/app/api/admin/users/[id]/route.ts index a65856e..20cc825 100644 --- a/app/api/admin/users/[id]/route.ts +++ b/app/api/admin/users/[id]/route.ts @@ -3,17 +3,17 @@ import { prisma } from "../../../../../lib/prisma"; type Params = { params: Promise<{ id: string }> }; -// DELETE /api/admin/users/[id] — supprimer un utilisateur +// DELETE /api/admin/users/[id] - supprimer un utilisateur export async function DELETE(_req: Request, { params }: Params) { const { id } = await params; await prisma.user.delete({ where: { id } }); return NextResponse.json({ ok: true }); } -// PATCH /api/admin/users/[id] — ban/unban +// PATCH /api/admin/users/[id] - ban/unban export async function PATCH(req: Request, { params }: Params) { const { id } = await params; - const { banned } = await req.json() as { banned: boolean }; + const { banned } = (await req.json()) as { banned: boolean }; const user = await prisma.user.update({ where: { id }, data: { banned } }); return NextResponse.json({ id: user.id, banned: user.banned }); } diff --git a/app/api/daily/leaderboard/route.ts b/app/api/daily/leaderboard/route.ts index 82c39a3..7e1f931 100644 --- a/app/api/daily/leaderboard/route.ts +++ b/app/api/daily/leaderboard/route.ts @@ -13,11 +13,13 @@ export async function GET() { take: 20, }); - return NextResponse.json(results.map((r, i) => ({ - rank: i + 1, - name: r.user.name, - clicks: r.clicks, - timeSeconds: r.timeSeconds, - userId: r.userId, - }))); + return NextResponse.json( + results.map((r, i) => ({ + rank: i + 1, + name: r.user.name, + clicks: r.clicks, + timeSeconds: r.timeSeconds, + userId: r.userId, + })), + ); } diff --git a/app/api/daily/route.ts b/app/api/daily/route.ts index 3bc8296..93ac3a9 100644 --- a/app/api/daily/route.ts +++ b/app/api/daily/route.ts @@ -13,7 +13,9 @@ async function getOrCreatePuzzle() { if (existing) return existing; const { start, target } = await pickTwoArticles(); - return prisma.dailyPuzzle.create({ data: { date, startArticle: start, targetArticle: target } }); + return prisma.dailyPuzzle.create({ + data: { date, startArticle: start, targetArticle: target }, + }); } // GET - retourne le puzzle du jour + si l'utilisateur a déjà joué @@ -24,28 +26,49 @@ export async function GET() { let myResult = null; if (session?.user?.id) { myResult = await prisma.dailyResult.findUnique({ - where: { puzzleId_userId: { puzzleId: puzzle.id, userId: session.user.id } }, + where: { + puzzleId_userId: { puzzleId: puzzle.id, userId: session.user.id }, + }, }); } return NextResponse.json({ - puzzle: { id: puzzle.id, date: puzzle.date, startArticle: puzzle.startArticle, targetArticle: puzzle.targetArticle }, + puzzle: { + id: puzzle.id, + date: puzzle.date, + startArticle: puzzle.startArticle, + targetArticle: puzzle.targetArticle, + }, alreadyPlayed: !!myResult, - myResult: myResult ? { clicks: myResult.clicks, timeSeconds: myResult.timeSeconds, won: myResult.won, path: JSON.parse(myResult.path) } : null, + myResult: myResult + ? { + clicks: myResult.clicks, + timeSeconds: myResult.timeSeconds, + won: myResult.won, + path: JSON.parse(myResult.path), + } + : null, }); } // POST - soumettre un résultat export async function POST(req: Request) { const session = await auth(); - if (!session?.user?.id) return NextResponse.json({ error: "Non connecté" }, { status: 401 }); + if (!session?.user?.id) + return NextResponse.json({ error: "Non connecté" }, { status: 401 }); - const { puzzleId, path, clicks, timeSeconds, won } = await req.json() as { - puzzleId: string; path: string[]; clicks: number; timeSeconds: number; won: boolean; + const { puzzleId, path, clicks, timeSeconds, won } = (await req.json()) as { + puzzleId: string; + path: string[]; + clicks: number; + timeSeconds: number; + won: boolean; }; // Vérifier que le puzzle est bien celui du jour - const puzzle = await prisma.dailyPuzzle.findUnique({ where: { id: puzzleId } }); + const puzzle = await prisma.dailyPuzzle.findUnique({ + where: { id: puzzleId }, + }); if (!puzzle || puzzle.date !== todayKey()) { return NextResponse.json({ error: "Puzzle invalide" }, { status: 400 }); } @@ -53,7 +76,14 @@ export async function POST(req: Request) { // Upsert - on n'enregistre qu'une fois const result = await prisma.dailyResult.upsert({ where: { puzzleId_userId: { puzzleId, userId: session.user.id } }, - create: { puzzleId, userId: session.user.id, path: JSON.stringify(path), clicks, timeSeconds, won }, + create: { + puzzleId, + userId: session.user.id, + path: JSON.stringify(path), + clicks, + timeSeconds, + won, + }, update: {}, }); diff --git a/app/api/games/route.ts b/app/api/games/route.ts index bea4b43..21a3cf6 100644 --- a/app/api/games/route.ts +++ b/app/api/games/route.ts @@ -10,7 +10,7 @@ export async function POST(req: NextRequest) { } const { mode, startArticle, targetArticle, path, clicks, timeSeconds, won } = - await req.json() as { + (await req.json()) as { mode: string; startArticle: string; targetArticle: string; @@ -53,6 +53,6 @@ export async function GET() { games.map((g) => ({ ...g, path: JSON.parse(g.path) as string[], - })) + })), ); } diff --git a/app/api/leaderboard/route.ts b/app/api/leaderboard/route.ts index 65032a2..892ff40 100644 --- a/app/api/leaderboard/route.ts +++ b/app/api/leaderboard/route.ts @@ -23,7 +23,9 @@ export async function GET() { const wins = all.filter((g) => g.won); const wonGames = wins.filter((g) => g.clicks > 0); const avgClicks = wonGames.length - ? Math.round(wonGames.reduce((s, g) => s + g.clicks, 0) / wonGames.length) + ? Math.round( + wonGames.reduce((s, g) => s + g.clicks, 0) / wonGames.length, + ) : null; const bestTime = wins.length ? Math.min(...wins.map((g) => g.timeSeconds)) diff --git a/app/api/register/route.ts b/app/api/register/route.ts index b959e12..e210b36 100644 --- a/app/api/register/route.ts +++ b/app/api/register/route.ts @@ -3,15 +3,27 @@ import bcrypt from "bcryptjs"; import { prisma } from "../../../lib/prisma"; export async function POST(req: NextRequest) { - const { name, email, password } = await req.json() as { name?: string; email?: string; password?: string }; + const { name, email, password } = (await req.json()) as { + name?: string; + email?: string; + password?: string; + }; if (!name?.trim() || !email?.trim() || !password || password.length < 6) { - return NextResponse.json({ error: "Champs invalides (mot de passe min. 6 caractères)" }, { status: 400 }); + return NextResponse.json( + { error: "Champs invalides (mot de passe min. 6 caractères)" }, + { status: 400 }, + ); } - const existing = await prisma.user.findUnique({ where: { email: email.toLowerCase() } }); + const existing = await prisma.user.findUnique({ + where: { email: email.toLowerCase() }, + }); if (existing) { - return NextResponse.json({ error: "Cet email est déjà utilisé" }, { status: 409 }); + return NextResponse.json( + { error: "Cet email est déjà utilisé" }, + { status: 409 }, + ); } const hashed = await bcrypt.hash(password, 10); @@ -19,5 +31,8 @@ export async function POST(req: NextRequest) { data: { name: name.trim(), email: email.toLowerCase(), password: hashed }, }); - return NextResponse.json({ id: user.id, name: user.name, email: user.email }, { status: 201 }); + return NextResponse.json( + { id: user.id, name: user.name, email: user.email }, + { status: 201 }, + ); } diff --git a/app/api/rooms/[code]/route.ts b/app/api/rooms/[code]/route.ts index 8987e73..1b39595 100644 --- a/app/api/rooms/[code]/route.ts +++ b/app/api/rooms/[code]/route.ts @@ -28,14 +28,14 @@ const PLAYER_TIMEOUT_MS = 15_000; function prunePlayers(room: Room) { const now = Date.now(); room.players = room.players.filter( - (p) => now - p.lastSeen < PLAYER_TIMEOUT_MS + (p) => now - p.lastSeen < PLAYER_TIMEOUT_MS, ); } // PATCH /api/rooms/[code] export async function PATCH( request: NextRequest, - { params }: { params: Promise<{ code: string }> } + { params }: { params: Promise<{ code: string }> }, ) { const { code } = await params; const rooms = getRooms(); @@ -62,14 +62,24 @@ export async function PATCH( switch (action) { // Rejoindre case "join": { - if (!playerName || typeof playerName !== "string" || playerName.trim() === "") { + if ( + !playerName || + typeof playerName !== "string" || + playerName.trim() === "" + ) { return Response.json({ error: "Pseudo invalide" }, { status: 400 }); } if (room.players.length >= room.maxPlayers) { - return Response.json({ error: `Salle pleine (${room.maxPlayers} joueurs max)` }, { status: 409 }); + return Response.json( + { error: `Salle pleine (${room.maxPlayers} joueurs max)` }, + { status: 409 }, + ); } if (room.phase !== "waiting" && room.phase !== "results") { - return Response.json({ error: "Partie en cours, attends la prochaine manche" }, { status: 409 }); + return Response.json( + { error: "Partie en cours, attends la prochaine manche" }, + { status: 409 }, + ); } const newId = generatePlayerId(); @@ -99,10 +109,16 @@ export async function PATCH( case "start": { const host = room.players.find((p) => p.id === playerId); if (!host?.isHost) { - return Response.json({ error: "Seul l'hote peut demarrer" }, { status: 403 }); + return Response.json( + { error: "Seul l'hote peut demarrer" }, + { status: 403 }, + ); } if (room.players.length < 1) { - return Response.json({ error: "Pas assez de joueurs" }, { status: 400 }); + return Response.json( + { error: "Pas assez de joueurs" }, + { status: 400 }, + ); } if (!startArticle || !targetArticle) { return Response.json({ error: "Articles manquants" }, { status: 400 }); @@ -174,7 +190,7 @@ export async function PATCH( // 1er joueur a gagner = +10 points const alreadyWon = room.players.some( - (p) => p.hasWon && p.id !== player.id + (p) => p.hasWon && p.id !== player.id, ); if (!alreadyWon) { player.score += 10; @@ -190,7 +206,10 @@ export async function PATCH( case "nextRound": { const host = room.players.find((p) => p.id === playerId); if (!host?.isHost) { - return Response.json({ error: "Seul l'hote peut continuer" }, { status: 403 }); + return Response.json( + { error: "Seul l'hote peut continuer" }, + { status: 403 }, + ); } room.phase = "waiting"; room.roundWinner = null; @@ -207,7 +226,10 @@ export async function PATCH( case "resetGame": { const host = room.players.find((p) => p.id === playerId); if (!host?.isHost) { - return Response.json({ error: "Seul l'hote peut reinitialiser" }, { status: 403 }); + return Response.json( + { error: "Seul l'hote peut reinitialiser" }, + { status: 403 }, + ); } room.phase = "waiting"; room.round = 0; @@ -226,4 +248,3 @@ export async function PATCH( return Response.json({ error: "Action inconnue" }, { status: 400 }); } } - diff --git a/app/api/rooms/route.ts b/app/api/rooms/route.ts index eed2bf7..8acd12a 100644 --- a/app/api/rooms/route.ts +++ b/app/api/rooms/route.ts @@ -76,14 +76,28 @@ function pruneOldRooms(rooms: Map) { // Response: { room: Room, playerId: string } export async function POST(request: NextRequest) { const body = await request.json(); - const { playerName, maxPlayers, totalRounds } = body as { playerName: string; maxPlayers?: number; totalRounds?: number }; + const { playerName, maxPlayers, totalRounds } = body as { + playerName: string; + maxPlayers?: number; + totalRounds?: number; + }; - if (!playerName || typeof playerName !== "string" || playerName.trim() === "") { + if ( + !playerName || + typeof playerName !== "string" || + playerName.trim() === "" + ) { return Response.json({ error: "Pseudo invalide" }, { status: 400 }); } - const clampedMax = Math.min(Math.max(typeof maxPlayers === "number" ? Math.floor(maxPlayers) : 16, 2), 16); - const clampedRounds = Math.min(Math.max(typeof totalRounds === "number" ? Math.floor(totalRounds) : 3, 1), 10); + const clampedMax = Math.min( + Math.max(typeof maxPlayers === "number" ? Math.floor(maxPlayers) : 16, 2), + 16, + ); + const clampedRounds = Math.min( + Math.max(typeof totalRounds === "number" ? Math.floor(totalRounds) : 3, 1), + 10, + ); const rooms = getRooms(); pruneOldRooms(rooms); diff --git a/app/api/users/[id]/route.ts b/app/api/users/[id]/route.ts index b6e5479..bf040d3 100644 --- a/app/api/users/[id]/route.ts +++ b/app/api/users/[id]/route.ts @@ -33,7 +33,9 @@ export async function GET(_req: Request, { params }: Params) { ? Math.round(won.reduce((s, g) => s + g.clicks, 0) / won.length) : null; const bestClicks = won.length ? Math.min(...won.map((g) => g.clicks)) : null; - const bestTime = won.length ? Math.min(...won.map((g) => g.timeSeconds)) : null; + const bestTime = won.length + ? Math.min(...won.map((g) => g.timeSeconds)) + : null; const avgTime = won.length ? won.reduce((s, g) => s + g.timeSeconds, 0) / won.length : null;