Skip to content

Commit ae5259c

Browse files
authored
Merge pull request #236 from Agbeleshe/HelthCheck
feat:Health Check with Dependency Status
2 parents 15730d4 + 2347022 commit ae5259c

File tree

15 files changed

+7364
-4
lines changed

15 files changed

+7364
-4
lines changed

.eslintrc.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2+
"extends": "next/core-web-vitals"
3+
}
24
"extends": "next/core-web-vitals"
3-
}
5+
}

app/api/auth/login/route.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
import { NextRequest, NextResponse } from 'next/server';
2+
import { Keypair } from '@stellar/stellar-sdk';
3+
import { getAndClearNonce } from '@/lib/auth-cache';
4+
import {
5+
createSession,
6+
getSessionCookieHeader,
7+
} from '@/lib/session';
8+
29
import { Keypair, StrKey } from '@stellar/stellar-sdk';
310
import { getNonce, deleteNonce } from '@/lib/auth/nonce-store';
411

@@ -15,6 +22,17 @@ export const runtime = 'nodejs';
1522
* - message: The nonce that was signed
1623
* - signature: Base64-encoded signature
1724
*/
25+
26+
export const dynamic = 'force-dynamic';
27+
28+
/**
29+
* Wallet-based auth flow:
30+
* 1. Frontend: user connects wallet (e.g. Freighter), gets address.
31+
* 2. Frontend: GET /api/auth/nonce?address={address} to get a random nonce.
32+
* 3. Frontend: sign the hex nonce with wallet, encode as base64.
33+
* 4. Frontend: POST /api/auth/login with { address, signature }.
34+
* 5. Backend: verify signature with Keypair; create encrypted session cookie.
35+
*/
1836
export async function POST(request: NextRequest) {
1937
try {
2038
const body = await request.json();
@@ -80,6 +98,25 @@ export async function POST(request: NextRequest) {
8098
);
8199
}
82100

101+
const sealed = await createSession(address);
102+
const cookieHeader = getSessionCookieHeader(sealed);
103+
104+
return new Response(
105+
JSON.stringify({ success: true, address, token: sealed }),
106+
{
107+
status: 200,
108+
headers: {
109+
'Content-Type': 'application/json',
110+
'Set-Cookie': cookieHeader,
111+
},
112+
}
113+
);
114+
} catch (err) {
115+
console.error('Login error:', err);
116+
return NextResponse.json(
117+
{ error: 'Internal server error' },
118+
{ status: 500 }
119+
);
83120
} catch (error) {
84121
console.error('Error during login:', error);
85122
return NextResponse.json(

app/api/v1/health/route.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { NextResponse } from 'next/server';
2+
import * as StellarSdk from '@stellar/stellar-sdk';
3+
import prisma from '@/lib/db';
4+
5+
/**
6+
* Health check endpoint for monitoring system status and connectivity.
7+
* GET /api/health (rewritten to /api/v1/health)
8+
*/
9+
export async function GET() {
10+
const results = {
11+
status: 'ok',
12+
database: 'ok' as 'ok' | 'error',
13+
rpc: 'ok' as 'ok' | 'error',
14+
anchor: 'skipped' as 'ok' | 'error' | 'skipped',
15+
};
16+
17+
let criticalFailure = false;
18+
19+
// 1. Database Check
20+
try {
21+
// Run a simple query to ensure connectivity
22+
await prisma.$queryRaw`SELECT 1`;
23+
} catch (error) {
24+
console.error('[Health Check] Database connectivity error:', error);
25+
results.database = 'error';
26+
criticalFailure = true;
27+
}
28+
29+
// 2. Soroban RPC Check
30+
try {
31+
// Use configured RPC URL or default to public testnet
32+
const rpcUrl = process.env.NEXT_PUBLIC_STELLAR_RPC_URL ||
33+
process.env.NEXT_PUBLIC_SOROBAN_RPC_URL ||
34+
'https://soroban-testnet.stellar.org';
35+
36+
// Ping the RPC server
37+
const server = new StellarSdk.SorobanRpc.Server(rpcUrl);
38+
await server.getLatestLedger();
39+
} catch (error) {
40+
console.error('[Health Check] Soroban RPC connectivity error:', error);
41+
results.rpc = 'error';
42+
criticalFailure = true;
43+
}
44+
45+
// 3. Anchor Platform Check (Optional)
46+
const anchorUrl = process.env.ANCHOR_PLATFORM_URL;
47+
if (anchorUrl) {
48+
try {
49+
const controller = new AbortController();
50+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
51+
52+
// Ping the standard Anchor info endpoint
53+
const res = await fetch(`${anchorUrl}/info`, { signal: controller.signal });
54+
clearTimeout(timeoutId);
55+
56+
if (res.ok) {
57+
results.anchor = 'ok';
58+
} else {
59+
console.warn(`[Health Check] Anchor Platform returned status ${res.status}`);
60+
results.anchor = 'error';
61+
}
62+
} catch (error) {
63+
console.error('[Health Check] Anchor Platform connectivity error:', error);
64+
results.anchor = 'error';
65+
// Anchor is optional, so we don't set criticalFailure = true
66+
}
67+
}
68+
69+
// Determine final status
70+
if (criticalFailure) {
71+
results.status = 'unhealthy';
72+
return NextResponse.json(results, { status: 503 });
73+
}
74+
75+
return NextResponse.json(results, { status: 200 });
76+
}

app/api/webhooks/anchor/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export async function POST(request: Request) {
5454

5555
// Internal function to handle the business logic
5656
async function handleAnchorEvent(payload: any) {
57-
const { event_type, transaction_id, status } = payload
57+
const { event_type, transaction_id } = payload
5858

5959
console.log(
6060
`[Webhook] Processing event: ${event_type} for tx: ${transaction_id}`

detailed_lint.txt

8.72 KB
Binary file not shown.

full_lint_output.txt

6.75 KB
Binary file not shown.

lint_output.txt

6.69 KB
Binary file not shown.

openapi.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,41 @@ info:
1616
servers:
1717
- url: /api/v1
1818
description: v1 base path (local/dev)
19+
paths:
20+
/health:
21+
get:
22+
summary: Health check
23+
description: Returns system status and connectivity for RPC, DB, and optional anchor.
24+
responses:
25+
'200':
26+
description: All critical dependencies are healthy.
27+
content:
28+
application/json:
29+
schema:
30+
$ref: '#/components/schemas/HealthStatus'
31+
'503':
32+
description: One or more critical dependencies are unhealthy.
33+
content:
34+
application/json:
35+
schema:
36+
$ref: '#/components/schemas/HealthStatus'
37+
components:
38+
schemas:
39+
HealthStatus:
40+
type: object
41+
properties:
42+
status:
43+
type: string
44+
enum: [ok, unhealthy]
45+
database:
46+
type: string
47+
enum: [ok, error]
48+
rpc:
49+
type: string
50+
enum: [ok, error]
51+
anchor:
52+
type: string
53+
enum: [ok, error, skipped]
1954
- url: /api
2055
description: legacy base path (rewrites to v1)
2156

0 commit comments

Comments
 (0)