Fix jwt-token
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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:
|
||||
|
||||
192
server/routes/dataRetention.js
Normal file
192
server/routes/dataRetention.js
Normal file
@@ -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;
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user