feat: add user accounts, authentication, and game history with Prisma 7
- Implement user registration and login with NextAuth v5 (email/password, JWT) - Add authentication modal in UI with login/register tabs - Create user profile screen showing game statistics and history - Integrate Prisma 7 ORM with SQLite database for data persistence - Store game results (mode, path, clicks, time) in database - Auto-save completed games only when user is authenticated - Separate business logic into reusable hooks (useSoloGame, useMultiGame) - Organize UI into composable screen components (HomeScreen, SoloScreen, ProfileScreen, etc) - Add session persistence across F5 refresh for solo and multiplayer - Style auth modal, account button, and profile stats dashboard
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
// Route handler: POST /api/rooms - creer une room
|
||||
// GET /api/rooms?code=XXXX - recuperer l'etat d'une room
|
||||
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
// Types
|
||||
|
||||
export type Player = {
|
||||
id: string;
|
||||
name: string;
|
||||
score: number;
|
||||
currentArticle: string;
|
||||
hasWon: boolean;
|
||||
isHost: boolean;
|
||||
lastSeen: number; // timestamp ms
|
||||
};
|
||||
|
||||
export type Room = {
|
||||
code: string;
|
||||
players: Player[];
|
||||
phase: "waiting" | "countdown" | "playing" | "results";
|
||||
round: number;
|
||||
totalRounds: number;
|
||||
startArticle: string;
|
||||
targetArticle: string;
|
||||
roundWinner: string | null; // player id
|
||||
countdownStart: number | null; // timestamp ms
|
||||
roundStart: number | null; // timestamp ms
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
// Stockage en memoire (singleton Node.js)
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __wikirooms: Map<string, Room> | undefined;
|
||||
}
|
||||
|
||||
function getRooms(): Map<string, Room> {
|
||||
if (!global.__wikirooms) {
|
||||
global.__wikirooms = new Map();
|
||||
}
|
||||
return global.__wikirooms;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function generateCode(): string {
|
||||
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||
let code = "";
|
||||
for (let i = 0; i < 4; i++) {
|
||||
code += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
function generatePlayerId(): string {
|
||||
return Math.random().toString(36).slice(2, 10);
|
||||
}
|
||||
|
||||
// Nettoie les rooms inactives depuis plus de 2h
|
||||
function pruneOldRooms(rooms: Map<string, Room>) {
|
||||
const now = Date.now();
|
||||
for (const [code, room] of rooms) {
|
||||
if (now - room.createdAt > 2 * 60 * 60 * 1000) {
|
||||
rooms.delete(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handlers
|
||||
|
||||
// POST /api/rooms
|
||||
// Body: { playerName: string }
|
||||
// Response: { room: Room, playerId: string }
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
const { playerName } = body as { playerName: string };
|
||||
|
||||
if (!playerName || typeof playerName !== "string" || playerName.trim() === "") {
|
||||
return Response.json({ error: "Pseudo invalide" }, { status: 400 });
|
||||
}
|
||||
|
||||
const rooms = getRooms();
|
||||
pruneOldRooms(rooms);
|
||||
|
||||
// Generer un code unique
|
||||
let code = generateCode();
|
||||
let attempts = 0;
|
||||
while (rooms.has(code) && attempts < 20) {
|
||||
code = generateCode();
|
||||
attempts++;
|
||||
}
|
||||
|
||||
const playerId = generatePlayerId();
|
||||
|
||||
const room: Room = {
|
||||
code,
|
||||
players: [
|
||||
{
|
||||
id: playerId,
|
||||
name: playerName.trim().slice(0, 20),
|
||||
score: 0,
|
||||
currentArticle: "",
|
||||
hasWon: false,
|
||||
isHost: true,
|
||||
lastSeen: Date.now(),
|
||||
},
|
||||
],
|
||||
phase: "waiting",
|
||||
round: 0,
|
||||
totalRounds: 3,
|
||||
startArticle: "",
|
||||
targetArticle: "",
|
||||
roundWinner: null,
|
||||
countdownStart: null,
|
||||
roundStart: null,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
rooms.set(code, room);
|
||||
|
||||
return Response.json({ room, playerId });
|
||||
}
|
||||
|
||||
// GET /api/rooms?code=XXXX
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const code = searchParams.get("code");
|
||||
|
||||
if (!code) {
|
||||
return Response.json({ error: "Code manquant" }, { status: 400 });
|
||||
}
|
||||
|
||||
const rooms = getRooms();
|
||||
const room = rooms.get(code.toUpperCase());
|
||||
|
||||
if (!room) {
|
||||
return Response.json({ error: "Room introuvable" }, { status: 404 });
|
||||
}
|
||||
|
||||
return Response.json({ room });
|
||||
}
|
||||
Reference in New Issue
Block a user