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; // 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 app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }); }); 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); }); // Dynamic OpenAPI spec endpoint 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 }); } }); // 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}`); });