// SPDX-License-Identifier: AGPL-3.0-or-later import {createInitializer, createShutdown} from '@app/api/app/APILifecycle'; import {registerControllers} from '@app/api/app/ControllerRegistry'; import {configureMiddleware} from '@app/api/app/MiddlewarePipeline'; import type {APIConfig} from '@app/api/config/APIConfig'; import type {ILogger} from '@app/api/ILogger'; import {recordRequestStatus} from '@app/api/middleware/RequestErrorTelemetry'; import type {HonoApp, HonoEnv} from '@app/api/types/HonoEnv'; import {AppErrorHandler, AppNotFoundHandler} from '@fluxer/errors/src/domains/core/ErrorHandlers'; import {IpBannedError} from '@fluxer/errors/src/domains/moderation/IpBannedError'; import {resolveErrorStatus} from '@fluxer/errors/src/error_handling/ErrorIntrospection'; import {createMetricsMiddleware} from '@fluxer/hono/src/middleware/Metrics'; import {LOCKED_DOWN_PERMISSIONS_POLICY, securityHeaders} from '@fluxer/hono/src/middleware/SecurityHeaders'; import {setIsDevelopment} from '@fluxer/schema/src/primitives/UrlValidators'; import type {Context} from 'hono'; import {Hono} from 'hono'; interface CreateAPIAppOptions { config: APIConfig; logger: ILogger; registerRoutes?: (routes: HonoApp) => void; } interface APIAppResult { app: HonoApp; initialize: () => Promise; shutdown: () => Promise; } function TelemetryAwareAppErrorHandler(err: Error, ctx: Context): Response | Promise { if (!(err instanceof IpBannedError)) { const status = resolveErrorStatus(err); if (status !== null && !ctx.get('user')) { recordRequestStatus(ctx.req.raw, status); } } return AppErrorHandler(err, ctx); } export async function createAPIApp(options: CreateAPIAppOptions): Promise { const {config, logger} = options; const shutdownApiLifecycle = createShutdown(config, logger); setIsDevelopment(config.nodeEnv === 'development'); const routes = new Hono({strict: true}); configureMiddleware(routes, { logger, nodeEnv: config.nodeEnv, corsOrigins: [...config.endpoints.webAppOrigins, config.endpoints.marketing], trustClientIpHeader: config.proxy.trust_client_ip_header, clientIpHeaderName: config.proxy.client_ip_header, maxInflightRequests: config.maxInflightRequests, }); routes.onError(TelemetryAwareAppErrorHandler); routes.notFound(AppNotFoundHandler); registerControllers(routes, config); options.registerRoutes?.(routes); const app = new Hono({strict: true}); const {middleware: metricsMiddleware, metricsHandler} = createMetricsMiddleware('api'); app.use('*', metricsMiddleware); app.get('/_metrics', metricsHandler); app.use( '*', securityHeaders({ contentSecurityPolicy: "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", permissionsPolicy: LOCKED_DOWN_PERMISSIONS_POLICY, }), ); app.route('/v1', routes); app.route('/', routes); app.onError(TelemetryAwareAppErrorHandler); app.notFound(AppNotFoundHandler); return { app, initialize: createInitializer(config, logger), shutdown: shutdownApiLifecycle, }; }