diff --git a/api/src/routes/app-routes.ts b/api/src/routes/app-routes.ts index 0fb791b6a..124d7b2a7 100644 --- a/api/src/routes/app-routes.ts +++ b/api/src/routes/app-routes.ts @@ -1,6 +1,17 @@ -import { appRoutes } from "@/db/schema.js"; +import { + NewAppRequest, + appRequestInsertSchema, + appRequests, + appResponseInsertSchema, + appResponses, + appRoutes, +} from "@/db/schema.js"; import { Hono } from "hono"; import type { Bindings, Variables } from "../lib/types.js"; +import { z } from "zod"; +import { zValidator } from "@hono/zod-validator"; +import { hc } from "hono/client"; +import { eq } from "drizzle-orm"; const app = new Hono<{ Bindings: Bindings; Variables: Variables }>(); @@ -10,4 +21,110 @@ app.get("/v0/app-routes", async (ctx) => { return ctx.json(routes); }); +app.get("/v0/all-requests", async (ctx) => { + const db = ctx.get("db"); + const requests = await db + .select() + .from(appResponses) + .rightJoin(appRequests, eq(appResponses.requestId, appRequests.id)); + return ctx.json(requests); +}); + +app.post( + "/v0/send-request", + zValidator("json", appRequestInsertSchema), + async (ctx) => { + const { + requestMethod, + requestUrl, + requestHeaders, + requestQueryParams, + requestBody, + } = ctx.req.valid("json"); + + const db = ctx.get("db"); + const dbErrors = ctx.get("dbErrors"); + + const requestObject = { + method: requestMethod, + body: requestBody ?? (undefined as any), // ah just make it work + headers: requestHeaders ?? undefined, + }; + + const finalUrl = resolveUrl(requestUrl, requestQueryParams); + + const newRequest: NewAppRequest = { + requestMethod, + requestUrl, + requestHeaders, + requestQueryParams, + requestBody, + }; + + const insertResult = await db + .insert(appRequests) + .values(newRequest) + .returning({ requestId: appRequests.id }); + + const requestId = insertResult[0].requestId; // only one insert always happens not sure why drizzle returns an array... + + const startTime = Date.now(); + const response = await fetch(finalUrl, requestObject); + const endTime = Date.now(); + + // We extract the header from the response and delete it so it doesn't duplicate + const traceId = response.headers.get("x-fpx-trace-id"); + + const { responseBody, responseTime, responseHeaders, responseStatusCode } = + await appResponseInsertSchema + .extend({ + headers: z.instanceof(Headers), + status: z.number(), + body: z.instanceof(ReadableStream), + traceId: z.string().optional(), + }) + .transform(async ({ headers, status, body }) => { + let responseHeaders: Record = {}; + headers.forEach((value, key) => (responseHeaders[key] = value)); + + return { + responseHeaders, + responseStatusCode: status, + responseBody: await response.text(), + responseTime: endTime - startTime, + }; + }) + .parseAsync(response); + + await db.insert(appResponses).values([ + { + responseStatusCode, + responseTime, + responseHeaders, + responseBody, + traceId: traceId ?? "", + requestId, + }, + ]); + + return ctx.json({ + responseStatusCode, + responseTime, + responseHeaders, + responseBody, + traceId: traceId ?? "", + }); + }, +); + +function resolveUrl(url: string, queryParams?: Record | null) { + if (!queryParams) return url; + + const urlObject = new URL(url); + for (const [key, value] of Object.entries(queryParams)) { + urlObject.searchParams.set(key, value); + } + return urlObject.toString(); +} + export default app;