Files
autofirmer-expanded/app/api/health/route.ts
T
Brandon LiandClaude Opus 5.5 5ec5abde6e Remove broker session rest
Reverts 062ec07 and 5eb0ea7: every firm's broker session stays logged in
around the clock again, renewing its token on the fixed timer as before.
The tree is back to 6dd2528's. A session_rest row left in settings is
unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 23:39:53 -05:00

73 lines
2.9 KiB
TypeScript

import { readFileSync } from 'node:fs';
import path from 'node:path';
import { NextResponse } from 'next/server';
import { getClients } from '@/lib/clients';
import { getTradableFirms } from '@/lib/firm-catalog';
import { getSchedulerHealth, getSchedulerStatus } from '@/lib/auto-trade';
import { getBuildVersion } from '@/lib/version';
import { errorMessage, isClientReady } from '@/lib/platform-client';
// Liveness has to be computed now, never served from a cache.
export const dynamic = 'force-dynamic';
/** A one-line marker file, or null when it isn't there. */
function readMarker(...segments: string[]): string | null {
try {
return readFileSync(path.join(process.cwd(), ...segments), 'utf8').trim() || null;
} catch {
return null;
}
}
/**
* GET /api/health — is this instance able to trade, and if not, why.
*
* The numbers to read before restarting a broken box, and what start-all
* prints after boot. Always 200 when the handler runs: `ok` in the body is the
* verdict (every tradable firm that has a client is synced, and synced
* recently), so a caller can tell "unhealthy" from "not answering".
*
* Calls getClients(), so hitting it also boots an instance that somehow
* hasn't been.
*
* Built field by field on purpose — firm rows carry passwords and API keys,
* and nothing here may echo a credential or a token.
*/
export async function GET() {
try {
const now = Date.now();
const clients = getClients();
const entries = getTradableFirms().map((firm) => {
const base = { firmId: firm.id, firm: firm.name, platform: firm.platform };
const client = clients.get(firm.id);
if (!client) return { ...base, hasClient: false, ready: false, accounts: 0 };
const ready = isClientReady(client, now);
const accounts = client.accountList.length;
try {
return { ...base, hasClient: true, ready, accounts, ...client.health() };
} catch (err) {
// One client's broken health() must not hide the rest.
return { ...base, hasClient: true, ready, accounts, healthError: errorMessage(err) };
}
});
return NextResponse.json({
ok: entries.every((e) => !e.hasClient || e.ready),
now,
uptimeSec: Math.round(process.uptime()),
version: getBuildVersion(),
// Read from disk on every call: the build .next holds now (the
// running one, unless a build landed since this process started),
// and the commit the updater last finished building.
buildId: readMarker('.next', 'BUILD_ID'),
builtCommit: readMarker('scripts', '.built-commit'),
scheduler: { ...getSchedulerStatus(), ...getSchedulerHealth() },
clients: entries,
});
} catch (err) {
return NextResponse.json({ ok: false, error: errorMessage(err) }, { status: 500 });
}
}