Add API Documentation Service and enhance existing services with OpenAPI support
This commit is contained in:
18
services/api-docs/Dockerfile
Normal file
18
services/api-docs/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --only=production
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8083
|
||||
|
||||
# Start the application
|
||||
CMD ["npm", "start"]
|
||||
18
services/api-docs/Dockerfile.dev
Normal file
18
services/api-docs/Dockerfile.dev
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8083
|
||||
|
||||
# Start the application in development mode
|
||||
CMD ["npm", "run", "dev"]
|
||||
30
services/api-docs/README.md
Normal file
30
services/api-docs/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# API Documentation Service
|
||||
|
||||
A unified API documentation service that aggregates OpenAPI specifications from all LabFusion services.
|
||||
|
||||
## Purpose
|
||||
- Provide a single entry point for all API documentation
|
||||
- Aggregate OpenAPI specs from all active services
|
||||
- Display unified Swagger UI for the entire LabFusion ecosystem
|
||||
- Monitor service health and availability
|
||||
|
||||
## Technology Stack
|
||||
- **Language**: Node.js
|
||||
- **Port**: 8083
|
||||
- **Dependencies**: Express, Swagger UI, Axios
|
||||
|
||||
## Features
|
||||
- **Unified Documentation**: Single Swagger UI for all services
|
||||
- **Service Health Monitoring**: Real-time status of all services
|
||||
- **Dynamic Spec Generation**: Automatically fetches and merges OpenAPI specs
|
||||
- **Service Prefixing**: Each service's endpoints are prefixed for clarity
|
||||
- **Fallback Handling**: Graceful handling of unavailable services
|
||||
|
||||
## API Endpoints
|
||||
- `GET /` - Swagger UI interface
|
||||
- `GET /openapi.json` - Unified OpenAPI specification
|
||||
- `GET /services` - Service health status
|
||||
- `GET /health` - Documentation service health
|
||||
|
||||
## Development Status
|
||||
✅ **Complete** - Ready for use
|
||||
30
services/api-docs/package.json
Normal file
30
services/api-docs/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "labfusion-api-docs",
|
||||
"version": "1.0.0",
|
||||
"description": "Unified API documentation service for LabFusion",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"axios": "^1.6.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
},
|
||||
"keywords": [
|
||||
"api",
|
||||
"documentation",
|
||||
"swagger",
|
||||
"openapi",
|
||||
"labfusion"
|
||||
],
|
||||
"author": "LabFusion Team",
|
||||
"license": "MIT"
|
||||
}
|
||||
228
services/api-docs/server.js
Normal file
228
services/api-docs/server.js
Normal file
@@ -0,0 +1,228 @@
|
||||
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}`);
|
||||
});
|
||||
Reference in New Issue
Block a user