Commit the app code currently running in Docker, including scheduler, store status, journal reminder, maintenance mode, and Foodsharing API check updates. Remove generated runtime logs from version control and ignore local runtime captures.
136 lines
4.1 KiB
JavaScript
136 lines
4.1 KiB
JavaScript
const adminConfig = require('./adminConfig');
|
|
const requestLogStore = require('./requestLogStore');
|
|
|
|
const SIGNIFICANT_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
|
|
function createMaintenanceError(message) {
|
|
const error = new Error(message || 'Wartungsmodus aktiv');
|
|
error.code = 'MAINTENANCE_MODE_ACTIVE';
|
|
error.statusCode = 503;
|
|
return error;
|
|
}
|
|
|
|
function getSnapshot() {
|
|
const settings = adminConfig.readSettings();
|
|
return {
|
|
active: !!settings.maintenanceModeActive,
|
|
autoEnabled: !!settings.maintenanceModeAutoEnabled,
|
|
activatedAt: settings.maintenanceModeActivatedAt || null,
|
|
reason: settings.maintenanceModeReason || '',
|
|
errorThreshold: Number(settings.maintenanceModeErrorThreshold) || 8,
|
|
errorWindowMinutes: Number(settings.maintenanceModeErrorWindowMinutes) || 10
|
|
};
|
|
}
|
|
|
|
function isActive() {
|
|
return getSnapshot().active;
|
|
}
|
|
|
|
function isSignificantOutboundApiError(entry) {
|
|
if (entry?.direction !== 'outgoing' || entry?.target !== 'foodsharing.de') {
|
|
return false;
|
|
}
|
|
const status = Number(entry?.status);
|
|
if (Number.isFinite(status)) {
|
|
return SIGNIFICANT_STATUS_CODES.has(status);
|
|
}
|
|
return Boolean(entry?.error);
|
|
}
|
|
|
|
function activate({ reason } = {}) {
|
|
const snapshot = getSnapshot();
|
|
if (snapshot.active) {
|
|
return snapshot;
|
|
}
|
|
const next = adminConfig.writeSettings({
|
|
maintenanceModeActive: true,
|
|
maintenanceModeActivatedAt: new Date().toISOString(),
|
|
maintenanceModeReason: reason || 'Automatisch aktiviert'
|
|
});
|
|
console.warn(`[MAINTENANCE] Wartungsmodus aktiviert: ${next.maintenanceModeReason || 'ohne Grundangabe'}`);
|
|
return {
|
|
active: !!next.maintenanceModeActive,
|
|
autoEnabled: !!next.maintenanceModeAutoEnabled,
|
|
activatedAt: next.maintenanceModeActivatedAt || null,
|
|
reason: next.maintenanceModeReason || '',
|
|
errorThreshold: Number(next.maintenanceModeErrorThreshold) || 8,
|
|
errorWindowMinutes: Number(next.maintenanceModeErrorWindowMinutes) || 10
|
|
};
|
|
}
|
|
|
|
function activateAutomatically({ reason } = {}) {
|
|
const snapshot = activate({ reason });
|
|
if (!snapshot.active) {
|
|
return snapshot;
|
|
}
|
|
try {
|
|
const notificationService = require('./notificationService');
|
|
Promise.resolve(
|
|
notificationService.sendAdminMaintenanceModeNotification({
|
|
reason: snapshot.reason,
|
|
activatedAt: snapshot.activatedAt,
|
|
errorThreshold: snapshot.errorThreshold,
|
|
errorWindowMinutes: snapshot.errorWindowMinutes
|
|
})
|
|
).catch((error) => {
|
|
console.error('[MAINTENANCE] Admin-Benachrichtigung konnte nicht versendet werden:', error.message);
|
|
});
|
|
} catch (error) {
|
|
console.error('[MAINTENANCE] Notification-Service konnte nicht geladen werden:', error.message);
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
function deactivate() {
|
|
const next = adminConfig.writeSettings({
|
|
maintenanceModeActive: false,
|
|
maintenanceModeActivatedAt: null,
|
|
maintenanceModeReason: ''
|
|
});
|
|
console.warn('[MAINTENANCE] Wartungsmodus deaktiviert.');
|
|
return next;
|
|
}
|
|
|
|
function ensureInactive(message) {
|
|
if (!isActive()) {
|
|
return;
|
|
}
|
|
throw createMaintenanceError(message);
|
|
}
|
|
|
|
function recordOutboundFailure(entry) {
|
|
if (!isSignificantOutboundApiError(entry)) {
|
|
return getSnapshot();
|
|
}
|
|
const snapshot = getSnapshot();
|
|
if (!snapshot.autoEnabled || snapshot.active) {
|
|
return snapshot;
|
|
}
|
|
const cutoff = Date.now() - snapshot.errorWindowMinutes * 60 * 1000;
|
|
const logs = requestLogStore.list();
|
|
const recentFailures = logs.filter(
|
|
(item) => Number(item?.timestamp) >= cutoff && isSignificantOutboundApiError(item)
|
|
);
|
|
if (recentFailures.length < snapshot.errorThreshold) {
|
|
return snapshot;
|
|
}
|
|
const statusSummary = recentFailures
|
|
.slice(0, 5)
|
|
.map((item) => item.status || item.error || 'unbekannt')
|
|
.join(', ');
|
|
return activateAutomatically({
|
|
reason: `${recentFailures.length} relevante Outbound-API-Fehler in ${snapshot.errorWindowMinutes} Minuten (z. B. ${statusSummary})`
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
activate,
|
|
activateAutomatically,
|
|
createMaintenanceError,
|
|
deactivate,
|
|
ensureInactive,
|
|
getSnapshot,
|
|
isActive,
|
|
recordOutboundFailure
|
|
};
|