Some checks failed
API Docs (Node.js Express) / test (16) (push) Failing after 5m42s
API Docs (Node.js Express) / test (20) (push) Has been cancelled
API Docs (Node.js Express) / build (push) Has been cancelled
API Docs (Node.js Express) / security (push) Has been cancelled
API Docs (Node.js Express) / test (18) (push) Has been cancelled
LabFusion CI/CD Pipeline / api-gateway (push) Has been cancelled
LabFusion CI/CD Pipeline / service-adapters (push) Has been cancelled
LabFusion CI/CD Pipeline / api-docs (push) Has been cancelled
LabFusion CI/CD Pipeline / frontend (push) Has been cancelled
LabFusion CI/CD Pipeline / integration-tests (push) Has been cancelled
LabFusion CI/CD Pipeline / security-scan (push) Has been cancelled
Docker Build and Push / build-and-push (push) Has been cancelled
Docker Build and Push / security-scan (push) Has been cancelled
Docker Build and Push / deploy-staging (push) Has been cancelled
Docker Build and Push / deploy-production (push) Has been cancelled
Frontend (React) / test (16) (push) Has been cancelled
Frontend (React) / test (18) (push) Has been cancelled
Frontend (React) / test (20) (push) Has been cancelled
Frontend (React) / build (push) Has been cancelled
Frontend (React) / lighthouse (push) Has been cancelled
Frontend (React) / security (push) Has been cancelled
Integration Tests / performance-tests (push) Has been cancelled
Service Adapters (Python FastAPI) / security (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.1) (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.11) (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.12) (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.9) (push) Has been cancelled
Service Adapters (Python FastAPI) / build (push) Has been cancelled
Integration Tests / integration-tests (push) Has been cancelled
337 lines
8.8 KiB
JavaScript
337 lines
8.8 KiB
JavaScript
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'
|
|
}))
|
|
|
|
// Export app for testing
|
|
module.exports = app
|
|
|
|
// Start server only if not in test environment
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
app.listen(PORT, () => {
|
|
console.log(`LabFusion API Docs server running on port ${PORT}`)
|
|
console.log(`Access the documentation at: http://localhost:${PORT}`)
|
|
})
|
|
}
|