Skip to content

Instantly share code, notes, and snippets.

@alcarazolabs
Created August 3, 2025 18:08
Show Gist options
  • Save alcarazolabs/bdbfacc4efe58baaefb3b6342ba3a9df to your computer and use it in GitHub Desktop.
Save alcarazolabs/bdbfacc4efe58baaefb3b6342ba3a9df to your computer and use it in GitHub Desktop.
// routes/devices.js
import express from 'express';
import dayjs from 'dayjs';
import { getRedisClient } from '../redisClient.js';
import { Device, DeviceLocation, DeviceStatus, Secret } from '../models/initModels.js';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import { ValidationError } from 'sequelize'; // validator de sequelize
import { generateApiKeyV1 } from '../utils/generateApiKey.js'
dotenv.config(); // Cargar variables del .env
// Obtener valor JWT_SECRET desde el .env
const JWT_SECRET = process.env.JWT_SECRET;
const router = express.Router();
// Función helper para formatear fecha con zona horaria
function formatearFechaHoraLima(fechaUTC) {
return dayjs(fechaUTC)
.tz('America/Lima')
.format('YYYY-MM-DD HH:mm:ss');
}
// Middleware para verificar el JWToken.
function verifyToken(req, res, next){
const token = req.header("Authorization");
if (!token){
return res.status(401).json({
message: "Acceso denegado!"
})
}
try {
const decoded = jwt.verify(
token.split(" ")[1],
JWT_SECRET
);
req.user = decoded;
next();
} catch (error) {
console.log("Error al verificar el token: ", error);
res.status(401).json({
message: "Token invalido!"
})
}
}
router.post("/devices/apikey", verifyToken, async (req, res) => {
try {
const { id } = req.body;
var message = "";
var key = generateApiKeyV1()
if (id == null){
// Registrar por primera vez
await Secret.create({ devices_api_key: key });
// Setear la key en la BD Cache
// Obtener cliente-Redis
const clientRedis = await getRedisClient();
await clientRedis.set("devices_api_key", key);
message = "Key registrada correctamente."
}else{
// Actualizar la key
await Secret.upsert({ id: id, devices_api_key: key });
// Setear la key en la BD Cache
// Obtener cliente-Redis
const clientRedis = await getRedisClient();
await clientRedis.set("devices_api_key", key);
message = "Key actualizada correctamente."
}
// Enviar las keys
const keys = await Secret.findAll({
limit: 1, // Limitar a 1 resultados
order: [['id', 'DESC']],
})
res.status(201).json({
success: true,
message: message,
keys
});
} catch (error) {
// Primero, verificamos si el error es una instancia de ValidationError de Sequelize
if (error instanceof ValidationError) {
console.log("Error de validación:", error.errors);
// Extraemos los mensajes de error personalizados que definimos en el modelo
const errorMessages = error.errors.map(err => err.message);
return res.status(400).json({ // 400 Bad Request es el código ideal para errores de validación
message: "Error de validación",
success: false,
errors: errorMessages // Enviamos un array con todos los mensajes de error
});
}
// Si es otro tipo de error (ej. error de conexión a la BD), lo manejamos como un error del servidor
console.log("Ocurrió un error en el registro del api key: ", error);
res.status(500).json({
message: "Error en el servidor.",
success: false
});
}
});
router.get("/devices/apikey", verifyToken, async (req, res) => {
try {
const keys = await Secret.findAll({
limit: 1, // Limitar a 1 resultados
order: [['id', 'DESC']],
})
res.json( {
success: true,
keys
})
} catch (error) {
res.status(500).json({
message: "Error del Servidor",
success: false,
})
}
});
router.post("/devices", verifyToken, async (req, res) => {
try {
const { nombre, descripcion } = req.body;
// Registrar el Device
await Device.create({ nombre, descripcion });
// Aquí hacemos un nuevo SELECT para retornar todo actualizado
const devices = await Device.findAll();
res.status(201).json({
success: true,
message: "Dispositivo registrado correctamente.",
devices
});
} catch (error) {
// Primero, verificamos si el error es una instancia de ValidationError de Sequelize
if (error instanceof ValidationError) {
console.log("Error de validación:", error.errors);
// Extraemos los mensajes de error personalizados que definimos en el modelo
const errorMessages = error.errors.map(err => err.message);
return res.status(400).json({ // 400 Bad Request es el código ideal para errores de validación
message: "Error de validación",
success: false,
errors: errorMessages // Enviamos un array con todos los mensajes de error
});
}
// Si es otro tipo de error (ej. error de conexión a la BD), lo manejamos como un error del servidor
console.log("Ocurrió un error en el registro del dispositivo: ", error);
res.status(500).json({
message: "Error en el servidor.",
success: false
});
}
});
router.put("/devices", verifyToken, async (req, res) => {
try {
const { id, nombre, descripcion } = req.body;
console.log(req.body)
// Registrar el Device
await Device.update(
{ nombre, descripcion },
{ where: { id } }
);
// retornar todo los devices actualizados
const devices = await Device.findAll();
res.status(201).json({
success: true,
message: "Dispositivo actualizado correctamente.",
devices
});
} catch (error) {
// Primero, verificamos si el error es una instancia de ValidationError de Sequelize
if (error instanceof ValidationError) {
console.log("Error de validación:", error.errors);
// Extraemos los mensajes de error personalizados que definimos en el modelo
const errorMessages = error.errors.map(err => err.message);
return res.status(400).json({ // 400 Bad Request es el código ideal para errores de validación
message: "Error de validación",
success: false,
errors: errorMessages // Enviamos un array con todos los mensajes de error
});
}
// Si es otro tipo de error (ej. error de conexión a la BD), lo manejamos como un error del servidor
console.log("Ocurrió un error en la actualizacion del dispositivo: ", error);
res.status(500).json({
message: "Error en el servidor.",
success: false
});
}
});
router.delete("/devices", verifyToken, async (req, res) => {
try {
const { id } = req.body;
// Validar que se haya enviado un id
if (!id) {
return res.status(400).json({
success: false,
message: "El ID del dispositivo es obligatorio."
});
}
// Verificar si el dispositivo existe
const device = await Device.findByPk(id);
if (!device) {
return res.status(404).json({
success: false,
message: `No se encontró un dispositivo con ID ${id}.`
});
}
// Eliminar el dispositivo
await device.destroy(); // o Device.destroy({ where: { id } })
// Obtener lista actualizada
const devices = await Device.findAll();
res.status(200).json({
success: true,
message: "Dispositivo eliminado correctamente.",
devices
});
} catch (error) {
if (error instanceof ValidationError) {
const errorMessages = error.errors.map(err => err.message);
return res.status(400).json({
message: "Error de validación",
success: false,
errors: errorMessages
});
}
console.error("Error en la eliminación del dispositivo: ", error);
res.status(500).json({
message: "Error en el servidor.",
success: false
});
}
});
router.get("/devices", verifyToken, async (req, res) => {
try {
const devices = await Device.findAll();
// Enviar datos del usuario
res.json( {
success: true,
devices
})
} catch (error) {
res.status(500).json({
message: "Error del Servidor",
success: false,
})
}
});
router.post("/devices/log-location", async (req, res) => {
try {
//Nota aqui falta recibir el api_key y validar!!
const { id_dispositivo, latitud, longitud } = req.body;
if (!id_dispositivo || !latitud || !longitud) {
return res.status(400).json({ message: "Faltan campos requeridos.", success: false });
}
// Obtener la fecha-hora de acuerdo a la zona horaria de Lima-Perú
const fecha_hora = dayjs().tz("America/Lima").toDate();
const device = await Device.findByPk(id_dispositivo);
if (!device) {
return res.status(404).json({ message: "Dispositivo no encontrado.", success: false });
}
// Logear la ubicación
await DeviceLocation.create({ id_dispositivo, latitud, longitud, fecha_hora });
// Actualizar o Registrar en DeviceStatuses
await DeviceStatus.upsert({ id_dispositivo, latitud, longitud, fecha_hora });
const allStatuses = await DeviceStatus.findAll({
include: [{ model: Device }] // Obtener los datos del Device (Relación)
});
// Obtener cliente-Redis
const clientRedis = await getRedisClient();
// Guardar los devices status en la cache-redis
await clientRedis.set("devices_status", JSON.stringify(allStatuses));
// Emitir evento por WebSocket
const io = req.app.get('socketio');
io.emit("locationUpdated", {
id_dispositivo,
latitud,
longitud,
fecha_hora: formatearFechaHoraLima(fecha_hora)
});
return res.status(200).json({ message: "Ubicación registrada correctamente.", success: true });
} catch (error) {
console.error("Error en /log-location:", error);
res.status(500).json({ message: "Error interno del servidor.", success: false });
}
});
router.get("/devices/status", async (req, res) => {
try {
const clientRedis = await getRedisClient();
const cacheData = await clientRedis.get("devices_status");
if (cacheData) {
const dataOriginal = JSON.parse(cacheData);
const data = dataOriginal.map(device => ({
...device,
fecha_hora: formatearFechaHoraLima(device.fecha_hora) //Obtener la fecha a la zona horaria de America/Lima
}));
return res.json({ source: "Redis", data });
} else {
return res.status(404).json({ message: "No hay datos en caché.", success: false });
}
} catch (error) {
console.error("Error en /devices-status:", error);
res.status(500).json({ message: "Error interno del servidor.", success: false });
}
});
export default router;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment