230 lines
7.8 KiB
JavaScript
230 lines
7.8 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const CONFIG_DIR = path.join(__dirname, '..', 'config');
|
|
const SETTINGS_FILE = path.join(CONFIG_DIR, 'admin-settings.json');
|
|
|
|
const DEFAULT_SETTINGS = {
|
|
scheduleCron: '*/10 7-22 * * *',
|
|
pickupFallbackCron: '0 7,12,17,22 * * *',
|
|
pickupWindowOffsetsMinutes: [-1, -0.5, 0, 0.5, 1, 1.5],
|
|
regularPickupRefreshCron: '0 3 * * *',
|
|
dormantMembershipCron: '0 4 */14 * *',
|
|
randomDelayMinSeconds: 10,
|
|
randomDelayMaxSeconds: 120,
|
|
initialDelayMinSeconds: 5,
|
|
initialDelayMaxSeconds: 30,
|
|
storeWatchCron: '*/30 * * * *',
|
|
storeWatchInitialDelayMinSeconds: 10,
|
|
storeWatchInitialDelayMaxSeconds: 60,
|
|
storeWatchRequestDelayMs: 1000,
|
|
storeWatchStatusCacheMaxAgeMinutes: 120,
|
|
storePickupCheckDelayMs: 400,
|
|
ignoredSlots: [
|
|
{
|
|
storeId: '51450',
|
|
slotName: 'TVS',
|
|
info: ''
|
|
}
|
|
],
|
|
notifications: {
|
|
ntfy: {
|
|
enabled: false,
|
|
serverUrl: 'https://ntfy.sh',
|
|
topicPrefix: '',
|
|
username: '',
|
|
password: ''
|
|
},
|
|
telegram: {
|
|
enabled: false,
|
|
botToken: '',
|
|
chatId: ''
|
|
}
|
|
}
|
|
};
|
|
|
|
function ensureDir() {
|
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
function sanitizeNumber(value, fallback) {
|
|
const num = Number(value);
|
|
if (Number.isFinite(num) && num >= 0) {
|
|
return num;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function sanitizeNumberArray(value, fallback) {
|
|
if (Array.isArray(value)) {
|
|
const cleaned = value
|
|
.map((entry) => Number(entry))
|
|
.filter((entry) => Number.isFinite(entry));
|
|
return cleaned.length > 0 ? cleaned : fallback;
|
|
}
|
|
if (typeof value === 'string') {
|
|
const cleaned = value
|
|
.split(',')
|
|
.map((entry) => Number(entry.trim()))
|
|
.filter((entry) => Number.isFinite(entry));
|
|
return cleaned.length > 0 ? cleaned : fallback;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function sanitizeIgnoredSlots(slots = []) {
|
|
if (!Array.isArray(slots)) {
|
|
return DEFAULT_SETTINGS.ignoredSlots;
|
|
}
|
|
return slots
|
|
.map((slot) => {
|
|
const slotName = slot?.slotName ?? slot?.description;
|
|
return {
|
|
storeId: slot?.storeId ? String(slot.storeId) : '',
|
|
slotName: slotName ? String(slotName) : '',
|
|
info: slot?.info ? String(slot.info) : ''
|
|
};
|
|
})
|
|
.filter((slot) => slot.storeId && slot.slotName);
|
|
}
|
|
|
|
function sanitizeString(value) {
|
|
if (typeof value === 'string') {
|
|
return value.trim();
|
|
}
|
|
if (value === null || value === undefined) {
|
|
return '';
|
|
}
|
|
return String(value).trim();
|
|
}
|
|
|
|
function sanitizeNotifications(input = {}) {
|
|
const defaults = DEFAULT_SETTINGS.notifications;
|
|
return {
|
|
ntfy: {
|
|
enabled: !!(input?.ntfy?.enabled),
|
|
serverUrl: sanitizeString(input?.ntfy?.serverUrl || defaults.ntfy.serverUrl),
|
|
topicPrefix: sanitizeString(input?.ntfy?.topicPrefix || defaults.ntfy.topicPrefix),
|
|
username: sanitizeString(input?.ntfy?.username || defaults.ntfy.username),
|
|
password: sanitizeString(input?.ntfy?.password || defaults.ntfy.password)
|
|
},
|
|
telegram: {
|
|
enabled: !!(input?.telegram?.enabled),
|
|
botToken: sanitizeString(input?.telegram?.botToken || defaults.telegram.botToken),
|
|
chatId: sanitizeString(input?.telegram?.chatId || defaults.telegram.chatId)
|
|
}
|
|
};
|
|
}
|
|
|
|
function readSettings() {
|
|
ensureDir();
|
|
if (!fs.existsSync(SETTINGS_FILE)) {
|
|
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(DEFAULT_SETTINGS, null, 2));
|
|
return { ...DEFAULT_SETTINGS };
|
|
}
|
|
|
|
try {
|
|
const raw = fs.readFileSync(SETTINGS_FILE, 'utf8');
|
|
const parsed = JSON.parse(raw);
|
|
return {
|
|
scheduleCron: parsed.scheduleCron || DEFAULT_SETTINGS.scheduleCron,
|
|
pickupFallbackCron: parsed.pickupFallbackCron || DEFAULT_SETTINGS.pickupFallbackCron,
|
|
pickupWindowOffsetsMinutes: sanitizeNumberArray(
|
|
parsed.pickupWindowOffsetsMinutes,
|
|
DEFAULT_SETTINGS.pickupWindowOffsetsMinutes
|
|
),
|
|
regularPickupRefreshCron: parsed.regularPickupRefreshCron || DEFAULT_SETTINGS.regularPickupRefreshCron,
|
|
dormantMembershipCron: parsed.dormantMembershipCron || DEFAULT_SETTINGS.dormantMembershipCron,
|
|
randomDelayMinSeconds: sanitizeNumber(parsed.randomDelayMinSeconds, DEFAULT_SETTINGS.randomDelayMinSeconds),
|
|
randomDelayMaxSeconds: sanitizeNumber(parsed.randomDelayMaxSeconds, DEFAULT_SETTINGS.randomDelayMaxSeconds),
|
|
initialDelayMinSeconds: sanitizeNumber(parsed.initialDelayMinSeconds, DEFAULT_SETTINGS.initialDelayMinSeconds),
|
|
initialDelayMaxSeconds: sanitizeNumber(parsed.initialDelayMaxSeconds, DEFAULT_SETTINGS.initialDelayMaxSeconds),
|
|
storeWatchCron: parsed.storeWatchCron || DEFAULT_SETTINGS.storeWatchCron,
|
|
storeWatchInitialDelayMinSeconds: sanitizeNumber(
|
|
parsed.storeWatchInitialDelayMinSeconds,
|
|
DEFAULT_SETTINGS.storeWatchInitialDelayMinSeconds
|
|
),
|
|
storeWatchInitialDelayMaxSeconds: sanitizeNumber(
|
|
parsed.storeWatchInitialDelayMaxSeconds,
|
|
DEFAULT_SETTINGS.storeWatchInitialDelayMaxSeconds
|
|
),
|
|
storeWatchRequestDelayMs: sanitizeNumber(
|
|
parsed.storeWatchRequestDelayMs,
|
|
DEFAULT_SETTINGS.storeWatchRequestDelayMs
|
|
),
|
|
storeWatchStatusCacheMaxAgeMinutes: sanitizeNumber(
|
|
parsed.storeWatchStatusCacheMaxAgeMinutes,
|
|
DEFAULT_SETTINGS.storeWatchStatusCacheMaxAgeMinutes
|
|
),
|
|
storePickupCheckDelayMs: sanitizeNumber(
|
|
parsed.storePickupCheckDelayMs,
|
|
DEFAULT_SETTINGS.storePickupCheckDelayMs
|
|
),
|
|
ignoredSlots: sanitizeIgnoredSlots(parsed.ignoredSlots),
|
|
notifications: sanitizeNotifications(parsed.notifications)
|
|
};
|
|
} catch (error) {
|
|
console.error('Konnte Admin-Einstellungen nicht lesen:', error.message);
|
|
return { ...DEFAULT_SETTINGS };
|
|
}
|
|
}
|
|
|
|
function writeSettings(patch = {}) {
|
|
const current = readSettings();
|
|
const next = {
|
|
scheduleCron: patch.scheduleCron || current.scheduleCron,
|
|
pickupFallbackCron: patch.pickupFallbackCron || current.pickupFallbackCron,
|
|
pickupWindowOffsetsMinutes: sanitizeNumberArray(
|
|
patch.pickupWindowOffsetsMinutes,
|
|
current.pickupWindowOffsetsMinutes
|
|
),
|
|
regularPickupRefreshCron: patch.regularPickupRefreshCron || current.regularPickupRefreshCron,
|
|
dormantMembershipCron: patch.dormantMembershipCron || current.dormantMembershipCron,
|
|
randomDelayMinSeconds: sanitizeNumber(patch.randomDelayMinSeconds, current.randomDelayMinSeconds),
|
|
randomDelayMaxSeconds: sanitizeNumber(patch.randomDelayMaxSeconds, current.randomDelayMaxSeconds),
|
|
initialDelayMinSeconds: sanitizeNumber(patch.initialDelayMinSeconds, current.initialDelayMinSeconds),
|
|
initialDelayMaxSeconds: sanitizeNumber(patch.initialDelayMaxSeconds, current.initialDelayMaxSeconds),
|
|
storeWatchCron: patch.storeWatchCron || current.storeWatchCron,
|
|
storeWatchInitialDelayMinSeconds: sanitizeNumber(
|
|
patch.storeWatchInitialDelayMinSeconds,
|
|
current.storeWatchInitialDelayMinSeconds
|
|
),
|
|
storeWatchInitialDelayMaxSeconds: sanitizeNumber(
|
|
patch.storeWatchInitialDelayMaxSeconds,
|
|
current.storeWatchInitialDelayMaxSeconds
|
|
),
|
|
storeWatchRequestDelayMs: sanitizeNumber(
|
|
patch.storeWatchRequestDelayMs,
|
|
current.storeWatchRequestDelayMs
|
|
),
|
|
storeWatchStatusCacheMaxAgeMinutes: sanitizeNumber(
|
|
patch.storeWatchStatusCacheMaxAgeMinutes,
|
|
current.storeWatchStatusCacheMaxAgeMinutes
|
|
),
|
|
storePickupCheckDelayMs: sanitizeNumber(
|
|
patch.storePickupCheckDelayMs,
|
|
current.storePickupCheckDelayMs
|
|
),
|
|
ignoredSlots:
|
|
patch.ignoredSlots !== undefined
|
|
? sanitizeIgnoredSlots(patch.ignoredSlots)
|
|
: current.ignoredSlots,
|
|
notifications:
|
|
patch.notifications !== undefined
|
|
? sanitizeNotifications(patch.notifications)
|
|
: current.notifications
|
|
};
|
|
|
|
ensureDir();
|
|
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(next, null, 2));
|
|
return next;
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_SETTINGS,
|
|
readSettings,
|
|
writeSettings
|
|
};
|