From 0b02b30d135165669bcd4887c9e7b51d5f0fe76f Mon Sep 17 00:00:00 2001 From: GSRN Date: Fri, 12 Sep 2025 13:05:35 +0200 Subject: [PATCH] Update API Docs service configuration and enhance error handling; modify ESLint rules to allow console statements and enforce code style standards --- services/api-docs/.eslintrc.js | 8 +- services/api-docs/server.js | 195 +++++++++++++----- .../controller/DashboardController.java | 5 +- 3 files changed, 158 insertions(+), 50 deletions(-) diff --git a/services/api-docs/.eslintrc.js b/services/api-docs/.eslintrc.js index 7b19057..02c1d25 100644 --- a/services/api-docs/.eslintrc.js +++ b/services/api-docs/.eslintrc.js @@ -14,12 +14,16 @@ module.exports = { sourceType: 'module' }, rules: { - 'no-console': 'warn', + 'no-console': 'off', // Allow console statements for API services 'no-unused-vars': 'warn', 'prefer-const': 'error', 'no-var': '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: { process: 'readonly' diff --git a/services/api-docs/server.js b/services/api-docs/server.js index 9040751..f1d1f16 100644 --- a/services/api-docs/server.js +++ b/services/api-docs/server.js @@ -1,16 +1,37 @@ -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 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; +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()); +app.use(cors()) +app.use(express.json()) // Service configurations const SERVICES = { @@ -40,10 +61,10 @@ const SERVICES = { description: 'Notification and alert management service', status: 'planned' } -}; +} // Fetch OpenAPI spec from a service -async function fetchServiceSpec(serviceKey, service) { +async function fetchServiceSpec (serviceKey, service) { try { if (service.status === 'planned') { return { @@ -59,15 +80,15 @@ async function fetchServiceSpec(serviceKey, service) { name: service.name.toLowerCase().replace(/\s+/g, '-'), description: service.description }] - }; + } } const response = await axios.get(`${service.url}${service.openapiPath}`, { timeout: 5000 - }); - return response.data; + }) + return response.data } 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 { openapi: '3.0.0', info: { @@ -82,12 +103,12 @@ async function fetchServiceSpec(serviceKey, service) { description: service.description }], 'x-service-status': 'unavailable' - }; + } } } // Generate unified OpenAPI spec -async function generateUnifiedSpec() { +async function generateUnifiedSpec () { const unifiedSpec = { openapi: '3.0.0', info: { @@ -129,24 +150,24 @@ async function generateUnifiedSpec() { } }, tags: [] - }; + } // Fetch specs from all 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 if (spec.paths) { for (const [path, methods] of Object.entries(spec.paths)) { - const prefixedPath = `/${serviceKey}${path}`; - unifiedSpec.paths[prefixedPath] = methods; + 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); + Object.assign(unifiedSpec.components.schemas, spec.components.schemas) } } @@ -156,54 +177,136 @@ async function generateUnifiedSpec() { description: service.description, 'x-service-url': service.url, 'x-service-status': service.status || 'active' - }); + }) } - return unifiedSpec; + 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() }); -}); + 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 = {}; - + const serviceStatus = {} + for (const [serviceKey, service] of Object.entries(SERVICES)) { try { - const response = await axios.get(`${service.url}/health`, { timeout: 2000 }); + 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 + 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); + const spec = await generateUnifiedSpec() + res.json(spec) } 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 -app.use('/', swaggerUi.serve); +app.use('/', swaggerUi.serve) app.get('/', swaggerUi.setup(null, { swaggerOptions: { url: '/openapi.json', @@ -219,10 +322,10 @@ app.get('/', swaggerUi.setup(null, { .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}`); -}); + console.log(`LabFusion API Docs server running on port ${PORT}`) + console.log(`Access the documentation at: http://localhost:${PORT}`) +}) diff --git a/services/api-gateway/src/main/java/com/labfusion/controller/DashboardController.java b/services/api-gateway/src/main/java/com/labfusion/controller/DashboardController.java index 7cf3152..0ae26a3 100644 --- a/services/api-gateway/src/main/java/com/labfusion/controller/DashboardController.java +++ b/services/api-gateway/src/main/java/com/labfusion/controller/DashboardController.java @@ -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.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -59,7 +60,7 @@ public class DashboardController { if (dashboard.get().getUser().getId().equals(user.getId())) { return ResponseEntity.ok(dashboard.get()); } else { - return ResponseEntity.forbidden().build(); + return ResponseEntity.status(HttpStatusCode.valueOf(403)).build(); } } return ResponseEntity.notFound().build(); @@ -89,6 +90,6 @@ public class DashboardController { dashboardService.deleteDashboard(id); return ResponseEntity.ok().build(); } - return ResponseEntity.forbidden().build(); + return ResponseEntity.status(HttpStatusCode.valueOf(403)).build(); } }