import { randomUUID } from 'node:crypto'; import cors from '@fastify/cors'; import rateLimit from '@fastify/rate-limit'; import Fastify, { type FastifyInstance } from 'fastify'; import { loadConfig, type AppConfig } from './config.js'; import { registerHealthRoutes, type MqttHealthProvider } from './routes/health.js'; import { registerPlatformBootstrapRoutes, type PlatformConfigResolver } from './routes/platform-bootstrap.js'; import { registerAuthRoutes, type AuthRouteOptions } from './routes/auth.js'; import { registerUserManagementRoutes, type UserManagementRouteOptions } from './routes/user-management.js'; import { registerStoreRoomRoutes, type StoreRoomRouteOptions } from './routes/store-room-management.js'; import { registerContentRoutes, type ContentRouteOptions } from './routes/content-management.js'; import { registerStoreDiscoveryRoutes, type StoreDiscoveryRouteOptions } from './routes/store-discovery.js'; import { registerStoreAccessRoutes, type StoreAccessRouteOptions } from './routes/store-access.js'; import { registerPricingRoutes, type PricingRouteOptions } from './routes/pricing.js'; import { registerOrderStateRoutes, type OrderStateRouteOptions } from './routes/order-state.js'; import { registerOrderQueryRoutes, type OrderQueryRouteOptions } from './routes/order-query.js'; import { registerOrderManagementRoutes, type OrderManagementRouteOptions } from './routes/order-management.js'; import { registerOrderShareRoutes, type OrderShareRouteOptions } from './routes/order-share.js'; import { registerPaymentRoutes, type PaymentRouteOptions } from './routes/payments.js'; import { registerThirdPartyRoutes, type ThirdPartyRouteOptions } from './routes/third-party.js'; import { registerDeviceRoutes, type DeviceRouteOptions } from './routes/devices.js'; import { registerDeviceControlRoutes, type DeviceControlRouteOptions } from './routes/device-control.js'; import { registerMemberRoutes, type MemberRouteOptions } from './routes/members.js'; import { registerRechargeRoutes, type RechargeRouteOptions } from './routes/recharge.js'; import { registerCleaningRoutes, type CleaningRouteOptions } from './routes/cleaning.js'; export interface BuildAppOptions { config?: AppConfig; platformConfigRepository?: PlatformConfigResolver; auth?: AuthRouteOptions; userManagement?: UserManagementRouteOptions; storeRoom?: StoreRoomRouteOptions; content?: ContentRouteOptions; storeDiscovery?: StoreDiscoveryRouteOptions; storeAccess?: StoreAccessRouteOptions; pricing?: PricingRouteOptions; orderState?: OrderStateRouteOptions; orderQuery?: OrderQueryRouteOptions; orderManagement?: OrderManagementRouteOptions; orderShare?: OrderShareRouteOptions; payment?: PaymentRouteOptions; thirdParty?: ThirdPartyRouteOptions; mqtt?: MqttHealthProvider; devices?: DeviceRouteOptions; deviceControl?: DeviceControlRouteOptions; members?: MemberRouteOptions; recharge?: RechargeRouteOptions; cleaning?: CleaningRouteOptions; } declare module 'fastify' { interface FastifyRequest { traceId: string; rawBody: string; } } export async function buildApp(options: BuildAppOptions = {}): Promise { const config = options.config ?? loadConfig(); const app = Fastify({ logger: { level: config.nodeEnv === 'test' ? 'silent' : 'info' }, genReqId: () => randomUUID() }); app.decorateRequest('traceId', ''); app.decorateRequest('rawBody', ''); app.addHook('onRequest', async (request, reply) => { const traceHeader = request.headers['x-trace-id']; request.traceId = Array.isArray(traceHeader) ? traceHeader[0] : traceHeader || request.id; reply.header('x-trace-id', request.traceId); }); await app.register(cors, { origin: config.corsOrigins }); await app.register(rateLimit, { max: 120, timeWindow: '1 minute' }); app.setErrorHandler((error, request, reply) => { request.log.error({ err: error, traceId: request.traceId }, 'request failed'); reply.status(500).send({ code: 'INTERNAL_ERROR', message: 'Internal server error', traceId: request.traceId }); }); await registerHealthRoutes(app, config, options.mqtt); if (options.platformConfigRepository) { await registerPlatformBootstrapRoutes(app, options.platformConfigRepository); } if (options.auth) { await registerAuthRoutes(app, options.auth); } if (options.userManagement) { await registerUserManagementRoutes(app, options.userManagement); } if (options.storeRoom) { await registerStoreRoomRoutes(app, options.storeRoom); } if (options.content) { await registerContentRoutes(app, options.content); } if (options.storeDiscovery) { await registerStoreDiscoveryRoutes(app, options.storeDiscovery); } if (options.storeAccess) { await registerStoreAccessRoutes(app, options.storeAccess); } if (options.pricing) { await registerPricingRoutes(app, options.pricing); } if (options.orderState) { await registerOrderStateRoutes(app, options.orderState); } if (options.orderQuery) { await registerOrderQueryRoutes(app, options.orderQuery); } if (options.orderManagement) { await registerOrderManagementRoutes(app, options.orderManagement); } if (options.orderShare) { await registerOrderShareRoutes(app, options.orderShare); } if (options.payment) { await registerPaymentRoutes(app, options.payment); } if (options.thirdParty) { await registerThirdPartyRoutes(app, options.thirdParty); } if (options.devices) { await registerDeviceRoutes(app, options.devices); } if (options.deviceControl) { await registerDeviceControlRoutes(app, options.deviceControl); } if (options.members) { await registerMemberRoutes(app, options.members); } if (options.recharge) { await registerRechargeRoutes(app, options.recharge); } if (options.cleaning) { await registerCleaningRoutes(app, options.cleaning); } return app; }