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;