iot-agent/routes/devices.js
2025-08-03 23:07:33 +05:30

544 lines
14 KiB
JavaScript

const express = require('express');
const { body, validationResult } = require('express-validator');
const { protect, authorize } = require('../middleware/auth');
const database = require('../config/database');
const redis = require('../config/redis');
const streamPipesService = require('../services/streamPipesService');
const logger = require('../utils/logger');
const router = express.Router();
// Get all devices
router.get('/', protect, async (req, res) => {
try {
const { page = 1, limit = 10, status, device_type } = req.query;
const offset = (page - 1) * limit;
let query = 'SELECT * FROM devices WHERE 1=1';
const params = [];
if (status) {
query += ' AND status = ?';
params.push(status);
}
if (device_type) {
query += ' AND device_type = ?';
params.push(device_type);
}
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
params.push(parseInt(limit), offset);
const devices = await database.query(query, params);
// Get total count
let countQuery = 'SELECT COUNT(*) as total FROM devices WHERE 1=1';
const countParams = [];
if (status) {
countQuery += ' AND status = ?';
countParams.push(status);
}
if (device_type) {
countQuery += ' AND device_type = ?';
countParams.push(device_type);
}
const [countResult] = await database.query(countQuery, countParams);
const total = countResult.total;
res.json({
success: true,
data: devices,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
logger.error('Get devices error:', error);
res.status(500).json({
success: false,
message: 'Failed to get devices'
});
}
});
// Get device by ID
router.get('/:deviceId', protect, async (req, res) => {
try {
const { deviceId } = req.params;
const devices = await database.query(
'SELECT * FROM devices WHERE device_id = ?',
[deviceId]
);
if (devices.length === 0) {
return res.status(404).json({
success: false,
message: 'Device not found'
});
}
// Get latest device data from Redis
const latestData = await redis.getDeviceData(deviceId);
const device = {
...devices[0],
latest_data: latestData
};
res.json({
success: true,
data: device
});
} catch (error) {
logger.error('Get device error:', error);
res.status(500).json({
success: false,
message: 'Failed to get device'
});
}
});
// Create new device
router.post('/', protect, authorize('admin', 'operator'), [
body('device_id').notEmpty().withMessage('Device ID is required'),
body('name').notEmpty().withMessage('Device name is required'),
body('device_type').notEmpty().withMessage('Device type is required')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { device_id, name, device_type, location, configuration } = req.body;
// Check if device already exists
const existingDevices = await database.query(
'SELECT id FROM devices WHERE device_id = ?',
[device_id]
);
if (existingDevices.length > 0) {
return res.status(400).json({
success: false,
message: 'Device ID already exists'
});
}
// Create device
const result = await database.query(
'INSERT INTO devices (device_id, name, device_type, location, configuration) VALUES (?, ?, ?, ?, ?)',
[device_id, name, device_type, location, JSON.stringify(configuration || {})]
);
logger.logSecurity('device_created', { device_id, name, created_by: req.user.id }, 'info');
res.status(201).json({
success: true,
message: 'Device created successfully',
data: {
id: result.insertId,
device_id,
name,
device_type,
location,
configuration
}
});
} catch (error) {
logger.error('Create device error:', error);
res.status(500).json({
success: false,
message: 'Failed to create device'
});
}
});
// Update device
router.put('/:deviceId', protect, authorize('admin', 'operator'), [
body('name').optional().notEmpty().withMessage('Device name cannot be empty'),
body('device_type').optional().notEmpty().withMessage('Device type cannot be empty')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { deviceId } = req.params;
const { name, device_type, location, configuration, status } = req.body;
// Check if device exists
const existingDevices = await database.query(
'SELECT id FROM devices WHERE device_id = ?',
[deviceId]
);
if (existingDevices.length === 0) {
return res.status(404).json({
success: false,
message: 'Device not found'
});
}
// Build update query
const updates = {};
const params = [];
if (name) {
updates.name = name;
params.push(name);
}
if (device_type) {
updates.device_type = device_type;
params.push(device_type);
}
if (location !== undefined) {
updates.location = location;
params.push(location);
}
if (configuration !== undefined) {
updates.configuration = JSON.stringify(configuration);
params.push(JSON.stringify(configuration));
}
if (status) {
updates.status = status;
params.push(status);
}
if (Object.keys(updates).length === 0) {
return res.status(400).json({
success: false,
message: 'No updates provided'
});
}
const updateFields = Object.keys(updates).map(key => `${key} = ?`).join(', ');
params.push(deviceId);
await database.query(
`UPDATE devices SET ${updateFields} WHERE device_id = ?`,
params
);
logger.logSecurity('device_updated', { device_id: deviceId, updated_by: req.user.id }, 'info');
res.json({
success: true,
message: 'Device updated successfully'
});
} catch (error) {
logger.error('Update device error:', error);
res.status(500).json({
success: false,
message: 'Failed to update device'
});
}
});
// Delete device
router.delete('/:deviceId', protect, authorize('admin'), async (req, res) => {
try {
const { deviceId } = req.params;
// Check if device exists
const existingDevices = await database.query(
'SELECT id FROM devices WHERE device_id = ?',
[deviceId]
);
if (existingDevices.length === 0) {
return res.status(404).json({
success: false,
message: 'Device not found'
});
}
// Delete device
await database.query(
'DELETE FROM devices WHERE device_id = ?',
[deviceId]
);
// Clear device data from Redis
await redis.del(`device:${deviceId}:data`);
logger.logSecurity('device_deleted', { device_id: deviceId, deleted_by: req.user.id }, 'info');
res.json({
success: true,
message: 'Device deleted successfully'
});
} catch (error) {
logger.error('Delete device error:', error);
res.status(500).json({
success: false,
message: 'Failed to delete device'
});
}
});
// Get device data
router.get('/:deviceId/data', protect, async (req, res) => {
try {
const { deviceId } = req.params;
const { limit = 100, start_date, end_date } = req.query;
// Check if device exists
const existingDevices = await database.query(
'SELECT id FROM devices WHERE device_id = ?',
[deviceId]
);
if (existingDevices.length === 0) {
return res.status(404).json({
success: false,
message: 'Device not found'
});
}
let query = 'SELECT * FROM device_data WHERE device_id = ?';
const params = [deviceId];
if (start_date && end_date) {
query += ' AND timestamp BETWEEN ? AND ?';
params.push(start_date, end_date);
}
query += ' ORDER BY timestamp DESC LIMIT ?';
params.push(parseInt(limit));
const data = await database.query(query, params);
res.json({
success: true,
data: data
});
} catch (error) {
logger.error('Get device data error:', error);
res.status(500).json({
success: false,
message: 'Failed to get device data'
});
}
});
// Send command to device
router.post('/:deviceId/command', protect, authorize('admin', 'operator'), [
body('command').notEmpty().withMessage('Command is required'),
body('parameters').optional().isObject().withMessage('Parameters must be an object')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { deviceId } = req.params;
const { command, parameters = {} } = req.body;
// Check if device exists
const existingDevices = await database.query(
'SELECT id FROM devices WHERE device_id = ?',
[deviceId]
);
if (existingDevices.length === 0) {
return res.status(404).json({
success: false,
message: 'Device not found'
});
}
// Store command in database
const result = await database.query(
'INSERT INTO device_controls (device_id, action, parameters, initiated_by, status) VALUES (?, ?, ?, ?, ?)',
[deviceId, command, JSON.stringify(parameters), req.user.id, 'pending']
);
logger.logSecurity('device_command_sent', {
device_id: deviceId,
command,
parameters,
initiated_by: req.user.id
}, 'info');
res.json({
success: true,
message: 'Command sent successfully',
data: {
control_id: result.insertId,
device_id: deviceId,
command,
parameters,
status: 'pending'
}
});
} catch (error) {
logger.error('Send device command error:', error);
res.status(500).json({
success: false,
message: 'Failed to send command'
});
}
});
// Get device statistics
router.get('/:deviceId/stats', protect, async (req, res) => {
try {
const { deviceId } = req.params;
const { period = '24h' } = req.query;
// Check if device exists
const existingDevices = await database.query(
'SELECT id FROM devices WHERE device_id = ?',
[deviceId]
);
if (existingDevices.length === 0) {
return res.status(404).json({
success: false,
message: 'Device not found'
});
}
let timeFilter;
switch (period) {
case '1h':
timeFilter = 'DATE_SUB(NOW(), INTERVAL 1 HOUR)';
break;
case '24h':
timeFilter = 'DATE_SUB(NOW(), INTERVAL 24 HOUR)';
break;
case '7d':
timeFilter = 'DATE_SUB(NOW(), INTERVAL 7 DAY)';
break;
case '30d':
timeFilter = 'DATE_SUB(NOW(), INTERVAL 30 DAY)';
break;
default:
timeFilter = 'DATE_SUB(NOW(), INTERVAL 24 HOUR)';
}
// Get data count
const [dataCount] = await database.query(
`SELECT COUNT(*) as count FROM device_data WHERE device_id = ? AND created_at >= ${timeFilter}`,
[deviceId]
);
// Get latest data
const [latestData] = await database.query(
'SELECT * FROM device_data WHERE device_id = ? ORDER BY timestamp DESC LIMIT 1',
[deviceId]
);
// Get alerts count
const [alertsCount] = await database.query(
`SELECT COUNT(*) as count FROM alerts WHERE device_id = ? AND created_at >= ${timeFilter}`,
[deviceId]
);
const stats = {
device_id: deviceId,
period,
data_count: dataCount.count,
alerts_count: alertsCount.count,
latest_data: latestData || null,
last_seen: latestData ? latestData.timestamp : null
};
res.json({
success: true,
data: stats
});
} catch (error) {
logger.error('Get device stats error:', error);
res.status(500).json({
success: false,
message: 'Failed to get device statistics'
});
}
});
// Get all device types
router.get('/types/list', protect, async (req, res) => {
try {
const types = await database.query(
'SELECT DISTINCT device_type FROM devices ORDER BY device_type'
);
const deviceTypes = types.map(type => type.device_type);
res.json({
success: true,
data: deviceTypes
});
} catch (error) {
logger.error('Get device types error:', error);
res.status(500).json({
success: false,
message: 'Failed to get device types'
});
}
});
// Get devices by type
router.get('/types/:deviceType', protect, async (req, res) => {
try {
const { deviceType } = req.params;
const { page = 1, limit = 10 } = req.query;
const offset = (page - 1) * limit;
const devices = await database.query(
'SELECT * FROM devices WHERE device_type = ? ORDER BY created_at DESC LIMIT ? OFFSET ?',
[deviceType, parseInt(limit), offset]
);
const [countResult] = await database.query(
'SELECT COUNT(*) as total FROM devices WHERE device_type = ?',
[deviceType]
);
const total = countResult.total;
res.json({
success: true,
data: devices,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
logger.error('Get devices by type error:', error);
res.status(500).json({
success: false,
message: 'Failed to get devices by type'
});
}
});
module.exports = router;