264 lines
9.7 KiB
JavaScript
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;
|