-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
137 lines (112 loc) · 4.52 KB
/
index.ts
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { Hono, type Context } from "hono";
import { type RootResolver, graphqlServer } from '@hono/graphql-server';
import { buildSchema } from 'graphql';
import { SafeParseSuccess, z } from 'zod';
import client from './src/clickhouse/client.js';
import openapi from "./static/@typespec/openapi3/openapi.json";
import * as prometheus from './src/prometheus.js';
import { APP_VERSION, config } from "./src/config.js";
import { logger } from './src/logger.js';
import { makeUsageQuery } from "./src/usage.js";
import { APIErrorResponse } from "./src/utils.js";
import { usageOperationsToEndpointsMap, type EndpointReturnTypes, type UsageEndpoints, type ValidPathParams, type ValidUserParams } from "./src/types/api.js";
import { paths, usageTransfersQueryParamsSchema } from './src/types/zod.gen.js';
async function AntelopeTokenAPI() {
const app = new Hono();
// Tracking all incoming requests
app.use(async (ctx: Context, next) => {
const pathname = ctx.req.path;
logger.trace(`Incoming request: [${pathname}]`);
prometheus.request.inc({ pathname });
await next();
});
// ---------------
// --- Swagger ---
// ---------------
app.get(
"/",
async (_) => new Response(Bun.file("./swagger/index.html"))
);
app.get(
"/favicon.ico",
async (_) => new Response(Bun.file("./swagger/favicon.ico"))
);
// ------------
// --- Docs ---
// ------------
app.get(
"/openapi",
async (ctx: Context) => ctx.json<{ [key: string]: EndpointReturnTypes<"/openapi">; }, 200>(openapi)
);
app.get(
"/version",
async (ctx: Context) => ctx.json<EndpointReturnTypes<"/version">, 200>(APP_VERSION)
);
// ------------------
// --- Monitoring ---
// ------------------
app.get(
"/health",
async (ctx: Context) => {
const response = await client.ping();
if (!response.success) {
return APIErrorResponse(ctx, 500, "bad_database_response", response.error.message);
}
return new Response("OK");
}
);
app.get(
"/metrics",
async () => new Response(await prometheus.registry.metrics())
);
// --------------------------
// --- REST API endpoints ---
// --------------------------
const createUsageEndpoint = (endpoint: UsageEndpoints) => app.get(
// Hono using different syntax than OpenAPI for path parameters
// `/{path_param}` (OpenAPI) VS `/:path_param` (Hono)
endpoint.replace(/{([^}]+)}/g, ":$1"),
async (ctx: Context) => {
// Use `unknown` for undefined schemas definitions in `zod.gen.ts`
const path_params_schema = paths[endpoint]["get"]["parameters"]["path"] ?? z.unknown();
const query_params_schema = z.preprocess(
(q: any) => {
// Preprocess block ranges to array so Zod can parse it
if (q.block_range)
q.block_range = q.block_range.split(',');
return q;
},
paths[endpoint]["get"]["parameters"]["query"] ?? z.unknown()
);
const path_params = path_params_schema.safeParse(ctx.req.param());
const query_params = query_params_schema.safeParse(ctx.req.query());
if (path_params.success && query_params.success) {
return makeUsageQuery(
ctx,
endpoint,
{
...path_params.data as SafeParseSuccess<ValidPathParams<typeof endpoint>>,
...query_params.data
} as ValidUserParams<typeof endpoint>
);
} else {
// Merge path and query params errors into one `ZodError` instance
const e = new z.ZodError([]);
e.addIssues(path_params.error?.issues);
e.addIssues(query_params.error?.issues);
return APIErrorResponse(ctx, 400, "bad_query_input", e);
}
}
);
// Create all API endpoints interacting with DB
Object.values(usageOperationsToEndpointsMap).forEach(e => createUsageEndpoint(e));
// -------------
// --- Miscs ---
// -------------
app.notFound((ctx: Context) => APIErrorResponse(ctx, 404, "route_not_found", `Path not found: ${ctx.req.method} ${ctx.req.path}`));
return app;
}
export default {
...await AntelopeTokenAPI(),
idleTimeout: config.requestIdleTimeout
}