72 lines
2.7 KiB
TypeScript
72 lines
2.7 KiB
TypeScript
import Fastify, { type FastifyInstance } from "fastify";
|
|
import { createAuth } from "./auth";
|
|
import { type Clock, createOffsetClock, systemClock } from "./clock";
|
|
import type { ServerConfig } from "./config";
|
|
import { createDatabase, type Database } from "./db/client";
|
|
import { registerErrorHandler } from "./http/errors";
|
|
import { requirePlayerId } from "./http/session";
|
|
import { appJobs } from "./jobs/definitions";
|
|
import { createPgBossQueue, type JobQueue } from "./jobs/queue";
|
|
import { registerBoardRoutes } from "./routes/boards";
|
|
import type { AppContext } from "./routes/context";
|
|
import { registerDispatchRoutes } from "./routes/dispatches";
|
|
import { registerFleetRoutes } from "./routes/fleet";
|
|
import { registerPlayerRoutes } from "./routes/player";
|
|
import { registerTestClockRoute } from "./routes/test-clock";
|
|
|
|
export interface AppDeps {
|
|
clock?: Clock;
|
|
/** An existing database (tests). Otherwise the app opens one and closes it on shutdown. */
|
|
database?: Database;
|
|
/** Background job queue. Defaults to pg-boss on the app's database. */
|
|
jobs?: JobQueue;
|
|
}
|
|
|
|
/** Builds the Fastify app without listening, so tests can start it on any port. */
|
|
export function buildApp(config: ServerConfig, deps: AppDeps = {}): FastifyInstance {
|
|
const database = deps.database ?? createDatabase(config.databaseUrl);
|
|
const { db } = database;
|
|
const auth = createAuth(db, { secret: config.authSecret, baseUrl: config.authUrl });
|
|
const clock = createOffsetClock(deps.clock ?? systemClock);
|
|
const jobs =
|
|
deps.jobs ??
|
|
createPgBossQueue(config.databaseUrl, {
|
|
workers: config.workersEnabled,
|
|
jobs: appJobs({ db, clock: clock.now }),
|
|
});
|
|
const ctx: AppContext = {
|
|
db,
|
|
auth,
|
|
clock: clock.now,
|
|
// pg-boss timers run on the real clock, not the (possibly offset) game clock.
|
|
toBaseTime: clock.toBase,
|
|
jobs,
|
|
options: {
|
|
requirePlayer: (request) => requirePlayerId(request, auth, db),
|
|
validateResponses: config.env !== "production",
|
|
},
|
|
};
|
|
|
|
const app = Fastify({
|
|
// Structured JSON logs (pino); silenced in tests unless LOG_LEVEL says otherwise.
|
|
logger: { level: config.logLevel },
|
|
});
|
|
// The queue starts with the app and stops before the database closes.
|
|
app.addHook("onReady", () => jobs.start());
|
|
app.addHook("onClose", async () => {
|
|
await jobs.stop();
|
|
if (!deps.database) await database.close();
|
|
});
|
|
registerErrorHandler(app);
|
|
|
|
// Under /api so it is reachable through the same-domain proxy (decision 0.5.20).
|
|
app.get("/api/health", async () => ({ status: "ok" }));
|
|
registerPlayerRoutes(app, ctx);
|
|
registerBoardRoutes(app, ctx);
|
|
registerDispatchRoutes(app, ctx);
|
|
registerFleetRoutes(app, ctx);
|
|
if (config.env !== "production") registerTestClockRoute(app, clock);
|
|
|
|
return app;
|
|
}
|