Files
source-gamepanel/apps/api/src/routes/nodes/daemon.ts
T
hibna c1adb94abb Format the repository with Prettier
CI has been running `prettier --check` against a tree that was never
formatted, so the check reported 63 files and failed every run. Nothing
here is a behaviour change: `pnpm lint` and the four typecheck builds
pass exactly as before.

conduit-bringup-artifacts is added to .prettierignore instead. Those
files are captured bring-up reports, not maintained sources; reflowing
them would only churn a record of what happened.
2026-08-02 21:08:12 +03:00

67 lines
1.8 KiB
TypeScript

import { Type } from '@sinclair/typebox';
import type { FastifyInstance } from 'fastify';
import { eq } from 'drizzle-orm';
import { nodes } from '@source/database';
import { AppError } from '../../lib/errors.js';
const HeartbeatSchema = {
body: Type.Object({
active_servers: Type.Number({ minimum: 0 }),
total_servers: Type.Number({ minimum: 0 }),
version: Type.String(),
}),
};
function extractBearerToken(authHeader?: string): string | null {
if (!authHeader) return null;
const [scheme, token] = authHeader.split(' ');
if (!scheme || !token || scheme.toLowerCase() !== 'bearer') return null;
return token;
}
export default async function daemonNodeRoutes(app: FastifyInstance) {
// POST /api/nodes/heartbeat
app.post('/heartbeat', { schema: HeartbeatSchema }, async (request) => {
const token = extractBearerToken(
typeof request.headers.authorization === 'string' ? request.headers.authorization : undefined,
);
if (!token) {
throw AppError.unauthorized('Missing daemon bearer token', 'DAEMON_AUTH_MISSING');
}
const node = await app.db.query.nodes.findFirst({
where: eq(nodes.daemonToken, token),
columns: { id: true },
});
if (!node) {
throw AppError.unauthorized('Invalid daemon token', 'DAEMON_AUTH_INVALID');
}
const now = new Date();
await app.db
.update(nodes)
.set({
isOnline: true,
lastHeartbeat: now,
updatedAt: now,
})
.where(eq(nodes.id, node.id));
const body = request.body as {
active_servers: number;
total_servers: number;
version: string;
};
return {
success: true,
nodeId: node.id,
activeServers: body.active_servers,
totalServers: body.total_servers,
version: body.version,
};
});
}