import { createApp } from './src/app.js'; import { ensureSchema, disableGuestsFromYesterday, getActiveSessions, markStaleSessions, syncConnectedDevicesFromSessions, cleanOldSessions } from './src/services/db.js'; import { activeSessions } from './src/services/radius.js'; import { broadcastStatus } from './src/sse.js'; const app = createApp(); const port = process.env.PORT || 3000; // Configuration from environment const STALE_CHECK_INTERVAL = parseInt(process.env.STALE_CHECK_INTERVAL || '120000', 10); // 2 min const MAX_IDLE_MINUTES = parseInt(process.env.MAX_IDLE_MINUTES || '10', 10); const SESSION_HISTORY_RETENTION_DAYS = parseInt(process.env.SESSION_HISTORY_RETENTION_DAYS || '90', 10); try { await ensureSchema(); } catch (e) { console.error('Database schema ensure failed:', e?.message || e); } // Initialize sessions from database on startup async function initializeSessionsFromDb() { try { // Sync dispositivos_conectados from active sessions await syncConnectedDevicesFromSessions(); // Load active sessions into memory Map for CoA const active = await getActiveSessions(); for (const sess of active) { activeSessions.set(sess.session_id, { username: sess.username, sessionId: sess.session_id, nasIp: sess.nas_ip, nasId: sess.nas_id, callingStationId: sess.calling_station_id, calledStationId: sess.called_station_id, updatedAt: new Date(sess.last_update).getTime(), }); } console.log(`[init] Loaded ${active.length} active sessions from database`); } catch (e) { console.error('[init] Failed to load sessions:', e?.message || e); } } // Job to detect and mark stale sessions function scheduleStaleSessionsJob() { setInterval(async () => { try { const result = await markStaleSessions(MAX_IDLE_MINUTES); if (result.count > 0) { console.log(`[stale-sessions] Marked ${result.count} sessions as stale`); // Remove stale sessions from memory Map for (const sess of result.sessions) { activeSessions.delete(sess.session_id); } // Notify SSE clients broadcastStatus({ type: 'sessions-updated', staleCount: result.count }); } } catch (e) { console.error('[stale-sessions] Error:', e?.message || e); } }, STALE_CHECK_INTERVAL); } // Job to clean old sessions (runs daily at 3:00 AM local) function scheduleSessionCleanupJob() { function schedule() { const now = new Date(); const next = new Date(now); next.setUTCHours(9, 0, 0, 0); // 9:00 UTC = 3:00 AM Honduras (UTC-6) if (next <= now) next.setUTCDate(next.getUTCDate() + 1); const delay = next - now; setTimeout(async () => { try { const deleted = await cleanOldSessions(SESSION_HISTORY_RETENTION_DAYS); if (deleted > 0) { console.log(`[session-cleanup] Deleted ${deleted} old sessions (>${SESSION_HISTORY_RETENTION_DAYS} days)`); } } catch (e) { console.error('[session-cleanup] Error:', e?.message || e); } finally { schedule(); // Re-schedule for tomorrow } }, delay); } schedule(); } // Initialize sessions from database try { await initializeSessionsFromDb(); } catch (e) { console.error('Session initialization failed:', e?.message || e); } // Start maintenance jobs scheduleStaleSessionsJob(); scheduleSessionCleanupJob(); app.listen(port, () => { console.log(`Node RADIUS REST API listening on :${port}`); console.log(`[config] Stale check: every ${STALE_CHECK_INTERVAL/1000}s, idle timeout: ${MAX_IDLE_MINUTES} min, retention: ${SESSION_HISTORY_RETENTION_DAYS} days`); }); // Schedule daily guest disable at 4:00 AM America/Tegucigalpa (UTC-6 -> 10:00 UTC) function scheduleGuestJob() { const now = new Date(); const next = new Date(now); next.setUTCHours(10, 0, 0, 0); // 10:00 UTC == 4:00 local (UTC-6) if (next <= now) next.setUTCDate(next.getUTCDate() + 1); const delay = next - now; setTimeout(async function run() { try { const { count } = await disableGuestsFromYesterday(); if (count) console.log(`[guest-cron] Disabled ${count} invitado users from yesterday`); } catch (e) { console.error('[guest-cron] Error:', e?.message || e); } finally { scheduleGuestJob(); } }, delay); } scheduleGuestJob();