'use strict'; /* Ретеншн данных онлайн-уроков: у завершённых сессий старше N дней удаляем * тяжёлые данные доски — штрихи + файлы-картинки. Саму сессию, посещаемость и * чат НЕ трогаем (история/сводка/экспорт продолжают работать). * Период: app_settings.classroom_retention_days (по умолчанию 30; 0 = выключено). */ const fs = require('fs'); const path = require('path'); const db = require('./db/db'); const logger = require('./utils/logger'); const IMG_DIR = path.join(__dirname, '../uploads/classroom'); function _retentionDays() { try { const r = db.prepare("SELECT value FROM app_settings WHERE key='classroom_retention_days'").get(); if (r && r.value != null) { const n = Number(r.value); if (Number.isFinite(n) && n >= 0) return n; } } catch (e) {} return 30; } function cleanupClassroomData() { const days = _retentionDays(); if (!days) return { skipped: true }; let sessions = []; try { sessions = db.prepare( "SELECT id FROM classroom_sessions WHERE status='ended' AND ended_at IS NOT NULL AND ended_at < datetime('now', ?)" ).all('-' + days + ' days'); } catch (e) { return { error: e.message }; } if (!sessions.length) return { sessions: 0, strokes: 0, files: 0 }; const ids = new Set(sessions.map(s => s.id)); let strokes = 0; const del = db.prepare('DELETE FROM classroom_strokes WHERE session_id=?'); for (const id of ids) { try { strokes += del.run(id).changes; } catch (e) {} } // Файлы названы '-.' — удаляем по префиксу сессии. let files = 0; try { for (const f of fs.readdirSync(IMG_DIR)) { const m = /^(\d+)-/.exec(f); if (m && ids.has(Number(m[1]))) { try { fs.unlinkSync(path.join(IMG_DIR, f)); files++; } catch (e) {} } } } catch (e) { /* директории может не быть — это норма */ } return { sessions: ids.size, strokes, files }; } /* Ретеншн error_log: не копим бесконечно (период app_settings.error_log_retention_days, по умолчанию 30; 0 = выключено). */ function cleanupErrorLog() { let days = 30; try { const r = db.prepare("SELECT value FROM app_settings WHERE key='error_log_retention_days'").get(); if (r && r.value != null) { const n = Number(r.value); if (Number.isFinite(n) && n >= 0) days = n; } } catch (e) {} if (!days) return 0; try { return db.prepare("DELETE FROM error_log WHERE created_at < datetime('now', ?)").run('-' + days + ' days').changes; } catch (e) { return 0; } } /* Раз в сутки + один прогон через минуту после старта. unref — не держит процесс. */ function schedule() { const run = () => { try { const r = cleanupClassroomData(); if (r && (r.strokes || r.files)) logger.info('classroom-cleanup', r); const errLogged = cleanupErrorLog(); if (errLogged) logger.info('error-log-cleanup', { deleted: errLogged }); } catch (e) {} }; setTimeout(run, 60_000).unref(); setInterval(run, 24 * 60 * 60 * 1000).unref(); } module.exports = { cleanupClassroomData, cleanupErrorLog, schedule };