first commit

This commit is contained in:
2026-02-03 14:27:12 +00:00
commit fdd50d2174
5879 changed files with 875439 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
const mysql = require('mysql2');
const dotenv = require('dotenv');
dotenv.config();
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// Convert pool to allow async/await
const promisePool = pool.promise();
console.log(`Intentando conectar a DB: ${process.env.DB_NAME} en ${process.env.DB_HOST}`);
module.exports = promisePool;
+33
View File
@@ -0,0 +1,33 @@
const admin = require('firebase-admin');
const dotenv = require('dotenv');
dotenv.config();
const initFirebaseAdmin = () => {
if (admin.apps.length) {
return admin;
}
const credentialPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
if (!credentialPath) {
throw new Error(
'GOOGLE_APPLICATION_CREDENTIALS no está definida. Configúrala antes de inicializar Firebase Admin.'
);
}
const options = {
credential: admin.credential.applicationDefault()
};
if (process.env.FIREBASE_DATABASE_URL) {
options.databaseURL = process.env.FIREBASE_DATABASE_URL;
}
admin.initializeApp(options);
return admin;
};
module.exports = {
admin,
initFirebaseAdmin
};
+72
View File
@@ -0,0 +1,72 @@
const db = require('../config/db');
const md5 = require('md5');
const jwt = require('jsonwebtoken');
const login = async (req, res) => {
try {
const { usuario, contrasena } = req.body;
if (!usuario || !contrasena) {
return res.json("0"); // Maintain legacy error response
}
const hashedPassword = md5(contrasena);
// Parametrized query to prevent SQL Injection
const [rows] = await db.query(
"SELECT id_transportista, nombre, id_proveedor, dni FROM m_proveedores_trasportistas WHERE desactivado = 0 AND dni = ? AND password = ?",
[usuario, hashedPassword]
);
const fecha = new Date().toISOString().slice(0, 19).replace('T', ' '); // Format: YYYY-MM-DD HH:mm:ss
if (rows.length > 0) {
const user = rows[0];
// Log successful attempt
await db.query(
"INSERT INTO c_log_app (usuario, fecha, acceso) VALUES (?, ?, ?)",
[usuario, fecha, 1]
);
// Legacy format response: array with [dni, id_proveedor]
// const response = [user.dni, user.id_proveedor];
// Modern JWT response
const token = jwt.sign(
{
id: user.id_transportista,
dni: user.dni,
id_proveedor: user.id_proveedor
},
process.env.JWT_SECRET,
{ expiresIn: '365d' } // Long expiration for app usability
);
return res.json({
success: true,
token: token,
user: {
dni: user.dni,
id_proveedor: user.id_proveedor,
nombre: user.nombre
}
});
} else {
// Log failed attempt
await db.query(
"INSERT INTO c_log_app (usuario, fecha, acceso) VALUES (?, ?, ?)",
[usuario, fecha, 0]
);
return res.json("0");
}
} catch (error) {
console.error("Login error:", error);
return res.status(500).json("0"); // Fallback to "0" on error to not crash client
}
};
module.exports = {
login
};
+118
View File
@@ -0,0 +1,118 @@
const fs = require('fs');
const path = require('path');
const saveLocation = async (req, res) => {
try {
const data = req.body;
console.log("🔍 [DEBUG SERVER] Full Request Body:", JSON.stringify(data, null, 2)); // Debug payload structure
const logFile = path.join(__dirname, '../logs/coordenadas.txt');
// Ensure logs directory exists
const logDir = path.dirname(logFile);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// Helper function to process a single location object
const processSingleLocation = (locationData) => {
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
// Intentar obtener alias desde "extras.alias" o "alias"
let alias = 'Desconocido';
if (locationData.extras && locationData.extras.alias) {
alias = locationData.extras.alias;
} else if (locationData.alias) {
alias = locationData.alias;
}
let lat, lng;
if (locationData.location && locationData.location.coords) {
lat = locationData.location.coords.latitude;
lng = locationData.location.coords.longitude;
} else if (locationData.coords) {
lat = locationData.coords.latitude;
lng = locationData.coords.longitude;
} else if (locationData.latitude && locationData.longitude) {
lat = locationData.latitude;
lng = locationData.longitude;
}
if (lat !== undefined && lng !== undefined) {
console.log(`📍 [Server] Received: Alias=${alias} | Lat=${lat} | Lng=${lng}`);
const linea = `[${timestamp}] Alias: ${alias} | IP: ${ip} | Lat: ${lat} | Lng: ${lng}\n`;
fs.appendFileSync(logFile, linea);
return true;
} else {
const errorLine = `[${timestamp}] Alias: ${alias} | IP: ${ip} | ⚠️ Formato inválido o sin coordenadas\n`;
try {
fs.appendFileSync(logFile, errorLine);
} catch (e) {
console.error("Error writing error log:", e);
}
return false;
}
};
// Determine if it's a Batch (Array) or Single Location
let locations = [];
let globalAlias = null;
// 1. Priority: JWT User (Secure & Reliable)
if (req.user) {
console.log("🔐 [Server] req.user payload:", JSON.stringify(req.user)); // Debug token content
if (req.user.dni) {
globalAlias = req.user.dni;
console.log(`🔒 [Server] Authenticated User (GlobalAlias): ${globalAlias}`);
}
}
// Case 1: Root Array
if (Array.isArray(data)) {
locations = data;
}
// Case 2: Wrapped in "location" property (common with params)
else if (data.location && Array.isArray(data.location)) {
locations = data.location;
// Check for alias in root (Fallback if no token)
if (!globalAlias) {
if (data.alias) globalAlias = data.alias;
else if (data.params && data.params.alias) globalAlias = data.params.alias;
}
}
// Case 3: Single Object
else {
locations = [data];
}
console.log(`📦 [Server] Processing ${locations.length} locations`);
let savedCount = 0;
locations.forEach(loc => {
// FORCE Inject global alias (Security: Always trust Token over Client Body)
if (globalAlias) {
if (!loc.extras) loc.extras = {};
loc.extras.alias = globalAlias;
// Also purge root alias to avoid confusion in processSingleLocation logic
if (loc.alias) delete loc.alias;
}
// Fallback for processSingleLocation helper logic...
if (processSingleLocation(loc)) savedCount++;
});
if (savedCount > 0) {
return res.json({ success: true, count: savedCount, message: "Locations saved" });
} else {
return res.json({ success: false, error: 'no_coords', message: "No valid coordinates found in batch/object" });
}
} catch (error) {
console.error("Error saving location:", error);
return res.status(500).json({ success: false, error: error.message });
}
};
module.exports = {
saveLocation
};
+61
View File
@@ -0,0 +1,61 @@
const db = require('../config/db');
const md5 = require('md5');
const getProfile = async (req, res) => {
try {
// Handle potential mismatch from legacy code (id_prov vs id_proveedor)
const id_proveedor = req.body.id_proveedor || req.body.id_prov;
if (!id_proveedor) {
return res.status(400).json({ error: "Missing id_proveedor" });
}
const [rows] = await db.query(
"SELECT nombre, num_proveedor, cp FROM m_proveedores WHERE id_proveedor = ?",
[id_proveedor]
);
// PHP returned an array of objects
return res.json(rows);
} catch (error) {
console.error("Error getting profile:", error);
return res.status(500).json([]);
}
};
const changePassword = async (req, res) => {
try {
const { usuario, password } = req.body;
if (!usuario || !password) {
return res.json("1"); // Error code
}
const hashedPassword = md5(password);
const [result] = await db.query(
"UPDATE m_proveedores_trasportistas SET password = ? WHERE dni = ?",
[hashedPassword, usuario]
);
if (result.affectedRows > 0) {
return res.json("0"); // Success code
} else {
// Could mean user not found or password didn't change, but legacy returns 0 for success.
// If nothing updated, maybe user doesn't exist.
// Legacy logic: if query runs, return 0. If error, return 1.
// But checking affectedRows is safer.
// Let's stick to legacy behavior: if no error, return 0?
// The PHP code only returns 1 if mysqli_query fails (returns false).
return res.json("0");
}
} catch (error) {
console.error("Error changing password:", error);
return res.json("1");
}
};
module.exports = {
getProfile,
changePassword
};
+25
View File
@@ -0,0 +1,25 @@
const db = require('../config/db');
const recordTest = async (req, res) => {
try {
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown';
console.log(`🔥 [Stress Test] Request received from ${ip}`);
// Insertar en prueba_esfuerzo
// Asume columnas 'fecha' (datetime) e 'ip' (varchar)
const [result] = await db.query("INSERT INTO prueba_esfuerzo (fecha, ip) VALUES (NOW(), ?)", [ip]);
res.json({
success: true,
message: "Insertado en prueba_esfuerzo",
insertId: result.insertId,
ip: ip
});
} catch (error) {
console.error("Stress test error:", error);
res.status(500).json({ success: false, error: error.message });
}
};
module.exports = { recordTest };
+8994
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
const jwt = require('jsonwebtoken');
const authenticateDevice = (req, res, next) => {
// 1. Obtener Token del Header 'Authorization: Bearer <token>'
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(' ')[1]; // Ignorar 'Bearer '
if (!token) {
return res.status(401).json({ success: false, error: 'MISSING_TOKEN', message: 'Token requerido' });
}
try {
// 2. Verificar Token usa la misma clave secreta que authController
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 3. Inyectar datos del usuario verificado al request
// authController firma: { id, dni, id_proveedor }
req.user = decoded;
next();
} catch (err) {
console.warn(`[Auth] Token inválido desde IP: ${req.ip}`);
return res.status(403).json({ success: false, error: 'INVALID_TOKEN', message: 'Token inválido o expirado' });
}
};
module.exports = authenticateDevice;
+26
View File
@@ -0,0 +1,26 @@
const jwt = require('jsonwebtoken');
const verifyToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
// Check if header exists and format is "Bearer <token>"
if (!authHeader) {
return res.status(403).json({ error: "Access denied. No token provided." });
}
const token = authHeader.split(' ')[1]; // Get token part
if (!token) {
return res.status(403).json({ error: "Access denied. Malformed token." });
}
try {
const verified = jwt.verify(token, process.env.JWT_SECRET);
req.user = verified; // Attach user info to request
next(); // Proceed to route
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
};
module.exports = verifyToken;
+9
View File
@@ -0,0 +1,9 @@
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
router.post('/login', authController.login);
module.exports = router;
+11
View File
@@ -0,0 +1,11 @@
const express = require('express');
const router = express.Router();
const locationController = require('../controllers/locationController');
const authenticateDevice = require('../middleware/auth');
// Route for background geolocation updates
// The plugin might POST to /api/locations
router.post('/api/locations', authenticateDevice, locationController.saveLocation);
module.exports = router;
+10
View File
@@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();
const profileController = require('../controllers/profileController');
const verifyToken = require('../middleware/authMiddleware');
// Modern routes (PROTECTED)
router.post('/get_profile', verifyToken, profileController.getProfile);
router.post('/change_password', verifyToken, profileController.changePassword);
module.exports = router;
+8
View File
@@ -0,0 +1,8 @@
const express = require('express');
const router = express.Router();
const stressController = require('../controllers/stressController');
// GET /api/stress-test
router.get('/stress-test', stressController.recordTest);
module.exports = router;
+56
View File
@@ -0,0 +1,56 @@
const { initFirebaseAdmin } = require('../config/firebaseAdmin');
const args = process.argv.slice(2);
const getArgValue = (flag) => {
const index = args.indexOf(flag);
if (index === -1) {
return null;
}
return args[index + 1] || null;
};
const token = getArgValue('--token') || process.env.FCM_TEST_TOKEN;
const title = getArgValue('--title') || process.env.FCM_TEST_TITLE || 'Prueba desde Firebase Admin';
const body = getArgValue('--body') || process.env.FCM_TEST_BODY || 'Notificación de prueba';
const dryRun = args.includes('--dry-run');
const selfTest = args.includes('--self-test');
const admin = initFirebaseAdmin();
if (selfTest) {
console.log('Firebase Admin inicializado correctamente.');
process.exit(0);
}
if (!token) {
console.error('Falta el token FCM. Usa --token o define FCM_TEST_TOKEN en el entorno.');
process.exit(1);
}
const message = {
token,
notification: {
title,
body
},
data: {
source: 'node-gestion-api',
sentAt: new Date().toISOString()
}
};
admin
.messaging()
.send(message, dryRun)
.then((response) => {
if (dryRun) {
console.log('Dry run OK:', response);
return;
}
console.log('Mensaje enviado:', response);
})
.catch((error) => {
console.error('Error al enviar la notificación:', error);
process.exit(1);
});