const express = require('express') const swaggerUi = require('swagger-ui-express') const swaggerJsdoc = require('swagger-jsdoc') const axios = require('axios') const cors = require('cors') require('dotenv').config() const app = express() const PORT = process.env.PORT || 8083 // Swagger JSDoc configuration const swaggerOptions = { definition: { openapi: '3.0.0', info: { title: 'LabFusion API Docs Service', version: '1.0.0', description: 'API documentation aggregation service for LabFusion microservices' }, servers: [ { url: `http://localhost:${PORT}`, description: 'API Docs Service' } ] }, apis: ['./server.js'] // Path to the API files } const swaggerSpec = swaggerJsdoc(swaggerOptions) // Middleware app.use(cors()) app.use(express.json()) // Service configurations const SERVICES = { 'api-gateway': { name: 'API Gateway', url: process.env.API_GATEWAY_URL || 'http://localhost:8080', openapiPath: '/v3/api-docs', description: 'Core API gateway for authentication, dashboards, and data management' }, 'service-adapters': { name: 'Service Adapters', url: process.env.SERVICE_ADAPTERS_URL || 'http://localhost:8000', openapiPath: '/openapi.json', description: 'Integration adapters for Home Assistant, Frigate, Immich, and other services' }, 'metrics-collector': { name: 'Metrics Collector', url: process.env.METRICS_COLLECTOR_URL || 'http://localhost:8081', openapiPath: '/openapi.json', description: 'System metrics collection and monitoring service', status: 'planned' }, 'notification-service': { name: 'Notification Service', url: process.env.NOTIFICATION_SERVICE_URL || 'http://localhost:8082', openapiPath: '/openapi.json', description: 'Notification and alert management service', status: 'planned' } } // Fetch OpenAPI spec from a service async function fetchServiceSpec (serviceKey, service) { try { if (service.status === 'planned') { return { openapi: '3.0.0', info: { title: service.name, description: service.description, version: '1.0.0' }, paths: {}, components: {}, tags: [{ name: service.name.toLowerCase().replace(/\s+/g, '-'), description: service.description }] } } const response = await axios.get(`${service.url}${service.openapiPath}`, { timeout: 5000 }) return response.data } catch (error) { console.warn(`Failed to fetch spec from ${service.name}:`, error.message) return { openapi: '3.0.0', info: { title: service.name, description: service.description, version: '1.0.0' }, paths: {}, components: {}, tags: [{ name: service.name.toLowerCase().replace(/\s+/g, '-'), description: service.description }], 'x-service-status': 'unavailable' } } } // Generate unified OpenAPI spec async function generateUnifiedSpec () { const unifiedSpec = { openapi: '3.0.0', info: { title: 'LabFusion API', description: 'Unified API documentation for all LabFusion services', version: '1.0.0', contact: { name: 'LabFusion Team', url: 'https://github.com/labfusion/labfusion' } }, servers: [ { url: 'http://localhost:8080', description: 'API Gateway (Production)' }, { url: 'http://localhost:8000', description: 'Service Adapters (Production)' }, { url: 'http://localhost:8081', description: 'Metrics Collector (Production)' }, { url: 'http://localhost:8082', description: 'Notification Service (Production)' } ], paths: {}, components: { schemas: {}, securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } }, tags: [] } // Fetch specs from all services for (const [serviceKey, service] of Object.entries(SERVICES)) { const spec = await fetchServiceSpec(serviceKey, service) // Merge paths with service prefix if (spec.paths) { for (const [path, methods] of Object.entries(spec.paths)) { const prefixedPath = `/${serviceKey}${path}` unifiedSpec.paths[prefixedPath] = methods } } // Merge components if (spec.components) { if (spec.components.schemas) { Object.assign(unifiedSpec.components.schemas, spec.components.schemas) } } // Add service tag unifiedSpec.tags.push({ name: service.name, description: service.description, 'x-service-url': service.url, 'x-service-status': service.status || 'active' }) } return unifiedSpec } // Routes /** * @swagger * /health: * get: * summary: Health check endpoint * description: Returns the health status of the API Docs service * tags: [Health] * responses: * 200: * description: Service is healthy * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: healthy * timestamp: * type: string * format: date-time * example: "2024-01-01T00:00:00.000Z" */ app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }) }) /** * @swagger * /services: * get: * summary: Get service status * description: Returns the health status of all LabFusion services * tags: [Services] * responses: * 200: * description: Service status information * content: * application/json: * schema: * type: object * additionalProperties: * type: object * properties: * name: * type: string * url: * type: string * status: * type: string * enum: [healthy, unhealthy, planned] * responseTime: * type: string * error: * type: string */ app.get('/services', async (req, res) => { const serviceStatus = {} for (const [serviceKey, service] of Object.entries(SERVICES)) { try { const response = await axios.get(`${service.url}/health`, { timeout: 2000 }) serviceStatus[serviceKey] = { name: service.name, url: service.url, status: 'healthy', responseTime: response.headers['x-response-time'] || 'unknown' } } catch (error) { serviceStatus[serviceKey] = { name: service.name, url: service.url, status: service.status || 'unhealthy', error: error.message } } } res.json(serviceStatus) }) /** * @swagger * /openapi.json: * get: * summary: Get unified OpenAPI specification * description: Returns the unified OpenAPI specification for all LabFusion services * tags: [Documentation] * responses: * 200: * description: Unified OpenAPI specification * content: * application/json: * schema: * type: object * 500: * description: Failed to generate OpenAPI spec * content: * application/json: * schema: * type: object * properties: * error: * type: string * details: * type: string */ app.get('/openapi.json', async (req, res) => { try { const spec = await generateUnifiedSpec() res.json(spec) } catch (error) { res.status(500).json({ error: 'Failed to generate OpenAPI spec', details: error.message }) } }) // API Docs service documentation endpoint app.get('/api-docs.json', (req, res) => { res.json(swaggerSpec) }) // Swagger UI app.use('/', swaggerUi.serve) app.get('/', swaggerUi.setup(null, { swaggerOptions: { url: '/openapi.json', deepLinking: true, displayRequestDuration: true, filter: true, showExtensions: true, showCommonExtensions: true }, customCss: ` .swagger-ui .topbar { display: none; } .swagger-ui .info { margin: 20px 0; } .swagger-ui .info .title { color: #1890ff; } `, customSiteTitle: 'LabFusion API Documentation' })) // Start server app.listen(PORT, () => { console.log(`LabFusion API Docs server running on port ${PORT}`) console.log(`Access the documentation at: http://localhost:${PORT}`) })