Fix connector start dates for SH-Meters and XMeters

Updated hardcoded dates from 2025 to 2026:
- SH-Meters: 2026-01-12
- XMeters: 2026-01-25

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Exteban08
2026-02-03 21:06:19 +00:00
parent 27494e7868
commit 6487e9105e

View File

@@ -0,0 +1,330 @@
import { Router, Response } from 'express';
import os from 'os';
import { authenticateToken, requireRole } from '../middleware/auth.middleware';
import { AuthenticatedRequest } from '../types';
import pool from '../config/database';
const router = Router();
// Track request metrics (in-memory for simplicity)
let requestMetrics = {
total: 0,
errors: 0,
totalResponseTime: 0,
};
// Middleware to track requests (exported for use in main app)
export function trackRequest(responseTime: number, isError: boolean) {
requestMetrics.total++;
requestMetrics.totalResponseTime += responseTime;
if (isError) {
requestMetrics.errors++;
}
}
/**
* GET /api/system/metrics
* Get server metrics (Admin only)
*/
router.get(
'/metrics',
authenticateToken,
requireRole('ADMIN'),
async (_req: AuthenticatedRequest, res: Response) => {
try {
// Test database connection
let dbConnected = false;
let dbResponseTime = 0;
try {
const startTime = Date.now();
await pool.query('SELECT 1');
dbResponseTime = Date.now() - startTime;
dbConnected = true;
} catch {
dbConnected = false;
}
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const metrics = {
uptime: process.uptime(),
memory: {
total: totalMem,
used: usedMem,
free: freeMem,
percentage: (usedMem / totalMem) * 100,
},
cpu: {
usage: os.loadavg()[0] * 10, // Approximate CPU percentage from load average
cores: os.cpus().length,
},
requests: {
total: requestMetrics.total,
errors: requestMetrics.errors,
avgResponseTime: requestMetrics.total > 0
? requestMetrics.totalResponseTime / requestMetrics.total
: 0,
},
database: {
connected: dbConnected,
responseTime: dbResponseTime,
},
timestamp: new Date().toISOString(),
};
res.json({
success: true,
data: metrics,
});
} catch (error) {
console.error('Error getting system metrics:', error);
res.status(500).json({
success: false,
error: 'Failed to get system metrics',
});
}
}
);
/**
* GET /api/system/health
* Detailed health check (Admin only)
*/
router.get(
'/health',
authenticateToken,
requireRole('ADMIN'),
async (_req: AuthenticatedRequest, res: Response) => {
try {
let dbConnected = false;
try {
await pool.query('SELECT 1');
dbConnected = true;
} catch {
dbConnected = false;
}
res.json({
success: true,
data: {
status: dbConnected ? 'healthy' : 'degraded',
database: dbConnected,
uptime: process.uptime(),
},
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Health check failed',
});
}
}
);
/**
* GET /api/system/meters-locations
* Get meters with coordinates for map (Admin only)
*/
router.get(
'/meters-locations',
authenticateToken,
requireRole('ADMIN'),
async (_req: AuthenticatedRequest, res: Response) => {
try {
// Query meters with their coordinates and latest reading
const result = await pool.query(`
SELECT
m.id,
m.serial_number,
m.name,
m.status,
p.name as project_name,
m.latitude as lat,
m.longitude as lng,
m.last_reading_value as last_reading,
m.last_reading_at as last_reading_date
FROM meters m
LEFT JOIN projects p ON m.project_id = p.id
WHERE m.latitude IS NOT NULL AND m.longitude IS NOT NULL
ORDER BY m.name
`);
res.json({
success: true,
data: result.rows,
});
} catch (error) {
console.error('Error getting meter locations:', error);
res.status(500).json({
success: false,
error: 'Failed to get meter locations',
});
}
}
);
/**
* GET /api/system/report-stats
* Get statistics for reports dashboard (Admin only)
*/
router.get(
'/report-stats',
authenticateToken,
requireRole('ADMIN'),
async (_req: AuthenticatedRequest, res: Response) => {
try {
// Get meter counts
const meterCountsResult = await pool.query(`
SELECT
COUNT(*) as total,
COUNT(*) FILTER (WHERE status = 'active') as active,
COUNT(*) FILTER (WHERE status != 'active') as inactive
FROM meters
`);
// Get total consumption from meters (last_reading_value)
const consumptionResult = await pool.query(`
SELECT COALESCE(SUM(last_reading_value), 0) as total_consumption
FROM meters
WHERE last_reading_value IS NOT NULL
`);
// Get project count
const projectCountResult = await pool.query(`
SELECT COUNT(*) as total FROM projects
`);
// Get meters with alerts (negative flow)
const alertsResult = await pool.query(`
SELECT COUNT(*) as count
FROM meters
WHERE current_flow < 0 OR total_flow_reverse > 0
`);
// Get consumption by project
const consumptionByProjectResult = await pool.query(`
SELECT
p.name as project_name,
COALESCE(SUM(m.last_reading_value), 0) as total_consumption,
COUNT(m.id) as meter_count
FROM projects p
LEFT JOIN meters m ON m.project_id = p.id
GROUP BY p.id, p.name
ORDER BY total_consumption DESC
LIMIT 10
`);
// Get consumption trend (last 6 months from meter_readings)
let trendResult = { rows: [] as any[] };
try {
trendResult = await pool.query(`
SELECT
TO_CHAR(DATE_TRUNC('month', received_at), 'YYYY-MM') as date,
SUM(reading_value) as consumption
FROM meter_readings
WHERE received_at >= NOW() - INTERVAL '6 months'
GROUP BY DATE_TRUNC('month', received_at)
ORDER BY date
`);
} catch (e) {
// meter_readings table might not exist, use empty array
console.log('meter_readings query failed, using empty trend data');
}
const meterCounts = meterCountsResult.rows[0];
res.json({
success: true,
data: {
totalMeters: parseInt(meterCounts.total) || 0,
activeMeters: parseInt(meterCounts.active) || 0,
inactiveMeters: parseInt(meterCounts.inactive) || 0,
totalConsumption: parseFloat(consumptionResult.rows[0]?.total_consumption) || 0,
totalProjects: parseInt(projectCountResult.rows[0]?.total) || 0,
metersWithAlerts: parseInt(alertsResult.rows[0]?.count) || 0,
consumptionByProject: consumptionByProjectResult.rows.map(row => ({
project_name: row.project_name,
total_consumption: parseFloat(row.total_consumption) || 0,
meter_count: parseInt(row.meter_count) || 0,
})),
consumptionTrend: trendResult.rows.map(row => ({
date: row.date,
consumption: parseFloat(row.consumption) || 0,
})),
},
});
} catch (error) {
console.error('Error getting report stats:', error);
res.status(500).json({
success: false,
error: 'Failed to get report statistics',
});
}
}
);
/**
* GET /api/system/connector-stats/:type
* Get connector statistics (Admin only)
*/
router.get(
'/connector-stats/:type',
authenticateToken,
requireRole('ADMIN'),
async (req: AuthenticatedRequest, res: Response) => {
try {
const { type } = req.params;
let meterType = '';
if (type === 'sh-meters') {
meterType = 'LORA';
} else if (type === 'xmeters') {
meterType = 'GRANDES';
}
// Get meter count by type
const meterCountResult = await pool.query(
`SELECT COUNT(*) as count FROM meters WHERE UPPER(type) = $1`,
[meterType]
);
const meterCount = parseInt(meterCountResult.rows[0]?.count) || 0;
// Start dates for each connector
const connectorStartDates: Record<string, Date> = {
'sh-meters': new Date('2026-01-12'),
'xmeters': new Date('2026-01-25'),
};
const startDate = connectorStartDates[type] || new Date();
const today = new Date();
const diffTime = Math.abs(today.getTime() - startDate.getTime());
const daysSinceStart = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
// Messages = meters * days (one message per meter per day)
const messagesReceived = meterCount * daysSinceStart;
res.json({
success: true,
data: {
meterCount,
messagesReceived,
daysSinceStart,
meterType,
},
});
} catch (error) {
console.error('Error getting connector stats:', error);
res.status(500).json({
success: false,
error: 'Failed to get connector statistics',
});
}
}
);
export default router;