forked from ezedike-evan/stellar-intel
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroute.ts
More file actions
62 lines (57 loc) · 2.39 KB
/
Copy pathroute.ts
File metadata and controls
62 lines (57 loc) · 2.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { NextRequest, NextResponse } from 'next/server';
import { createYoga } from 'graphql-yoga';
import { schema } from '@/lib/graphql/schema';
import { createGraphqlSecurityPlugin } from '@/lib/graphql/security';
import { checkRateLimit, getClientIp } from '@/lib/api/rate-limit';
import { getLogger } from '@/lib/logger';
// This route resolves rates/anchors/health/intent data through the same
// lib/ functions the REST v1 routes use — see lib/graphql/resolvers.ts.
// Additive surface only: REST v1 (public/openapi.json) remains canonical.
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
const { handleRequest } = createYoga({
schema,
graphqlEndpoint: '/api/graphql',
// The interactive GraphiQL explorer is a local/staging convenience, not
// part of the public product surface — off in production.
landingPage: process.env.NODE_ENV !== 'production',
// Depth + field-count limits on every operation, and introspection disabled
// in production (see lib/graphql/security.ts).
plugins: [createGraphqlSecurityPlugin()],
});
async function handler(request: NextRequest): Promise<Response> {
const ip = getClientIp(request.headers);
const rl = await checkRateLimit(ip, { bucket: 'api.graphql', maxRequests: 60 });
if (!rl.allowed) {
getLogger('api.graphql').warn({
event: 'rate_limit_exceeded',
ip,
retryAfter: rl.retryAfter,
});
return NextResponse.json(
{ error: 'Too many requests', retryAfter: rl.retryAfter },
{
status: 429,
headers: {
'Retry-After': String(rl.retryAfter),
'X-RateLimit-Remaining': '0',
},
}
);
}
try {
return await handleRequest(request, {});
} catch (error) {
// Never let an unhandled throw surface as a bare empty 500 — return a
// structured GraphQL-shaped error and log the cause. (The live endpoint has
// been observed 500ing with an empty body in prod but not locally; the root
// cause is still unconfirmed and needs a prod-build repro — see maintainer.md
// Phase 0. This wrapper at least makes any throw observable.)
getLogger('api.graphql').error({
event: 'graphql_handler_error',
error: error instanceof Error ? error.message : String(error),
});
return NextResponse.json({ errors: [{ message: 'Internal server error' }] }, { status: 500 });
}
}
export { handler as GET, handler as POST };