Files
fluxer/fluxer_api/src/api/App.ts
T

79 lines
3.0 KiB
TypeScript

// 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<void>;
shutdown: () => Promise<void>;
}
function TelemetryAwareAppErrorHandler(err: Error, ctx: Context<HonoEnv>): Response | Promise<Response> {
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<APIAppResult> {
const {config, logger} = options;
const shutdownApiLifecycle = createShutdown(config, logger);
setIsDevelopment(config.nodeEnv === 'development');
const routes = new Hono<HonoEnv>({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<HonoEnv>({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,
};
}