Update API Docs service configuration and enhance error handling; modify ESLint rules to allow console statements and enforce code style standards
Some checks failed
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
API Docs (Node.js Express) / test (16) (push) Failing after 5m20s
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
Integration Tests / integration-tests (push) Has been cancelled
Integration Tests / performance-tests (push) Has been cancelled
API Gateway (Java Spring Boot) / test (17) (push) Failing after 4m55s
API Gateway (Java Spring Boot) / test (21) (push) Failing after 4m54s
API Gateway (Java Spring Boot) / security (push) Has been skipped
API Gateway (Java Spring Boot) / build (push) Has been skipped

This commit is contained in:
GSRN
2025-09-12 13:05:35 +02:00
parent 6d7e7f863d
commit 0b02b30d13
3 changed files with 158 additions and 50 deletions

View File

@@ -14,12 +14,16 @@ module.exports = {
sourceType: 'module' sourceType: 'module'
}, },
rules: { rules: {
'no-console': 'warn', 'no-console': 'off', // Allow console statements for API services
'no-unused-vars': 'warn', 'no-unused-vars': 'warn',
'prefer-const': 'error', 'prefer-const': 'error',
'no-var': 'error', 'no-var': 'error',
'object-shorthand': 'error', 'object-shorthand': 'error',
'prefer-template': 'error' 'prefer-template': 'error',
'no-trailing-spaces': 'error',
'semi': ['error', 'never'], // Enforce no semicolons
'quotes': ['error', 'single'], // Enforce single quotes
'space-before-function-paren': ['error', 'always']
}, },
globals: { globals: {
process: 'readonly' process: 'readonly'

View File

@@ -1,16 +1,37 @@
const express = require('express'); const express = require('express')
const swaggerUi = require('swagger-ui-express'); const swaggerUi = require('swagger-ui-express')
const swaggerJsdoc = require('swagger-jsdoc'); const swaggerJsdoc = require('swagger-jsdoc')
const axios = require('axios'); const axios = require('axios')
const cors = require('cors'); const cors = require('cors')
require('dotenv').config(); require('dotenv').config()
const app = express(); const app = express()
const PORT = process.env.PORT || 8083; 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 // Middleware
app.use(cors()); app.use(cors())
app.use(express.json()); app.use(express.json())
// Service configurations // Service configurations
const SERVICES = { const SERVICES = {
@@ -40,7 +61,7 @@ const SERVICES = {
description: 'Notification and alert management service', description: 'Notification and alert management service',
status: 'planned' status: 'planned'
} }
}; }
// Fetch OpenAPI spec from a service // Fetch OpenAPI spec from a service
async function fetchServiceSpec (serviceKey, service) { async function fetchServiceSpec (serviceKey, service) {
@@ -59,15 +80,15 @@ async function fetchServiceSpec(serviceKey, service) {
name: service.name.toLowerCase().replace(/\s+/g, '-'), name: service.name.toLowerCase().replace(/\s+/g, '-'),
description: service.description description: service.description
}] }]
}; }
} }
const response = await axios.get(`${service.url}${service.openapiPath}`, { const response = await axios.get(`${service.url}${service.openapiPath}`, {
timeout: 5000 timeout: 5000
}); })
return response.data; return response.data
} catch (error) { } catch (error) {
console.warn(`Failed to fetch spec from ${service.name}:`, error.message); console.warn(`Failed to fetch spec from ${service.name}:`, error.message)
return { return {
openapi: '3.0.0', openapi: '3.0.0',
info: { info: {
@@ -82,7 +103,7 @@ async function fetchServiceSpec(serviceKey, service) {
description: service.description description: service.description
}], }],
'x-service-status': 'unavailable' 'x-service-status': 'unavailable'
}; }
} }
} }
@@ -129,24 +150,24 @@ async function generateUnifiedSpec() {
} }
}, },
tags: [] tags: []
}; }
// Fetch specs from all services // Fetch specs from all services
for (const [serviceKey, service] of Object.entries(SERVICES)) { for (const [serviceKey, service] of Object.entries(SERVICES)) {
const spec = await fetchServiceSpec(serviceKey, service); const spec = await fetchServiceSpec(serviceKey, service)
// Merge paths with service prefix // Merge paths with service prefix
if (spec.paths) { if (spec.paths) {
for (const [path, methods] of Object.entries(spec.paths)) { for (const [path, methods] of Object.entries(spec.paths)) {
const prefixedPath = `/${serviceKey}${path}`; const prefixedPath = `/${serviceKey}${path}`
unifiedSpec.paths[prefixedPath] = methods; unifiedSpec.paths[prefixedPath] = methods
} }
} }
// Merge components // Merge components
if (spec.components) { if (spec.components) {
if (spec.components.schemas) { if (spec.components.schemas) {
Object.assign(unifiedSpec.components.schemas, spec.components.schemas); Object.assign(unifiedSpec.components.schemas, spec.components.schemas)
} }
} }
@@ -156,54 +177,136 @@ async function generateUnifiedSpec() {
description: service.description, description: service.description,
'x-service-url': service.url, 'x-service-url': service.url,
'x-service-status': service.status || 'active' 'x-service-status': service.status || 'active'
}); })
} }
return unifiedSpec; return unifiedSpec
} }
// Routes // 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) => { app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() }); 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) => { app.get('/services', async (req, res) => {
const serviceStatus = {}; const serviceStatus = {}
for (const [serviceKey, service] of Object.entries(SERVICES)) { for (const [serviceKey, service] of Object.entries(SERVICES)) {
try { try {
const response = await axios.get(`${service.url}/health`, { timeout: 2000 }); const response = await axios.get(`${service.url}/health`, { timeout: 2000 })
serviceStatus[serviceKey] = { serviceStatus[serviceKey] = {
name: service.name, name: service.name,
url: service.url, url: service.url,
status: 'healthy', status: 'healthy',
responseTime: response.headers['x-response-time'] || 'unknown' responseTime: response.headers['x-response-time'] || 'unknown'
}; }
} catch (error) { } catch (error) {
serviceStatus[serviceKey] = { serviceStatus[serviceKey] = {
name: service.name, name: service.name,
url: service.url, url: service.url,
status: service.status || 'unhealthy', status: service.status || 'unhealthy',
error: error.message error: error.message
}; }
} }
} }
res.json(serviceStatus); res.json(serviceStatus)
}); })
// Dynamic OpenAPI spec endpoint /**
* @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) => { app.get('/openapi.json', async (req, res) => {
try { try {
const spec = await generateUnifiedSpec(); const spec = await generateUnifiedSpec()
res.json(spec); res.json(spec)
} catch (error) { } catch (error) {
res.status(500).json({ error: 'Failed to generate OpenAPI spec', details: error.message }); 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 // Swagger UI
app.use('/', swaggerUi.serve); app.use('/', swaggerUi.serve)
app.get('/', swaggerUi.setup(null, { app.get('/', swaggerUi.setup(null, {
swaggerOptions: { swaggerOptions: {
url: '/openapi.json', url: '/openapi.json',
@@ -219,10 +322,10 @@ app.get('/', swaggerUi.setup(null, {
.swagger-ui .info .title { color: #1890ff; } .swagger-ui .info .title { color: #1890ff; }
`, `,
customSiteTitle: 'LabFusion API Documentation' customSiteTitle: 'LabFusion API Documentation'
})); }))
// Start server // Start server
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`LabFusion API Docs server running on port ${PORT}`); console.log(`LabFusion API Docs server running on port ${PORT}`)
console.log(`Access the documentation at: http://localhost:${PORT}`); console.log(`Access the documentation at: http://localhost:${PORT}`)
}); })

View File

@@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -59,7 +60,7 @@ public class DashboardController {
if (dashboard.get().getUser().getId().equals(user.getId())) { if (dashboard.get().getUser().getId().equals(user.getId())) {
return ResponseEntity.ok(dashboard.get()); return ResponseEntity.ok(dashboard.get());
} else { } else {
return ResponseEntity.forbidden().build(); return ResponseEntity.status(HttpStatusCode.valueOf(403)).build();
} }
} }
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
@@ -89,6 +90,6 @@ public class DashboardController {
dashboardService.deleteDashboard(id); dashboardService.deleteDashboard(id);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
return ResponseEntity.forbidden().build(); return ResponseEntity.status(HttpStatusCode.valueOf(403)).build();
} }
} }