Back-office (roadmap V1 step 7): - server: token-protected HTTP API under /api/admin (ADMIN_TOKEN), Drizzle-backed CRUD for manual questions — list, create (mcq/truefalse/free, category upsert, dedup on prompt, validation), delete; 401/503 when unauthorized/unconfigured - client: /admin page with token login + TanStack Query (list/create/delete), format-aware form (QCM / vrai-faux / réponse libre) - env: ADMIN_TOKEN README: replace the shadcn template with a real NerdWare overview (stack, structure, getting started, scripts, game modes, back-office). Verified against real Postgres: auth (401), create (201), dedup (409), validation (400), list, delete (200/404). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
39 lines
1.1 KiB
TypeScript
39 lines
1.1 KiB
TypeScript
import Fastify from "fastify"
|
|
import cors from "@fastify/cors"
|
|
import { env, isDev } from "./env"
|
|
import { createSocketServer } from "./socket"
|
|
import { adminRoutes } from "./admin/routes"
|
|
import "./game/modes" // enregistre les épreuves (registerRound)
|
|
|
|
const app = Fastify({
|
|
logger: isDev
|
|
? { transport: { target: "pino-pretty" } }
|
|
: true,
|
|
})
|
|
|
|
await app.register(cors, { origin: env.corsOrigins })
|
|
|
|
app.get("/health", async () => ({ status: "ok", uptime: process.uptime() }))
|
|
|
|
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)
|
|
})
|
|
}
|