From e3816b056ec62745977b6ff31eb8ee9651ed2149 Mon Sep 17 00:00:00 2001 From: Alexander Borg Date: Tue, 23 Sep 2025 15:03:31 +0200 Subject: [PATCH] Fix jwt-token --- data-retention-service/index.js | 124 +++++++++++++++++++++ docker-compose.yml | 5 + server/routes/dataRetention.js | 192 ++++++++++++++++++++++++++++++++ server/routes/index.js | 13 ++- 4 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 server/routes/dataRetention.js diff --git a/data-retention-service/index.js b/data-retention-service/index.js index e397b35..51f14cb 100644 --- a/data-retention-service/index.js +++ b/data-retention-service/index.js @@ -5,6 +5,8 @@ const cron = require('node-cron'); const { Op } = require('sequelize'); +const http = require('http'); +const url = require('url'); // Initialize database connection const { initializeDatabase, getModels } = require('./database'); @@ -46,6 +48,9 @@ class DataRetentionService { console.log('โฐ Scheduled cleanup: Daily at 2:00 AM UTC'); + // Start metrics HTTP server + this.startMetricsServer(); + // Run immediate cleanup in development or if IMMEDIATE_CLEANUP is set if (process.env.NODE_ENV === 'development' || process.env.IMMEDIATE_CLEANUP === 'true') { console.log('๐Ÿงน Running immediate cleanup...'); @@ -233,6 +238,125 @@ class DataRetentionService { }; } + /** + * Get detailed metrics for dashboard + */ + getMetrics() { + const uptime = Math.floor(process.uptime()); + const memoryUsage = process.memoryUsage(); + + return { + service: { + name: 'data-retention-service', + version: '1.0.0', + status: 'running', + uptime: uptime, + uptimeFormatted: this.formatUptime(uptime) + }, + performance: { + memoryUsage: { + heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024), + heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024), + external: Math.round(memoryUsage.external / 1024 / 1024), + rss: Math.round(memoryUsage.rss / 1024 / 1024) + }, + cpuUsage: process.cpuUsage() + }, + cleanup: { + lastRun: this.lastCleanup, + lastRunFormatted: this.lastCleanup ? new Date(this.lastCleanup).toLocaleString() : null, + isCurrentlyRunning: this.isRunning, + nextScheduledRun: '2:00 AM UTC daily', + stats: this.cleanupStats + }, + schedule: { + cronExpression: '0 2 * * *', + timezone: 'UTC', + description: 'Daily cleanup at 2:00 AM UTC' + } + }; + } + + /** + * Format uptime in human readable format + */ + formatUptime(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = seconds % 60; + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m ${secs}s`; + } else if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s`; + } else if (minutes > 0) { + return `${minutes}m ${secs}s`; + } else { + return `${secs}s`; + } + } + + /** + * Start HTTP server for metrics endpoint + */ + startMetricsServer() { + const port = process.env.METRICS_PORT || 3001; + + const server = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url, true); + + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + res.setHeader('Content-Type', 'application/json'); + + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + + if (req.method === 'GET') { + if (parsedUrl.pathname === '/metrics') { + // Detailed metrics for dashboard + res.writeHead(200); + res.end(JSON.stringify(this.getMetrics(), null, 2)); + + } else if (parsedUrl.pathname === '/health') { + // Simple health check + res.writeHead(200); + res.end(JSON.stringify({ + status: 'healthy', + uptime: Math.floor(process.uptime()), + lastCleanup: this.lastCleanup, + isRunning: this.isRunning + }, null, 2)); + + } else if (parsedUrl.pathname === '/stats') { + // Basic stats + res.writeHead(200); + res.end(JSON.stringify(this.getStats(), null, 2)); + + } else { + res.writeHead(404); + res.end(JSON.stringify({ error: 'Not found' })); + } + } else { + res.writeHead(405); + res.end(JSON.stringify({ error: 'Method not allowed' })); + } + }); + + server.listen(port, () => { + console.log(`๐Ÿ“Š Metrics server listening on port ${port}`); + console.log(`๐Ÿ“Š Endpoints: /health, /metrics, /stats`); + }); + + return server; + } + /** * Graceful shutdown */ diff --git a/docker-compose.yml b/docker-compose.yml index 1403a33..9cced71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,8 @@ services: RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000} RATE_LIMIT_MAX_REQUESTS: ${RATE_LIMIT_MAX_REQUESTS:-1000} SECURITY_LOG_DIR: /app/logs + DATA_RETENTION_HOST: data-retention + DATA_RETENTION_PORT: 3001 ports: - "3002:3001" volumes: @@ -188,6 +190,9 @@ services: DB_PASSWORD: ${DB_PASSWORD:-your_secure_password} NODE_ENV: ${NODE_ENV:-production} IMMEDIATE_CLEANUP: ${IMMEDIATE_CLEANUP:-false} + METRICS_PORT: 3001 + ports: + - "3004:3001" # Expose metrics port networks: - drone-network depends_on: diff --git a/server/routes/dataRetention.js b/server/routes/dataRetention.js new file mode 100644 index 0000000..6725b09 --- /dev/null +++ b/server/routes/dataRetention.js @@ -0,0 +1,192 @@ +const express = require('express'); +const router = express.Router(); +const http = require('http'); + +/** + * Data Retention Metrics Proxy + * Proxies requests to the data retention microservice + */ + +const DATA_RETENTION_HOST = process.env.DATA_RETENTION_HOST || 'data-retention'; +const DATA_RETENTION_PORT = process.env.DATA_RETENTION_PORT || 3001; + +/** + * Make HTTP request to data retention service + */ +function makeRequest(path) { + return new Promise((resolve, reject) => { + const options = { + hostname: DATA_RETENTION_HOST, + port: DATA_RETENTION_PORT, + path: path, + method: 'GET', + timeout: 5000 + }; + + const req = http.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + try { + const result = JSON.parse(data); + resolve({ status: res.statusCode, data: result }); + } catch (error) { + reject(new Error(`Failed to parse response: ${error.message}`)); + } + }); + }); + + req.on('error', (error) => { + reject(new Error(`Request failed: ${error.message}`)); + }); + + req.on('timeout', () => { + req.destroy(); + reject(new Error('Request timeout')); + }); + + req.end(); + }); +} + +/** + * GET /api/data-retention/metrics + * Get detailed metrics from data retention service + */ +router.get('/metrics', async (req, res) => { + try { + const response = await makeRequest('/metrics'); + + if (response.status === 200) { + res.json({ + success: true, + data: response.data, + timestamp: new Date().toISOString() + }); + } else { + res.status(response.status).json({ + success: false, + error: 'Failed to fetch metrics from data retention service' + }); + } + } catch (error) { + console.error('Data retention metrics error:', error); + res.status(503).json({ + success: false, + error: 'Data retention service unavailable', + details: error.message + }); + } +}); + +/** + * GET /api/data-retention/health + * Get health status from data retention service + */ +router.get('/health', async (req, res) => { + try { + const response = await makeRequest('/health'); + + if (response.status === 200) { + res.json({ + success: true, + data: response.data, + timestamp: new Date().toISOString() + }); + } else { + res.status(response.status).json({ + success: false, + error: 'Failed to fetch health from data retention service' + }); + } + } catch (error) { + console.error('Data retention health error:', error); + res.status(503).json({ + success: false, + error: 'Data retention service unavailable', + details: error.message + }); + } +}); + +/** + * GET /api/data-retention/stats + * Get basic statistics from data retention service + */ +router.get('/stats', async (req, res) => { + try { + const response = await makeRequest('/stats'); + + if (response.status === 200) { + res.json({ + success: true, + data: response.data, + timestamp: new Date().toISOString() + }); + } else { + res.status(response.status).json({ + success: false, + error: 'Failed to fetch stats from data retention service' + }); + } + } catch (error) { + console.error('Data retention stats error:', error); + res.status(503).json({ + success: false, + error: 'Data retention service unavailable', + details: error.message + }); + } +}); + +/** + * GET /api/data-retention/status + * Get combined status including service connectivity + */ +router.get('/status', async (req, res) => { + try { + const [healthResponse, metricsResponse] = await Promise.allSettled([ + makeRequest('/health'), + makeRequest('/metrics') + ]); + + const result = { + success: true, + service: { + name: 'data-retention-service', + connected: false, + error: null + }, + health: null, + metrics: null, + timestamp: new Date().toISOString() + }; + + if (healthResponse.status === 'fulfilled' && healthResponse.value.status === 200) { + result.service.connected = true; + result.health = healthResponse.value.data; + } else { + result.service.error = healthResponse.reason?.message || 'Health check failed'; + } + + if (metricsResponse.status === 'fulfilled' && metricsResponse.value.status === 200) { + result.metrics = metricsResponse.value.data; + } + + res.json(result); + + } catch (error) { + console.error('Data retention status error:', error); + res.status(503).json({ + success: false, + error: 'Failed to get data retention service status', + details: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js index 19ccb12..49984a1 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -16,6 +16,7 @@ const detectorsRoutes = require('./detectors'); const detectionsRoutes = require('./detections'); const droneTypesRoutes = require('./droneTypes'); const tenantDebugRoutes = require('./tenant-debug'); +const dataRetentionRoutes = require('./dataRetention'); // Management portal routes (before API versioning) router.use('/management', managementRoutes); @@ -49,6 +50,7 @@ router.use('/detectors', detectorsRoutes); router.use('/detections', detectionsRoutes); router.use('/drone-types', droneTypesRoutes); router.use('/tenant-debug', tenantDebugRoutes); +router.use('/data-retention', dataRetentionRoutes); // API documentation endpoint router.get('/', (req, res) => { @@ -64,7 +66,16 @@ router.get('/', (req, res) => { dashboard: '/api/dashboard', health: '/api/health', 'device-health': '/api/device-health', - 'drone-types': '/api/drone-types' + 'drone-types': '/api/drone-types', + 'data-retention': '/api/data-retention' + }, + microservices: { + 'data-retention': { + status: '/api/data-retention/status', + health: '/api/data-retention/health', + metrics: '/api/data-retention/metrics', + stats: '/api/data-retention/stats' + } }, documentation: '/api/docs' });