Fix jwt-token
This commit is contained in:
41
server/migrations/20250828000001-add-device-approval.js
Normal file
41
server/migrations/20250828000001-add-device-approval.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Migration: Add is_approved field to devices table
|
||||||
|
* This migration adds device approval functionality to the system
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
// Add is_approved column to devices table
|
||||||
|
await queryInterface.addColumn('devices', 'is_approved', {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
allowNull: false,
|
||||||
|
comment: 'Whether the device is approved to send data'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add index for is_approved for better query performance
|
||||||
|
await queryInterface.addIndex('devices', ['is_approved'], {
|
||||||
|
name: 'devices_is_approved_idx'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Approve all existing devices by default (backward compatibility)
|
||||||
|
await queryInterface.sequelize.query(
|
||||||
|
'UPDATE devices SET is_approved = true WHERE created_at < NOW()'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('✅ Added is_approved field to devices table');
|
||||||
|
console.log('✅ Approved all existing devices for backward compatibility');
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface, Sequelize) {
|
||||||
|
// Remove index first
|
||||||
|
await queryInterface.removeIndex('devices', 'devices_is_approved_idx');
|
||||||
|
|
||||||
|
// Remove the column
|
||||||
|
await queryInterface.removeColumn('devices', 'is_approved');
|
||||||
|
|
||||||
|
console.log('✅ Removed is_approved field from devices table');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -34,6 +34,11 @@ module.exports = (sequelize) => {
|
|||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
comment: 'Whether the device is currently active'
|
comment: 'Whether the device is currently active'
|
||||||
},
|
},
|
||||||
|
is_approved: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
comment: 'Whether the device is approved to send data'
|
||||||
|
},
|
||||||
last_heartbeat: {
|
last_heartbeat: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@@ -81,6 +86,9 @@ module.exports = (sequelize) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: ['is_active']
|
fields: ['is_active']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: ['is_approved']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,15 +98,56 @@ async function handleHeartbeat(req, res) {
|
|||||||
deviceId = keyMatch ? parseInt(keyMatch[1]) : key.hashCode();
|
deviceId = keyMatch ? parseInt(keyMatch[1]) : key.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure device exists or create it
|
// Check if device exists and is approved
|
||||||
const [device] = await Device.findOrCreate({
|
let device = await Device.findOne({ where: { id: deviceId } });
|
||||||
where: { id: deviceId },
|
|
||||||
defaults: {
|
if (!device) {
|
||||||
|
// Create new device as unapproved
|
||||||
|
device = await Device.create({
|
||||||
id: deviceId,
|
id: deviceId,
|
||||||
name: `Device ${deviceId}`,
|
name: `Device ${deviceId}`,
|
||||||
last_heartbeat: new Date()
|
last_heartbeat: new Date(),
|
||||||
}
|
is_approved: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Emit notification for new device requiring approval
|
||||||
|
req.io.emit('new_device_pending', {
|
||||||
|
device_id: deviceId,
|
||||||
|
device_key: key,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: `New device ${deviceId} (${key}) requires approval`
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`⚠️ New unapproved device ${deviceId} created, awaiting approval`);
|
||||||
|
|
||||||
|
return res.status(202).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not approved',
|
||||||
|
message: 'Device has been registered but requires approval before it can send data',
|
||||||
|
device_id: deviceId,
|
||||||
|
approval_required: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.is_approved) {
|
||||||
|
console.log(`🚫 Heartbeat rejected from unapproved device ${deviceId}`);
|
||||||
|
|
||||||
|
// Emit reminder notification
|
||||||
|
req.io.emit('device_approval_reminder', {
|
||||||
|
device_id: deviceId,
|
||||||
|
device_key: key,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: `Device ${deviceId} (${key}) still awaiting approval`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not approved',
|
||||||
|
message: 'Device requires approval before it can send data',
|
||||||
|
device_id: deviceId,
|
||||||
|
approval_required: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update device's last heartbeat
|
// Update device's last heartbeat
|
||||||
await device.update({ last_heartbeat: new Date() });
|
await device.update({ last_heartbeat: new Date() });
|
||||||
@@ -143,16 +184,56 @@ async function handleDetection(req, res) {
|
|||||||
|
|
||||||
console.log(`🚁 Drone detection received from device ${detectionData.device_id}: drone_id=${detectionData.drone_id}, type=${detectionData.drone_type}, rssi=${detectionData.rssi}`);
|
console.log(`🚁 Drone detection received from device ${detectionData.device_id}: drone_id=${detectionData.drone_id}, type=${detectionData.drone_type}, rssi=${detectionData.rssi}`);
|
||||||
|
|
||||||
// Ensure device exists or create it (from original detection route)
|
// Check if device exists and is approved
|
||||||
const [device] = await Device.findOrCreate({
|
let device = await Device.findOne({ where: { id: detectionData.device_id } });
|
||||||
where: { id: detectionData.device_id },
|
|
||||||
defaults: {
|
if (!device) {
|
||||||
|
// Create new device as unapproved
|
||||||
|
device = await Device.create({
|
||||||
id: detectionData.device_id,
|
id: detectionData.device_id,
|
||||||
|
name: `Device ${detectionData.device_id}`,
|
||||||
geo_lat: detectionData.geo_lat || 0,
|
geo_lat: detectionData.geo_lat || 0,
|
||||||
geo_lon: detectionData.geo_lon || 0,
|
geo_lon: detectionData.geo_lon || 0,
|
||||||
last_heartbeat: new Date()
|
last_heartbeat: new Date(),
|
||||||
}
|
is_approved: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Emit notification for new device requiring approval
|
||||||
|
req.io.emit('new_device_pending', {
|
||||||
|
device_id: detectionData.device_id,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: `New device ${detectionData.device_id} requires approval`
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`⚠️ New unapproved device ${detectionData.device_id} created, awaiting approval`);
|
||||||
|
|
||||||
|
return res.status(202).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not approved',
|
||||||
|
message: 'Device has been registered but requires approval before it can send data',
|
||||||
|
device_id: detectionData.device_id,
|
||||||
|
approval_required: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.is_approved) {
|
||||||
|
console.log(`🚫 Detection rejected from unapproved device ${detectionData.device_id}`);
|
||||||
|
|
||||||
|
// Emit reminder notification
|
||||||
|
req.io.emit('device_approval_reminder', {
|
||||||
|
device_id: detectionData.device_id,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: `Device ${detectionData.device_id} still awaiting approval`
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not approved',
|
||||||
|
message: 'Device requires approval before it can send data',
|
||||||
|
device_id: detectionData.device_id,
|
||||||
|
approval_required: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create detection record
|
// Create detection record
|
||||||
const detection = await DroneDetection.create({
|
const detection = await DroneDetection.create({
|
||||||
|
|||||||
@@ -315,4 +315,84 @@ router.delete('/:id', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/devices/pending - List devices pending approval
|
||||||
|
router.get('/pending', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pendingDevices = await Device.findAll({
|
||||||
|
where: { is_approved: false },
|
||||||
|
attributes: [
|
||||||
|
'id', 'name', 'geo_lat', 'geo_lon', 'last_heartbeat',
|
||||||
|
'created_at', 'firmware_version', 'is_approved'
|
||||||
|
],
|
||||||
|
order: [['created_at', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: pendingDevices,
|
||||||
|
count: pendingDevices.length
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching pending devices:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to fetch pending devices',
|
||||||
|
error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/devices/:id/approve - Approve or reject a device
|
||||||
|
router.post('/:id/approve', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deviceId = parseInt(req.params.id);
|
||||||
|
const { approved } = req.body;
|
||||||
|
|
||||||
|
if (typeof approved !== 'boolean') {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'approved field must be a boolean'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = await Device.findByPk(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Device not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await device.update({ is_approved: approved });
|
||||||
|
|
||||||
|
// Emit real-time notification
|
||||||
|
const { io } = require('../index');
|
||||||
|
if (io) {
|
||||||
|
io.emit('device_approval_updated', {
|
||||||
|
device_id: deviceId,
|
||||||
|
approved: approved,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: approved ?
|
||||||
|
`Device ${deviceId} has been approved` :
|
||||||
|
`Device ${deviceId} approval has been revoked`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${approved ? '✅' : '❌'} Device ${deviceId} approval ${approved ? 'granted' : 'revoked'}`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: device,
|
||||||
|
message: approved ? 'Device approved successfully' : 'Device approval revoked'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating device approval:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to update device approval',
|
||||||
|
error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
293
server/routes/devices.js
Normal file
293
server/routes/devices.js
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Joi = require('joi');
|
||||||
|
const { validateRequest } = require('../middleware/validation');
|
||||||
|
const { Device, Heartbeat, DroneDetection } = require('../models');
|
||||||
|
const { Op } = require('sequelize');
|
||||||
|
|
||||||
|
// Validation schemas
|
||||||
|
const approveDeviceSchema = Joi.object({
|
||||||
|
device_id: Joi.number().integer().required(),
|
||||||
|
approved: Joi.boolean().required()
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateDeviceSchema = Joi.object({
|
||||||
|
name: Joi.string().optional(),
|
||||||
|
geo_lat: Joi.number().min(-90).max(90).optional(),
|
||||||
|
geo_lon: Joi.number().min(-180).max(180).optional(),
|
||||||
|
location_description: Joi.string().optional(),
|
||||||
|
is_active: Joi.boolean().optional(),
|
||||||
|
heartbeat_interval: Joi.number().integer().min(30).optional(),
|
||||||
|
notes: Joi.string().allow('').optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/devices - List all devices with approval status
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const devices = await Device.findAll({
|
||||||
|
attributes: [
|
||||||
|
'id', 'name', 'geo_lat', 'geo_lon', 'location_description',
|
||||||
|
'is_active', 'is_approved', 'last_heartbeat', 'heartbeat_interval',
|
||||||
|
'firmware_version', 'installation_date', 'notes', 'created_at'
|
||||||
|
],
|
||||||
|
order: [['created_at', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add status information
|
||||||
|
const devicesWithStatus = devices.map(device => {
|
||||||
|
const lastHeartbeat = device.last_heartbeat;
|
||||||
|
const heartbeatInterval = device.heartbeat_interval || 300;
|
||||||
|
const now = new Date();
|
||||||
|
const timeSinceHeartbeat = lastHeartbeat ?
|
||||||
|
(now - new Date(lastHeartbeat)) / 1000 : null;
|
||||||
|
|
||||||
|
let status = 'unknown';
|
||||||
|
if (!device.is_approved) {
|
||||||
|
status = 'pending_approval';
|
||||||
|
} else if (!device.is_active) {
|
||||||
|
status = 'inactive';
|
||||||
|
} else if (timeSinceHeartbeat && timeSinceHeartbeat > heartbeatInterval * 2) {
|
||||||
|
status = 'offline';
|
||||||
|
} else if (timeSinceHeartbeat && timeSinceHeartbeat <= heartbeatInterval * 2) {
|
||||||
|
status = 'online';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...device.toJSON(),
|
||||||
|
status,
|
||||||
|
time_since_heartbeat: timeSinceHeartbeat
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: devicesWithStatus
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching devices:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to fetch devices'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/devices/pending - List devices pending approval
|
||||||
|
router.get('/pending', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pendingDevices = await Device.findAll({
|
||||||
|
where: { is_approved: false },
|
||||||
|
attributes: [
|
||||||
|
'id', 'name', 'geo_lat', 'geo_lon', 'last_heartbeat',
|
||||||
|
'created_at', 'firmware_version'
|
||||||
|
],
|
||||||
|
order: [['created_at', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: pendingDevices
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching pending devices:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to fetch pending devices'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/devices/:id/approve - Approve or reject a device
|
||||||
|
router.post('/:id/approve', validateRequest(approveDeviceSchema), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deviceId = parseInt(req.params.id);
|
||||||
|
const { approved } = req.body;
|
||||||
|
|
||||||
|
const device = await Device.findByPk(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await device.update({ is_approved: approved });
|
||||||
|
|
||||||
|
// Emit real-time notification
|
||||||
|
const { io } = require('../index');
|
||||||
|
if (io) {
|
||||||
|
io.emit('device_approval_updated', {
|
||||||
|
device_id: deviceId,
|
||||||
|
approved: approved,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: approved ?
|
||||||
|
`Device ${deviceId} has been approved` :
|
||||||
|
`Device ${deviceId} approval has been revoked`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${approved ? '✅' : '❌'} Device ${deviceId} approval ${approved ? 'granted' : 'revoked'}`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: device,
|
||||||
|
message: approved ? 'Device approved successfully' : 'Device approval revoked'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating device approval:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to update device approval'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT /api/devices/:id - Update device information
|
||||||
|
router.put('/:id', validateRequest(updateDeviceSchema), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deviceId = parseInt(req.params.id);
|
||||||
|
const updateData = req.body;
|
||||||
|
|
||||||
|
const device = await Device.findByPk(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await device.update(updateData);
|
||||||
|
|
||||||
|
// Emit real-time notification
|
||||||
|
const { io } = require('../index');
|
||||||
|
if (io) {
|
||||||
|
io.emit('device_updated', {
|
||||||
|
device_id: deviceId,
|
||||||
|
updates: updateData,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📝 Device ${deviceId} updated`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: device,
|
||||||
|
message: 'Device updated successfully'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating device:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to update device'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /api/devices/:id - Delete a device and all its data
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deviceId = parseInt(req.params.id);
|
||||||
|
|
||||||
|
const device = await Device.findByPk(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete related data first (due to foreign key constraints)
|
||||||
|
await DroneDetection.destroy({ where: { device_id: deviceId } });
|
||||||
|
await Heartbeat.destroy({ where: { device_id: deviceId } });
|
||||||
|
|
||||||
|
// Delete the device
|
||||||
|
await device.destroy();
|
||||||
|
|
||||||
|
// Emit real-time notification
|
||||||
|
const { io } = require('../index');
|
||||||
|
if (io) {
|
||||||
|
io.emit('device_deleted', {
|
||||||
|
device_id: deviceId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
message: `Device ${deviceId} has been deleted`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🗑️ Device ${deviceId} and all related data deleted`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Device deleted successfully'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting device:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to delete device'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/devices/:id/stats - Get device statistics
|
||||||
|
router.get('/:id/stats', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deviceId = parseInt(req.params.id);
|
||||||
|
const days = parseInt(req.query.days) || 7;
|
||||||
|
|
||||||
|
const device = await Device.findByPk(deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Device not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = new Date();
|
||||||
|
startDate.setDate(startDate.getDate() - days);
|
||||||
|
|
||||||
|
// Get heartbeat count
|
||||||
|
const heartbeatCount = await Heartbeat.count({
|
||||||
|
where: {
|
||||||
|
device_id: deviceId,
|
||||||
|
received_at: { [Op.gte]: startDate }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get detection count
|
||||||
|
const detectionCount = await DroneDetection.count({
|
||||||
|
where: {
|
||||||
|
device_id: deviceId,
|
||||||
|
server_timestamp: { [Op.gte]: startDate }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get latest heartbeat
|
||||||
|
const latestHeartbeat = await Heartbeat.findOne({
|
||||||
|
where: { device_id: deviceId },
|
||||||
|
order: [['received_at', 'DESC']],
|
||||||
|
attributes: ['received_at', 'signal_strength', 'battery_level', 'temperature']
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
device_id: deviceId,
|
||||||
|
period_days: days,
|
||||||
|
heartbeat_count: heartbeatCount,
|
||||||
|
detection_count: detectionCount,
|
||||||
|
latest_heartbeat: latestHeartbeat,
|
||||||
|
uptime_percentage: heartbeatCount > 0 ?
|
||||||
|
Math.min(100, (heartbeatCount / (days * 24 * 12)) * 100) : 0 // Assuming 5min intervals
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching device stats:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to fetch device statistics'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user