DB:
- quiz_question gains `lang` ('fr' default) and `active` (true default); migration 0002
- only active questions are served to games (pool filter)
History:
- engine writes a game_history row at game end (modes + ranked results), fire-and-forget
- public GET /api/history/:code lists a room's recent games
- lobby shows a "Parties précédentes" collapsible (date, modes, winner/scores)
Back-office:
- create form has a FR/EN language select; lang stored
- per-question active toggle (PATCH /questions/:id) — disable without deleting;
inactive rows dimmed and labelled
- seeds set lang (opentdb=en, manual/pokemon=fr)
Verified e2e: migration applied; game played → /api/history returns it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import { mkdirSync } from "node:fs"
|
|
import { resolve } from "node:path"
|
|
import Fastify from "fastify"
|
|
import cors from "@fastify/cors"
|
|
import multipart from "@fastify/multipart"
|
|
import fastifyStatic from "@fastify/static"
|
|
import { env, isDev } from "./env"
|
|
import { createSocketServer } from "./socket"
|
|
import { adminRoutes } from "./admin/routes"
|
|
import { listCategories } from "./db/quiz-repo"
|
|
import { listGameHistory } from "./db/history-repo"
|
|
import "./game/modes" // enregistre les épreuves (registerRound)
|
|
|
|
const app = Fastify({
|
|
logger: isDev
|
|
? { transport: { target: "pino-pretty" } }
|
|
: true,
|
|
})
|
|
|
|
await app.register(cors, { origin: env.corsOrigins })
|
|
|
|
// Images (image_reveal) : dossier servi en statique sur /uploads.
|
|
const uploadsRoot = resolve(env.uploadsDir)
|
|
mkdirSync(uploadsRoot, { recursive: true })
|
|
await app.register(multipart, { limits: { fileSize: 8 * 1024 * 1024 } })
|
|
await app.register(fastifyStatic, { root: uploadsRoot, prefix: "/uploads/" })
|
|
|
|
app.get("/health", async () => ({ status: "ok", uptime: process.uptime() }))
|
|
|
|
// Catégories de quiz disponibles (public, pour le filtre du lobby).
|
|
app.get("/api/categories", async () => listCategories())
|
|
|
|
// Historique des parties d'une room (public).
|
|
app.get<{ Params: { code: string } }>("/api/history/:code", async (req) =>
|
|
listGameHistory(req.params.code.toUpperCase())
|
|
)
|
|
|
|
await app.register(adminRoutes, { prefix: "/api/admin" })
|
|
|
|
// Socket.IO se greffe sur le serveur HTTP sous-jacent de Fastify.
|
|
const io = createSocketServer(app.server)
|
|
|
|
try {
|
|
await app.listen({ host: env.host, port: env.port })
|
|
app.log.info(`Socket.IO ready (${io.engine.clientsCount} clients)`)
|
|
} catch (err) {
|
|
app.log.error(err)
|
|
process.exit(1)
|
|
}
|
|
|
|
// Arrêt propre : on ferme Socket.IO puis Fastify.
|
|
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
process.on(signal, async () => {
|
|
app.log.info(`${signal} received, shutting down`)
|
|
await io.close()
|
|
await app.close()
|
|
process.exit(0)
|
|
})
|
|
}
|