| import Fastify from 'fastify'; |
| import cors from '@fastify/cors'; |
| import websocket from '@fastify/websocket'; |
| import swagger from '@fastify/swagger'; |
| import swaggerUi from '@fastify/swagger-ui'; |
| import { config, validateConfig } from './utils/config.js'; |
| import { logger } from './utils/logger.js'; |
| import { swaggerOptions, swaggerUiOptions } from './utils/swagger.js'; |
| import { MedagenAgent } from './agent/agent-executor.js'; |
| import { SupabaseService } from './services/supabase.service.js'; |
| import { MapsService } from './services/maps.service.js'; |
| import { registerRoutes } from './routes/index.js'; |
| import { wsConnectionManager } from './services/websocket.service.js'; |
|
|
| async function startServer() { |
| try { |
| |
| logger.info('Validating configuration...'); |
| validateConfig(); |
|
|
| |
| const fastify = Fastify({ |
| logger: false, |
| requestIdLogLabel: 'reqId', |
| disableRequestLogging: false, |
| requestIdHeader: 'x-request-id' |
| }); |
|
|
| |
| await fastify.register(cors, { |
| origin: true, |
| credentials: true |
| }); |
|
|
| |
| await fastify.register(websocket, { |
| options: { |
| maxPayload: 10 * 1024, |
| verifyClient: (_info, callback) => { |
| |
| callback(true); |
| } |
| } |
| }); |
|
|
| |
| await fastify.register(swagger as any, swaggerOptions); |
| await fastify.register(swaggerUi, swaggerUiOptions); |
|
|
| |
| logger.info('Initializing services...'); |
| const supabaseService = new SupabaseService(); |
| const mapsService = new MapsService(); |
| |
| |
| logger.info('Initializing Medagen Agent...'); |
| const agent = new MedagenAgent(supabaseService, mapsService); |
| await agent.initialize(); |
|
|
| |
| logger.info('Registering routes...'); |
| await registerRoutes(fastify, agent, supabaseService, mapsService); |
|
|
| |
| fastify.addHook('onRequest', async (request) => { |
| logger.info({ |
| method: request.method, |
| url: request.url, |
| ip: request.ip |
| }, 'Incoming request'); |
| }); |
|
|
| |
| fastify.addHook('onResponse', async (request, reply) => { |
| logger.info({ |
| method: request.method, |
| url: request.url, |
| statusCode: reply.statusCode |
| }, 'Request completed'); |
| }); |
|
|
| |
| fastify.setErrorHandler((error, _request, reply) => { |
| |
| if (error && typeof error === 'object' && 'validation' in error && error.validation) { |
| const validationError = error as { validation?: unknown; message?: string }; |
| logger.warn({ validation: error.validation }, 'Validation error'); |
| return reply.status(400).send({ |
| error: 'Validation Error', |
| message: validationError.message || 'Validation failed', |
| details: error.validation |
| }); |
| } |
|
|
| |
| const errorMessage = error instanceof Error ? error.message : String(error); |
| logger.error({ error }, 'Unhandled error'); |
| |
| return reply.status(500).send({ |
| error: 'Internal Server Error', |
| message: process.env.NODE_ENV === 'development' ? errorMessage : 'An error occurred' |
| }); |
| }); |
|
|
| |
| const port = config.port; |
| const host = process.env.HOST || '0.0.0.0'; |
|
|
| await fastify.listen({ port, host }); |
|
|
| logger.info(`π Medagen Backend is running on http://${host}:${port}`); |
| logger.info(`π Health check: http://${host}:${port}/health`); |
| logger.info(`π₯ Triage endpoint: http://${host}:${port}/api/health-check`); |
| logger.info(`π WebSocket endpoint: ws://${host}:${port}/ws/chat`); |
| logger.info(`π Swagger docs: http://${host}:${port}/docs`); |
| } catch (error) { |
| logger.error({ error }, 'Failed to start server'); |
| process.exit(1); |
| } |
| } |
|
|
| |
| process.on('SIGINT', () => { |
| logger.info('Received SIGINT, shutting down gracefully...'); |
| wsConnectionManager.destroy(); |
| process.exit(0); |
| }); |
|
|
| process.on('SIGTERM', () => { |
| logger.info('Received SIGTERM, shutting down gracefully...'); |
| wsConnectionManager.destroy(); |
| process.exit(0); |
| }); |
|
|
| |
| startServer(); |
|
|
|
|