165 lines
4.6 KiB
JavaScript
165 lines
4.6 KiB
JavaScript
const cron = require('node-cron');
|
|
const { getSetting, setSetting } = require('../db/database');
|
|
const { applyScheduledRetention, createBackup } = require('./backupService');
|
|
|
|
let task = null;
|
|
let nextRunAt = null;
|
|
let running = false;
|
|
|
|
function parseBool(value) {
|
|
return value === true || value === 'true';
|
|
}
|
|
|
|
function validateScheduleSettings(input = {}) {
|
|
const enabled = parseBool(input.enabled);
|
|
const frequency = input.frequency || 'daily';
|
|
const time = input.time || '02:00';
|
|
const retentionCount = parseInt(input.retention_count ?? '14', 10);
|
|
|
|
if (!['daily', 'weekly'].includes(frequency)) {
|
|
const err = new Error('frequency must be daily or weekly');
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
if (!/^\d{2}:\d{2}$/.test(time)) {
|
|
const err = new Error('time must use HH:MM format');
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
|
|
const [hour, minute] = time.split(':').map(Number);
|
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
|
const err = new Error('time must be a valid 24-hour time');
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
if (!Number.isInteger(retentionCount) || retentionCount < 1 || retentionCount > 365) {
|
|
const err = new Error('retention_count must be between 1 and 365');
|
|
err.status = 400;
|
|
throw err;
|
|
}
|
|
|
|
return { enabled, frequency, time, retention_count: retentionCount };
|
|
}
|
|
|
|
function readSettings() {
|
|
return validateScheduleSettings({
|
|
enabled: getSetting('backup_schedule_enabled') === 'true',
|
|
frequency: getSetting('backup_schedule_frequency') || 'daily',
|
|
time: getSetting('backup_schedule_time') || '02:00',
|
|
retention_count: getSetting('backup_schedule_retention_count') || '14',
|
|
});
|
|
}
|
|
|
|
function getLastRunAt() {
|
|
return getSetting('backup_schedule_last_run_at') || null;
|
|
}
|
|
|
|
function getLastError() {
|
|
return getSetting('backup_schedule_last_error') || null;
|
|
}
|
|
|
|
function computeNextRun(settings = readSettings(), from = new Date()) {
|
|
if (!settings.enabled) return null;
|
|
const [hour, minute] = settings.time.split(':').map(Number);
|
|
const next = new Date(from);
|
|
next.setHours(hour, minute, 0, 0);
|
|
|
|
if (settings.frequency === 'weekly') {
|
|
while (next <= from || next.getDay() !== 0) next.setDate(next.getDate() + 1);
|
|
return next.toISOString();
|
|
}
|
|
|
|
if (next <= from) next.setDate(next.getDate() + 1);
|
|
return next.toISOString();
|
|
}
|
|
|
|
function cronExpression(settings) {
|
|
const [hour, minute] = settings.time.split(':').map(Number);
|
|
return settings.frequency === 'weekly'
|
|
? `${minute} ${hour} * * 0`
|
|
: `${minute} ${hour} * * *`;
|
|
}
|
|
|
|
async function runScheduledBackupNow() {
|
|
if (running) {
|
|
const err = new Error('Scheduled backup is already running');
|
|
err.status = 409;
|
|
throw err;
|
|
}
|
|
|
|
running = true;
|
|
try {
|
|
const settings = readSettings();
|
|
const backup = await createBackup('scheduled-backup');
|
|
applyScheduledRetention(settings.retention_count);
|
|
setSetting('backup_schedule_last_run_at', new Date().toISOString());
|
|
setSetting('backup_schedule_last_error', '');
|
|
nextRunAt = computeNextRun(settings);
|
|
return backup;
|
|
} catch (err) {
|
|
setSetting('backup_schedule_last_error', err.message || 'Scheduled backup failed');
|
|
throw err;
|
|
} finally {
|
|
running = false;
|
|
}
|
|
}
|
|
|
|
function stop() {
|
|
if (task) task.stop();
|
|
task = null;
|
|
}
|
|
|
|
function reloadSchedule() {
|
|
stop();
|
|
const settings = readSettings();
|
|
nextRunAt = computeNextRun(settings);
|
|
|
|
if (!settings.enabled) return getScheduleStatus();
|
|
|
|
task = cron.schedule(cronExpression(settings), () => {
|
|
runScheduledBackupNow().catch(err => {
|
|
console.error('[backupScheduler] Scheduled backup failed:', err.message);
|
|
});
|
|
});
|
|
|
|
return getScheduleStatus();
|
|
}
|
|
|
|
function saveSettings(input) {
|
|
const settings = validateScheduleSettings(input);
|
|
setSetting('backup_schedule_enabled', settings.enabled ? 'true' : 'false');
|
|
setSetting('backup_schedule_frequency', settings.frequency);
|
|
setSetting('backup_schedule_time', settings.time);
|
|
setSetting('backup_schedule_retention_count', String(settings.retention_count));
|
|
return reloadSchedule();
|
|
}
|
|
|
|
function getScheduleStatus() {
|
|
const settings = readSettings();
|
|
return {
|
|
ok: !getLastError(),
|
|
enabled: settings.enabled,
|
|
running,
|
|
frequency: settings.frequency,
|
|
time: settings.time,
|
|
retention_count: settings.retention_count,
|
|
last_run_at: getLastRunAt(),
|
|
next_run_at: nextRunAt ?? computeNextRun(settings),
|
|
last_error: getLastError(),
|
|
};
|
|
}
|
|
|
|
function start() {
|
|
return reloadSchedule();
|
|
}
|
|
|
|
module.exports = {
|
|
getScheduleStatus,
|
|
reloadSchedule,
|
|
runScheduledBackupNow,
|
|
saveSettings,
|
|
start,
|
|
validateScheduleSettings,
|
|
};
|