feat: Agregar modulo de Sistema (mongosh, logs, export/import)
This commit is contained in:
+119
-1
@@ -17,9 +17,43 @@ import { initDb,
|
||||
getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion,
|
||||
getAllMovimientosIndicaciones, createMovimientoIndicacion,
|
||||
getAllPendientes, createPendiente, updatePendiente, deletePendiente,
|
||||
getValue, setValue
|
||||
getValue, setValue,
|
||||
getDb, exportAllData, importAllData
|
||||
} from './db-mongodb.js';
|
||||
|
||||
// Capture live logs in-memory
|
||||
const logBuffer = [];
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
|
||||
function addLog(type, args) {
|
||||
const message = args.map(arg => {
|
||||
if (typeof arg === 'object') {
|
||||
try { return JSON.stringify(arg); } catch { return String(arg); }
|
||||
}
|
||||
return String(arg);
|
||||
}).join(' ');
|
||||
const logEntry = `[${new Date().toISOString()}] [${type}] ${message}`;
|
||||
logBuffer.push(logEntry);
|
||||
if (logBuffer.length > 500) {
|
||||
logBuffer.shift();
|
||||
}
|
||||
}
|
||||
|
||||
console.log = (...args) => {
|
||||
addLog('INFO', args);
|
||||
originalLog.apply(console, args);
|
||||
};
|
||||
console.error = (...args) => {
|
||||
addLog('ERROR', args);
|
||||
originalError.apply(console, args);
|
||||
};
|
||||
console.warn = (...args) => {
|
||||
addLog('WARN', args);
|
||||
originalWarn.apply(console, args);
|
||||
};
|
||||
|
||||
const app = express();
|
||||
app.use(cors({
|
||||
origin: true,
|
||||
@@ -760,6 +794,90 @@ app.put('/api/auth/update-email', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ========== ADMIN SYSTEM ENDPOINTS ==========
|
||||
|
||||
app.post('/api/admin/mongosh', async (req, res) => {
|
||||
try {
|
||||
const { command } = req.body;
|
||||
if (!command) {
|
||||
return res.status(400).json({ error: 'Comando requerido' });
|
||||
}
|
||||
|
||||
const dbInstance = getDb();
|
||||
if (!dbInstance) {
|
||||
return res.json({ result: 'Advertencia: MongoDB no está conectado. Utilizando store en memoria. Las consultas de mongosh no están disponibles.', isMemStore: true });
|
||||
}
|
||||
|
||||
const dbProxy = new Proxy(dbInstance, {
|
||||
get(target, prop) {
|
||||
if (typeof target[prop] !== 'undefined') {
|
||||
if (typeof target[prop] === 'function') {
|
||||
return target[prop].bind(target);
|
||||
}
|
||||
return target[prop];
|
||||
}
|
||||
return target.collection(prop);
|
||||
}
|
||||
});
|
||||
|
||||
const { ObjectId } = await import('mongodb');
|
||||
|
||||
const evalFn = new Function('db', 'ObjectId', `
|
||||
return (async () => {
|
||||
${command.trim().includes('return') || command.trim().startsWith('{') || command.trim().includes(';') ? command : 'return (' + command + ')'}
|
||||
})();
|
||||
`);
|
||||
|
||||
const result = await evalFn(dbProxy, ObjectId);
|
||||
res.json({ result });
|
||||
} catch (err) {
|
||||
res.status(400).json({ error: err.message || String(err) });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/admin/db/export', async (req, res) => {
|
||||
try {
|
||||
const dump = await exportAllData();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=hospital_backup.json');
|
||||
res.json(dump);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error al exportar base de datos: ' + err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/admin/db/import', async (req, res) => {
|
||||
try {
|
||||
const dump = req.body;
|
||||
if (!dump || typeof dump !== 'object') {
|
||||
return res.status(400).json({ error: 'Formato de importación inválido' });
|
||||
}
|
||||
await importAllData(dump);
|
||||
res.json({ ok: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error al importar base de datos: ' + err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/admin/logs', async (req, res) => {
|
||||
try {
|
||||
let fileLogs = '';
|
||||
try {
|
||||
const fs = await import('fs/promises');
|
||||
fileLogs = await fs.readFile('server_log.txt', 'utf8');
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const memoryLogs = logBuffer.join('\n');
|
||||
const combined = [fileLogs, '\n--- LOGS DE LA SESION EN VIVO ---', memoryLogs].filter(Boolean).join('\n');
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.send(combined);
|
||||
} catch (err) {
|
||||
res.status(500).send('Error al leer logs: ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize DB and start server
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
|
||||
@@ -993,3 +993,68 @@ export async function setValue(key, value) {
|
||||
memStore.kv[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDb() {
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function exportAllData() {
|
||||
const collections = [
|
||||
'usuarios', 'pacientes', 'areas', 'camas', 'internaciones',
|
||||
'evoluciones', 'laboratorios', 'glucemias', 'acidosbase',
|
||||
'cultivos', 'estudiosComplementarios', 'interconsultas', 'atb',
|
||||
'indicaciones', 'movimientos_indicaciones', 'pendientes', 'kv'
|
||||
];
|
||||
const dump = {};
|
||||
if (db) {
|
||||
for (const name of collections) {
|
||||
const docs = await db.collection(name).find().toArray();
|
||||
dump[name] = docs;
|
||||
}
|
||||
} else {
|
||||
for (const name of collections) {
|
||||
dump[name] = [...(memStore[name] || [])];
|
||||
}
|
||||
}
|
||||
return dump;
|
||||
}
|
||||
|
||||
export async function importAllData(dump) {
|
||||
const collections = [
|
||||
'usuarios', 'pacientes', 'areas', 'camas', 'internaciones',
|
||||
'evoluciones', 'laboratorios', 'glucemias', 'acidosbase',
|
||||
'cultivos', 'estudiosComplementarios', 'interconsultas', 'atb',
|
||||
'indicaciones', 'movimientos_indicaciones', 'pendientes', 'kv'
|
||||
];
|
||||
if (db) {
|
||||
for (const name of collections) {
|
||||
if (Array.isArray(dump[name])) {
|
||||
try {
|
||||
await db.collection(name).deleteMany({});
|
||||
} catch (e) {
|
||||
console.warn(`Could not clear collection ${name}:`, e);
|
||||
}
|
||||
if (dump[name].length > 0) {
|
||||
const cleaned = dump[name].map(doc => {
|
||||
const copy = { ...doc };
|
||||
if (copy._id) {
|
||||
if (ObjectId.isValid(copy._id)) {
|
||||
copy._id = new ObjectId(copy._id);
|
||||
} else {
|
||||
delete copy._id;
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
});
|
||||
await db.collection(name).insertMany(cleaned);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const name of collections) {
|
||||
if (Array.isArray(dump[name])) {
|
||||
memStore[name] = [...dump[name]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user