Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,10 +473,6 @@ Integration tests are in the `__tests__/integration/` directory.
**Testing Checklist:**

- ✓ Authentication flow (nonce → sign → verify)
- ✓ Contract interactions (all 5 contracts)
- ✓ Error handling and validation
- ✓ Rate limiting
- ✓ Session management

### API Documentation

Expand Down
90 changes: 35 additions & 55 deletions app/api/health/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,50 @@
* GET /api/health
*
* Returns 200 when all critical dependencies are healthy, 503 otherwise.
* Response always includes: status, database, rpc, anchor, timestamp.
* Response always includes: status, database, soroban, anchor, timestamp.
*/

import { NextResponse } from "next/server";
import {
getLatestLedger,
getNetworkPassphrase,
SorobanClientError,
} from "@/lib/soroban/client";
import { prisma } from "@/lib/prisma";
import { container, initializeProductionServices } from "@/lib/di/container";
import { HealthService } from "@/lib/services/health-service";

export const runtime = "nodejs";

export async function GET() {
// ── 1. Database ─────────────────────────────────────────────────
let database: { reachable: boolean; error?: string };
// Initialize production services if not in test environment
if (process.env.NODE_ENV !== 'test' && process.env.VITEST !== 'true') {
// Only initialize if not already done
try {
await prisma.$queryRaw`SELECT 1`;
database = { reachable: true };
} catch (err: any) {
database = { reachable: false, error: err?.message ?? "unreachable" };
container.getDb();
} catch {
// Database not initialized, set up production services
const { prisma } = require("@/lib/prisma");
const { createProductionDbClient } = require("@/lib/di/db-factory");
const { createProductionSorobanClient } = require("@/lib/di/soroban-factory");

container.setDb(createProductionDbClient(prisma));
container.setSoroban(createProductionSorobanClient());
}
}

// ── 2. Soroban RPC ───────────────────────────────────────────────
let rpc: {
reachable: boolean;
latestLedger?: number;
protocolVersion?: number;
networkPassphrase?: string;
error?: string;
};
export async function GET() {
const healthService = new HealthService(container);

try {
const ledger = await getLatestLedger();
rpc = {
reachable: true,
latestLedger: ledger.sequence,
protocolVersion: Number(ledger.protocolVersion),
networkPassphrase: getNetworkPassphrase(),
};
} catch (err) {
rpc = {
reachable: false,
error:
err instanceof SorobanClientError
? err.message
: "Unexpected error contacting Soroban RPC",
};
const healthData = await healthService.getOverallHealth();
const healthy = healthData.database.reachable && healthData.soroban.reachable;

return healthService.createHealthResponse(healthData, healthy ? 200 : 503);
} catch (error) {
// Fallback health response if service fails
return NextResponse.json(
{
status: "degraded",
database: { reachable: false, error: "Health service initialization failed" },
soroban: { reachable: false, error: "Health service initialization failed" },
anchor: { reachable: false, error: "Health service initialization failed" },
timestamp: new Date().toISOString(),
},
{ status: 503 }
);
}

// ── 3. Anchor ────────────────────────────────────────────────────
// Placeholder — swap for a real HTTP probe once an anchor URL is configured
const anchor: { reachable: boolean; error?: string } = { reachable: true };

// ── 4. Overall status ────────────────────────────────────────────
const healthy = database.reachable && rpc.reachable;

return NextResponse.json(
{
status: healthy ? "ok" : "degraded",
database,
rpc,
anchor,
timestamp: new Date().toISOString(),
},
{ status: healthy ? 200 : 503 }
);
}
Loading