From d2452324978d99c41f7910a5bae0e234353470a1 Mon Sep 17 00:00:00 2001 From: jessy-david-dev Date: Sat, 11 Apr 2026 13:37:50 +0200 Subject: [PATCH] fix(api): improve security and performance in leaderboard and rooms --- app/api/leaderboard/route.ts | 77 ++++++++++++++++++++--------------- app/api/rooms/[code]/route.ts | 8 +++- app/api/rooms/route.ts | 2 +- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/app/api/leaderboard/route.ts b/app/api/leaderboard/route.ts index 31a5c89..65032a2 100644 --- a/app/api/leaderboard/route.ts +++ b/app/api/leaderboard/route.ts @@ -1,40 +1,53 @@ import { NextResponse } from "next/server"; import { prisma } from "../../../lib/prisma"; +export const revalidate = 60; + export async function GET() { - const users = await prisma.user.findMany({ - include: { games: true }, - }); + try { + const users = await prisma.user.findMany({ + select: { + id: true, + name: true, + games: { + select: { mode: true, won: true, clicks: true, timeSeconds: true }, + }, + }, + }); - const rows = users - .map((u) => { - const all = u.games; - const solo = all.filter((g) => g.mode === "solo"); - const multi = all.filter((g) => g.mode === "multi"); - const wins = all.filter((g) => g.won); - const wonGames = all.filter((g) => g.won && g.clicks > 0); - const avgClicks = 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)) - : null; + const rows = users + .map((u) => { + const all = u.games; + const solo = all.filter((g) => g.mode === "solo"); + const multi = all.filter((g) => g.mode === "multi"); + 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) + : null; + const bestTime = wins.length + ? Math.min(...wins.map((g) => g.timeSeconds)) + : null; - return { - id: u.id, - name: u.name, - totalGames: all.length, - wins: wins.length, - soloGames: solo.length, - soloWins: solo.filter((g) => g.won).length, - multiGames: multi.length, - multiWins: multi.filter((g) => g.won).length, - avgClicks, - bestTime, - }; - }) - .filter((r) => r.totalGames > 0) - .sort((a, b) => b.wins - a.wins || b.totalGames - a.totalGames); + return { + id: u.id, + name: u.name, + totalGames: all.length, + wins: wins.length, + soloGames: solo.length, + soloWins: solo.filter((g) => g.won).length, + multiGames: multi.length, + multiWins: multi.filter((g) => g.won).length, + avgClicks, + bestTime, + }; + }) + .filter((r) => r.totalGames > 0) + .sort((a, b) => b.wins - a.wins || b.totalGames - a.totalGames) + .slice(0, 100); - return NextResponse.json(rows); + return NextResponse.json(rows); + } catch { + return NextResponse.json({ error: "Erreur serveur" }, { status: 500 }); + } } diff --git a/app/api/rooms/[code]/route.ts b/app/api/rooms/[code]/route.ts index 1910c48..c80d245 100644 --- a/app/api/rooms/[code]/route.ts +++ b/app/api/rooms/[code]/route.ts @@ -19,7 +19,7 @@ function getRooms(): Map { } function generatePlayerId(): string { - return Math.random().toString(36).slice(2, 10); + return crypto.randomUUID(); } // Timeout joueur inactif : 15s @@ -156,7 +156,10 @@ export async function PATCH( return Response.json({ error: "Joueur inconnu" }, { status: 404 }); } - player.currentArticle = article ?? ""; + if (!article || typeof article !== "string" || article.length > 300) { + return Response.json({ error: "Article invalide" }, { status: 400 }); + } + player.currentArticle = article; player.lastSeen = Date.now(); // Verifier si le joueur a atteint la cible @@ -223,3 +226,4 @@ 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 9f77083..9d3348e 100644 --- a/app/api/rooms/route.ts +++ b/app/api/rooms/route.ts @@ -55,7 +55,7 @@ function generateCode(): string { } function generatePlayerId(): string { - return Math.random().toString(36).slice(2, 10); + return crypto.randomUUID(); } // Nettoie les rooms inactives depuis plus de 2h