2026-06-04 21:32:28 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
2026-05-03 19:51:57 -05:00
|
|
|
const MAX_RECENT_ERRORS = 10;
|
|
|
|
|
|
2026-06-04 21:32:28 -05:00
|
|
|
// Keys written to the settings table for cross-restart persistence
|
|
|
|
|
const DB_KEY = {
|
|
|
|
|
workerLastRun: '_worker_last_run_at',
|
|
|
|
|
workerNextRun: '_worker_next_run_at',
|
|
|
|
|
workerError: '_worker_last_error',
|
|
|
|
|
workerStarted: '_worker_started_at',
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-03 19:51:57 -05:00
|
|
|
const state = {
|
|
|
|
|
worker: {
|
2026-06-04 21:32:28 -05:00
|
|
|
enabled: false,
|
|
|
|
|
running: false,
|
|
|
|
|
started_at: null,
|
2026-05-03 19:51:57 -05:00
|
|
|
last_run_at: null,
|
|
|
|
|
next_run_at: null,
|
2026-06-04 21:32:28 -05:00
|
|
|
last_error: null,
|
2026-05-03 19:51:57 -05:00
|
|
|
},
|
|
|
|
|
notifications: {
|
|
|
|
|
last_test_at: null,
|
2026-06-04 21:32:28 -05:00
|
|
|
last_error: null,
|
2026-05-03 19:51:57 -05:00
|
|
|
},
|
|
|
|
|
recentErrors: [],
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-04 21:32:28 -05:00
|
|
|
// Seed from DB on first load so status survives container restarts.
|
|
|
|
|
// Wrapped in try/catch — DB may not be ready at require-time in tests.
|
|
|
|
|
function seedFromDb() {
|
|
|
|
|
try {
|
|
|
|
|
const { getSetting } = require('../db/database');
|
|
|
|
|
state.worker.last_run_at = getSetting(DB_KEY.workerLastRun) || null;
|
|
|
|
|
state.worker.next_run_at = getSetting(DB_KEY.workerNextRun) || null;
|
|
|
|
|
state.worker.last_error = getSetting(DB_KEY.workerError) || null;
|
|
|
|
|
state.worker.started_at = getSetting(DB_KEY.workerStarted) || null;
|
|
|
|
|
if (state.worker.last_run_at) state.worker.enabled = true;
|
|
|
|
|
} catch { /* DB not ready — will be populated when workers run */ }
|
|
|
|
|
}
|
|
|
|
|
seedFromDb();
|
|
|
|
|
|
|
|
|
|
function dbSet(key, value) {
|
|
|
|
|
try {
|
|
|
|
|
const { setSetting } = require('../db/database');
|
|
|
|
|
setSetting(key, value == null ? '' : String(value));
|
|
|
|
|
} catch { /* non-fatal */ }
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 19:51:57 -05:00
|
|
|
function toMessage(error) {
|
|
|
|
|
if (!error) return null;
|
|
|
|
|
if (typeof error === 'string') return error;
|
|
|
|
|
return error.message || String(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function recordError(source, error) {
|
|
|
|
|
const message = toMessage(error);
|
|
|
|
|
if (!message) return;
|
2026-06-04 21:32:28 -05:00
|
|
|
state.recentErrors.unshift({ timestamp: new Date().toISOString(), source, message });
|
2026-05-03 19:51:57 -05:00
|
|
|
state.recentErrors = state.recentErrors.slice(0, MAX_RECENT_ERRORS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markWorkerStarted(nextRunAt = null) {
|
2026-06-04 21:32:28 -05:00
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
state.worker.enabled = true;
|
|
|
|
|
state.worker.running = true;
|
|
|
|
|
state.worker.started_at = state.worker.started_at || now;
|
2026-05-03 19:51:57 -05:00
|
|
|
state.worker.next_run_at = nextRunAt;
|
2026-06-04 21:32:28 -05:00
|
|
|
dbSet(DB_KEY.workerStarted, state.worker.started_at);
|
|
|
|
|
if (nextRunAt) dbSet(DB_KEY.workerNextRun, nextRunAt);
|
2026-05-03 19:51:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markWorkerSuccess(nextRunAt = null) {
|
2026-06-04 21:32:28 -05:00
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
state.worker.running = false;
|
|
|
|
|
state.worker.last_run_at = now;
|
|
|
|
|
state.worker.last_error = null;
|
2026-05-03 19:51:57 -05:00
|
|
|
if (nextRunAt !== undefined) state.worker.next_run_at = nextRunAt;
|
2026-06-04 21:32:28 -05:00
|
|
|
dbSet(DB_KEY.workerLastRun, now);
|
|
|
|
|
dbSet(DB_KEY.workerError, '');
|
|
|
|
|
if (nextRunAt) dbSet(DB_KEY.workerNextRun, nextRunAt);
|
2026-05-03 19:51:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markWorkerError(error, nextRunAt = null) {
|
2026-06-04 21:32:28 -05:00
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
state.worker.running = false;
|
|
|
|
|
state.worker.last_run_at = now;
|
|
|
|
|
state.worker.last_error = toMessage(error);
|
2026-05-03 19:51:57 -05:00
|
|
|
if (nextRunAt !== undefined) state.worker.next_run_at = nextRunAt;
|
2026-06-04 21:32:28 -05:00
|
|
|
dbSet(DB_KEY.workerLastRun, now);
|
|
|
|
|
dbSet(DB_KEY.workerError, state.worker.last_error || '');
|
|
|
|
|
if (nextRunAt) dbSet(DB_KEY.workerNextRun, nextRunAt);
|
2026-05-03 19:51:57 -05:00
|
|
|
recordError('Daily Worker', error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markNotificationTestSuccess() {
|
|
|
|
|
state.notifications.last_test_at = new Date().toISOString();
|
2026-06-04 21:32:28 -05:00
|
|
|
state.notifications.last_error = null;
|
2026-05-03 19:51:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markNotificationError(error) {
|
|
|
|
|
state.notifications.last_error = toMessage(error);
|
|
|
|
|
recordError('Notifications', error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function markNotificationSuccess() {
|
|
|
|
|
state.notifications.last_error = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getStatusRuntime() {
|
|
|
|
|
return {
|
2026-06-04 21:32:28 -05:00
|
|
|
worker: { ...state.worker },
|
2026-05-03 19:51:57 -05:00
|
|
|
notifications: { ...state.notifications },
|
|
|
|
|
recentErrors: [...state.recentErrors],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
getStatusRuntime,
|
|
|
|
|
markNotificationError,
|
|
|
|
|
markNotificationSuccess,
|
|
|
|
|
markNotificationTestSuccess,
|
|
|
|
|
markWorkerError,
|
|
|
|
|
markWorkerStarted,
|
|
|
|
|
markWorkerSuccess,
|
|
|
|
|
recordError,
|
|
|
|
|
};
|