fix(api): improve security and performance in leaderboard and rooms

This commit is contained in:
jessy-david-dev
2026-04-11 13:37:50 +02:00
parent f62ce65858
commit d245232497
3 changed files with 52 additions and 35 deletions
+45 -32
View File
@@ -1,40 +1,53 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { prisma } from "../../../lib/prisma"; import { prisma } from "../../../lib/prisma";
export const revalidate = 60;
export async function GET() { export async function GET() {
const users = await prisma.user.findMany({ try {
include: { games: true }, const users = await prisma.user.findMany({
}); select: {
id: true,
name: true,
games: {
select: { mode: true, won: true, clicks: true, timeSeconds: true },
},
},
});
const rows = users const rows = users
.map((u) => { .map((u) => {
const all = u.games; const all = u.games;
const solo = all.filter((g) => g.mode === "solo"); const solo = all.filter((g) => g.mode === "solo");
const multi = all.filter((g) => g.mode === "multi"); const multi = all.filter((g) => g.mode === "multi");
const wins = all.filter((g) => g.won); const wins = all.filter((g) => g.won);
const wonGames = all.filter((g) => g.won && g.clicks > 0); const wonGames = wins.filter((g) => g.clicks > 0);
const avgClicks = wonGames.length 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; : null;
const bestTime = wins.length const bestTime = wins.length
? Math.min(...wins.map((g) => g.timeSeconds)) ? Math.min(...wins.map((g) => g.timeSeconds))
: null; : null;
return { return {
id: u.id, id: u.id,
name: u.name, name: u.name,
totalGames: all.length, totalGames: all.length,
wins: wins.length, wins: wins.length,
soloGames: solo.length, soloGames: solo.length,
soloWins: solo.filter((g) => g.won).length, soloWins: solo.filter((g) => g.won).length,
multiGames: multi.length, multiGames: multi.length,
multiWins: multi.filter((g) => g.won).length, multiWins: multi.filter((g) => g.won).length,
avgClicks, avgClicks,
bestTime, bestTime,
}; };
}) })
.filter((r) => r.totalGames > 0) .filter((r) => r.totalGames > 0)
.sort((a, b) => b.wins - a.wins || b.totalGames - a.totalGames); .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 });
}
} }
+6 -2
View File
@@ -19,7 +19,7 @@ function getRooms(): Map<string, Room> {
} }
function generatePlayerId(): string { function generatePlayerId(): string {
return Math.random().toString(36).slice(2, 10); return crypto.randomUUID();
} }
// Timeout joueur inactif : 15s // Timeout joueur inactif : 15s
@@ -156,7 +156,10 @@ export async function PATCH(
return Response.json({ error: "Joueur inconnu" }, { status: 404 }); 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(); player.lastSeen = Date.now();
// Verifier si le joueur a atteint la cible // Verifier si le joueur a atteint la cible
@@ -223,3 +226,4 @@ export async function PATCH(
return Response.json({ error: "Action inconnue" }, { status: 400 }); return Response.json({ error: "Action inconnue" }, { status: 400 });
} }
} }
+1 -1
View File
@@ -55,7 +55,7 @@ function generateCode(): string {
} }
function generatePlayerId(): string { function generatePlayerId(): string {
return Math.random().toString(36).slice(2, 10); return crypto.randomUUID();
} }
// Nettoie les rooms inactives depuis plus de 2h // Nettoie les rooms inactives depuis plus de 2h