Reverts062ec07and5eb0ea7: 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>
73 lines
2.9 KiB
TypeScript
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 });
|
|
}
|
|
}
|