ccfb151eca
- imggen: дневной счётчик генераций перенесён из in-memory Map в таблицу imggen_usage (миграция 070) — переживает рестарт. Cooldown остаётся в памяти, но добавлена периодическая чистка Map + старых строк imggen_usage (>7 дн). - classroom-cleanup: ретеншн error_log (app_settings.error_log_retention_days, по умолч. 30; 0 = выкл) в том же суточном джобе. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
3.3 KiB
JavaScript
81 lines
3.3 KiB
JavaScript
'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) {} }
|
|
|
|
// Файлы названы '<sessionId>-<rand>.<ext>' — удаляем по префиксу сессии.
|
|
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 };
|