544 lines
14 KiB
JavaScript
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;
|