Fix jwt-token
This commit is contained in:
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
const cron = require('node-cron');
|
const cron = require('node-cron');
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
|
const http = require('http');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
// Initialize database connection
|
// Initialize database connection
|
||||||
const { initializeDatabase, getModels } = require('./database');
|
const { initializeDatabase, getModels } = require('./database');
|
||||||
@@ -46,6 +48,9 @@ class DataRetentionService {
|
|||||||
|
|
||||||
console.log('⏰ Scheduled cleanup: Daily at 2:00 AM UTC');
|
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
|
// Run immediate cleanup in development or if IMMEDIATE_CLEANUP is set
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.IMMEDIATE_CLEANUP === 'true') {
|
if (process.env.NODE_ENV === 'development' || process.env.IMMEDIATE_CLEANUP === 'true') {
|
||||||
console.log('🧹 Running immediate cleanup...');
|
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
|
* Graceful shutdown
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ services:
|
|||||||
RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000}
|
RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000}
|
||||||
RATE_LIMIT_MAX_REQUESTS: ${RATE_LIMIT_MAX_REQUESTS:-1000}
|
RATE_LIMIT_MAX_REQUESTS: ${RATE_LIMIT_MAX_REQUESTS:-1000}
|
||||||
SECURITY_LOG_DIR: /app/logs
|
SECURITY_LOG_DIR: /app/logs
|
||||||
|
DATA_RETENTION_HOST: data-retention
|
||||||
|
DATA_RETENTION_PORT: 3001
|
||||||
ports:
|
ports:
|
||||||
- "3002:3001"
|
- "3002:3001"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -188,6 +190,9 @@ services:
|
|||||||
DB_PASSWORD: ${DB_PASSWORD:-your_secure_password}
|
DB_PASSWORD: ${DB_PASSWORD:-your_secure_password}
|
||||||
NODE_ENV: ${NODE_ENV:-production}
|
NODE_ENV: ${NODE_ENV:-production}
|
||||||
IMMEDIATE_CLEANUP: ${IMMEDIATE_CLEANUP:-false}
|
IMMEDIATE_CLEANUP: ${IMMEDIATE_CLEANUP:-false}
|
||||||
|
METRICS_PORT: 3001
|
||||||
|
ports:
|
||||||
|
- "3004:3001" # Expose metrics port
|
||||||
networks:
|
networks:
|
||||||
- drone-network
|
- drone-network
|
||||||
depends_on:
|
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 detectionsRoutes = require('./detections');
|
||||||
const droneTypesRoutes = require('./droneTypes');
|
const droneTypesRoutes = require('./droneTypes');
|
||||||
const tenantDebugRoutes = require('./tenant-debug');
|
const tenantDebugRoutes = require('./tenant-debug');
|
||||||
|
const dataRetentionRoutes = require('./dataRetention');
|
||||||
|
|
||||||
// Management portal routes (before API versioning)
|
// Management portal routes (before API versioning)
|
||||||
router.use('/management', managementRoutes);
|
router.use('/management', managementRoutes);
|
||||||
@@ -49,6 +50,7 @@ router.use('/detectors', detectorsRoutes);
|
|||||||
router.use('/detections', detectionsRoutes);
|
router.use('/detections', detectionsRoutes);
|
||||||
router.use('/drone-types', droneTypesRoutes);
|
router.use('/drone-types', droneTypesRoutes);
|
||||||
router.use('/tenant-debug', tenantDebugRoutes);
|
router.use('/tenant-debug', tenantDebugRoutes);
|
||||||
|
router.use('/data-retention', dataRetentionRoutes);
|
||||||
|
|
||||||
// API documentation endpoint
|
// API documentation endpoint
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
@@ -64,7 +66,16 @@ router.get('/', (req, res) => {
|
|||||||
dashboard: '/api/dashboard',
|
dashboard: '/api/dashboard',
|
||||||
health: '/api/health',
|
health: '/api/health',
|
||||||
'device-health': '/api/device-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'
|
documentation: '/api/docs'
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user