Files
NoIdle/backend/routes/devices.js
root 6086c13be7 feat: Implementação completa do NoIdle - Cliente, Backend e Scripts
- Cliente Windows com modo silencioso e auto-start robusto
- Backend Node.js + API REST
- Frontend Next.js + Dashboard
- Scripts PowerShell de configuração e diagnóstico
- Documentação completa
- Build scripts para Windows e Linux
- Solução de auto-start após reinicialização

Resolução do problema: Cliente não voltava ativo após reboot
Solução: Registro do Windows + Task Scheduler + Modo silencioso
2025-11-16 22:56:35 +00:00

264 lines
9.7 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { query } = require('../config/database');
const { authenticateToken } = require('../middleware/auth');
// LISTAR DISPOSITIVOS
router.get('/', authenticateToken, async (req, res) => {
try {
const company_id = req.user.company_id;
// Primeiro, marcar dispositivos como inativos se não receberam heartbeat há mais de 5 minutos
await query(
`UPDATE devices
SET is_active = false
WHERE company_id = $1
AND (last_seen IS NULL OR last_seen < NOW() - INTERVAL '5 minutes')
AND is_active = true`,
[company_id]
);
// Agora buscar dispositivos com status atualizado
const result = await query(
`SELECT d.*,
u.name as user_name,
u.email as user_email,
CASE
WHEN d.last_seen IS NULL THEN false
WHEN d.last_seen < NOW() - INTERVAL '5 minutes' THEN false
ELSE d.is_active
END as real_status
FROM devices d
LEFT JOIN users u ON d.user_id = u.id
WHERE d.company_id = $1
ORDER BY d.created_at DESC`,
[company_id]
);
// Atualizar is_active com o real_status calculado
const devices = result.rows.map(device => ({
...device,
is_active: device.real_status,
real_status: undefined // Remover campo auxiliar
}));
res.json({ success: true, devices });
} catch (error) {
console.error('Erro ao listar dispositivos:', error);
res.status(500).json({ error: 'Erro ao listar dispositivos' });
}
});
// ATIVAR DISPOSITIVO (sem auth - usado pelo client)
router.post('/activate', async (req, res) => {
try {
const { activation_key, device_info } = req.body;
if (!activation_key) {
return res.status(400).json({ error: 'Chave de ativação é obrigatória' });
}
// Buscar chave (SEM verificar is_used ou expires_at)
const keyResult = await query(
'SELECT * FROM activation_keys WHERE key = $1',
[activation_key]
);
if (keyResult.rows.length === 0) {
return res.status(400).json({ error: 'Chave de ativação inválida' });
}
const key = keyResult.rows[0];
const device_id = `DEV-${Date.now()}-${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
const device_name = device_info?.device_name || device_info?.hostname || 'Dispositivo';
const hostname = device_info?.hostname || device_name;
const username = device_info?.username || '';
// Criar device
const deviceResult = await query(
`INSERT INTO devices (device_id, device_name, hostname, username, company_id, is_active, created_at)
VALUES ($1, $2, $3, $4, $5, true, NOW()) RETURNING *`,
[device_id, device_name, hostname, username, key.company_id]
);
const device = deviceResult.rows[0];
// Incrementar contador de devices da chave
await query(
'UPDATE activation_keys SET devices_count = devices_count + 1 WHERE id = $1',
[key.id]
);
console.log(`✅ Dispositivo ativado: ${device.device_name} (${device_id})`);
res.status(201).json({
success: true,
device_id: device.device_id,
device_name: device.device_name,
company_id: device.company_id,
message: 'Dispositivo ativado com sucesso'
});
} catch (error) {
console.error('Erro ao ativar dispositivo:', error);
res.status(500).json({ error: 'Erro ao ativar dispositivo', details: error.message });
}
});
// HEARTBEAT (sem auth - usado pelo client)
router.post('/heartbeat', async (req, res) => {
try {
const { device_id } = req.body;
if (!device_id) {
return res.status(400).json({ error: 'device_id é obrigatório' });
}
const result = await query(
'UPDATE devices SET last_seen = NOW(), is_active = true WHERE device_id = $1 RETURNING device_name',
[device_id]
);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Dispositivo não encontrado' });
}
console.log(`💓 Heartbeat: ${result.rows[0].device_name}`);
res.json({ success: true, message: 'Heartbeat registrado' });
} catch (error) {
console.error('Erro heartbeat:', error);
res.status(500).json({ error: 'Erro ao registrar heartbeat' });
}
});
// ATUALIZAR DISPOSITIVO
router.put('/:id', authenticateToken, async (req, res) => {
try {
const { id } = req.params;
const { device_name, user_id, team_id, is_active } = req.body;
const company_id = req.user.company_id;
console.log(`🔵 PUT /api/devices/${id} - Requisição recebida`);
console.log(`🔵 Body recebido:`, JSON.stringify({ device_name, user_id, team_id, is_active }, null, 2));
console.log(`🔵 Company ID:`, company_id);
console.log(`🔵 ID recebido (tipo):`, typeof id, id);
// Verificar se o dispositivo existe e pertence à company
// Aceitar tanto id numérico quanto device_id string
let deviceCheck;
if (isNaN(id)) {
// Se não é número, pode ser device_id
deviceCheck = await query(
'SELECT id FROM devices WHERE device_id = $1 AND company_id = $2',
[id, company_id]
);
} else {
// Se é número, é o id numérico
deviceCheck = await query(
'SELECT id FROM devices WHERE id = $1 AND company_id = $2',
[parseInt(id), company_id]
);
}
if (deviceCheck.rows.length === 0) {
return res.status(404).json({ error: 'Dispositivo não encontrado' });
}
const deviceId = deviceCheck.rows[0].id; // Usar o ID numérico real
// Se user_id foi fornecido, verificar se o usuário existe e pertence à mesma company
if (user_id !== undefined && user_id !== null) {
const userCheck = await query(
'SELECT id FROM users WHERE id = $1 AND company_id = $2',
[user_id, company_id]
);
if (userCheck.rows.length === 0) {
return res.status(404).json({ error: 'Usuário não encontrado ou não pertence à sua empresa' });
}
}
// Construir query dinamicamente baseado nos campos fornecidos
const updateFields = [];
const updateValues = [];
let paramCount = 1;
if (device_name !== undefined) {
updateFields.push(`device_name = $${paramCount++}`);
updateValues.push(device_name);
}
if (user_id !== undefined) {
// Converter string vazia para null, e garantir que seja número ou null
let finalUserId = null;
if (user_id !== null && user_id !== '' && user_id !== undefined) {
finalUserId = parseInt(user_id);
if (isNaN(finalUserId)) {
return res.status(400).json({ error: 'user_id deve ser um número válido' });
}
}
updateFields.push(`user_id = $${paramCount++}`);
updateValues.push(finalUserId);
console.log(`🔵 user_id processado: ${user_id} -> ${finalUserId}`);
}
if (team_id !== undefined) {
updateFields.push(`team_id = $${paramCount++}`);
updateValues.push(team_id === null || team_id === '' ? null : team_id);
}
if (is_active !== undefined) {
updateFields.push(`is_active = $${paramCount++}`);
updateValues.push(is_active);
}
if (updateFields.length === 0) {
return res.status(400).json({ error: 'Nenhum campo para atualizar' });
}
updateValues.push(deviceId, company_id);
const queryText = `UPDATE devices
SET ${updateFields.join(', ')}
WHERE id = $${paramCount++} AND company_id = $${paramCount}
RETURNING *,
(SELECT name FROM users WHERE id = devices.user_id) as user_name,
(SELECT email FROM users WHERE id = devices.user_id) as user_email`;
const result = await query(queryText, updateValues);
console.log(`✅ Dispositivo ${deviceId} (${id}) atualizado com sucesso`);
res.json({ success: true, device: result.rows[0] });
} catch (error) {
console.error('Erro ao atualizar dispositivo:', error);
console.error('Erro detalhado:', error.message);
res.status(500).json({ error: 'Erro ao atualizar dispositivo', details: error.message });
}
});
// DELETAR DISPOSITIVO
router.delete('/:id', authenticateToken, async (req, res) => {
try {
const { id } = req.params;
const company_id = req.user.company_id;
const result = await query(
'DELETE FROM devices WHERE id = $1 AND company_id = $2 RETURNING *',
[id, company_id]
);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Dispositivo não encontrado' });
}
res.json({ success: true, message: 'Dispositivo deletado' });
} catch (error) {
console.error('Erro ao deletar dispositivo:', error);
res.status(500).json({ error: 'Erro ao deletar dispositivo' });
}
});
module.exports = router;