import type { APIContext } from "./context.js"; import { LoggingHandlerPlugin } from "@orpc/experimental-pino"; import { RPCHandler } from "@orpc/server/fetch"; import { createDb } from "@reviq/db"; import { createLoggingEmailClient, createPostmarkClient, } from "@reviq/emails"; import pino from "pino"; import { BASE_URL, DEFAULT_PORT, DEFAULT_RP_NAME, EMAIL_FROM, POSTMARK_API_KEY, getAllowedOrigins, } from "./constants.js"; import { router } from "./router.js"; const logger = pino({ transport: { target: "pino-pretty", options: { colorize: true, }, }, }); const databaseUrl = Bun.env.DATABASE_URL; if (!databaseUrl) { throw new Error("DATABASE_URL environment variable is required"); } const db = createDb(databaseUrl); // Create email client - use Postmark if API key is set, otherwise log to console const emailClient = POSTMARK_API_KEY ? createPostmarkClient(POSTMARK_API_KEY) : createLoggingEmailClient(); if (!POSTMARK_API_KEY) { logger.info("POSTMARK_API_KEY not set - emails will be logged to console"); } const handler = new RPCHandler(router, { plugins: [ new LoggingHandlerPlugin({ logger, logRequestResponse: true, }), ], }); const port = Bun.env.PORT ?? DEFAULT_PORT; const allowedOrigins = getAllowedOrigins(); const rpName = Bun.env.RP_NAME ?? DEFAULT_RP_NAME; Bun.serve({ port, async fetch(request, server) { const url = new URL(request.url); if (url.pathname.startsWith("/api/v1/rpc")) { // Build context for the request const origin = request.headers.get("origin") ?? `http://localhost:${port.toString()}`; // Create response headers for setting cookies const resHeaders = new Headers(); // Get client IP from Bun's server (fallback for when no proxy headers) const socketInfo = server.requestIP(request); const clientIP = socketInfo?.address ?? null; const context: APIContext = { db, origin, allowedOrigins, rpName, reqHeaders: request.headers, resHeaders, clientIP, email: { client: emailClient, fromAddress: EMAIL_FROM, baseUrl: BASE_URL, }, }; const { response } = await handler.handle(request, { prefix: "/api/v1/rpc", context, }); // Merge response headers (cookies) into the response if (response) { resHeaders.forEach((value, key) => { response.headers.append(key, value); }); } return response ?? new Response("Not Found", { status: 404 }); } return new Response("Not Found", { status: 404 }); }, }); logger.info({ port }, "API server running");