diff --git a/server.js b/server.js index 252ca47..dd70194 100644 --- a/server.js +++ b/server.js @@ -13,7 +13,11 @@ const { readNotificationSettings, writeNotificationSettings } = require('./servi const notificationService = require('./services/notificationService'); const { readStoreWatch, writeStoreWatch, listWatcherProfiles } = require('./services/storeWatchStore'); const { readPreferences, writePreferences, sanitizeLocation } = require('./services/userPreferencesStore'); -const { readStoreStatus, writeStoreStatus } = require('./services/storeStatusStore'); +const { + getStoreStatus: getCachedStoreStatusEntry, + setStoreStatus: setCachedStoreStatusEntry, + persistStoreStatusCache +} = require('./services/storeStatusCache'); const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000; const adminEmail = (process.env.ADMIN_EMAIL || '').toLowerCase(); @@ -26,7 +30,6 @@ const cachedStoreSnapshots = new Map(); const regionStoreCache = new Map(); const REGION_STORE_CACHE_MS = 15 * 60 * 1000; const STORE_STATUS_MAX_AGE_MS = 60 * 24 * 60 * 60 * 1000; -const storeStatusCache = new Map(); const storeLocationIndex = new Map(); let storeLocationIndexUpdatedAt = 0; const STORE_LOCATION_INDEX_TTL_MS = 12 * 60 * 60 * 1000; @@ -56,33 +59,6 @@ function haversineDistanceKm(lat1, lon1, lat2, lon2) { return R * c; } -(function bootstrapStoreStatusCache() { - try { - const cached = readStoreStatus(); - Object.entries(cached || {}).forEach(([storeId, entry]) => { - if (entry && typeof entry === 'object') { - storeStatusCache.set(String(storeId), { - teamSearchStatus: - entry.teamSearchStatus === null || entry.teamSearchStatus === undefined - ? null - : Number(entry.teamSearchStatus), - fetchedAt: Number(entry.fetchedAt) || 0 - }); - } - }); - } catch (error) { - console.error('[STORE-STATUS] Bootstrap fehlgeschlagen:', error.message); - } -})(); - -function persistStoreStatusCache() { - const payload = {}; - storeStatusCache.forEach((value, key) => { - payload[key] = value; - }); - writeStoreStatus(payload); -} - app.use(cors()); app.use(express.json({ limit: '1mb' })); app.use(express.static(path.join(__dirname, 'build'))); @@ -170,7 +146,7 @@ function setCachedRegionStores(regionId, payload) { } function getCachedStoreStatus(storeId) { - return storeStatusCache.get(String(storeId)) || null; + return getCachedStoreStatusEntry(storeId); } function ingestStoreLocations(stores = []) { @@ -318,7 +294,7 @@ async function refreshStoreStatus( const changes = []; for (const id of storeIds) { const storeId = String(id); - const entry = storeStatusCache.get(storeId); + const entry = getCachedStoreStatus(storeId); const ageOk = entry && now - (entry.fetchedAt || 0) <= STORE_STATUS_MAX_AGE_MS; if (!force && ageOk) { continue; @@ -328,7 +304,7 @@ async function refreshStoreStatus( const status = Number(details?.teamSearchStatus); const normalized = Number.isFinite(status) ? status : null; const previous = entry ? entry.teamSearchStatus : null; - storeStatusCache.set(storeId, { + setCachedStoreStatusEntry(storeId, { teamSearchStatus: normalized, fetchedAt: now }); diff --git a/services/notificationService.js b/services/notificationService.js index 334e38d..bd99419 100644 --- a/services/notificationService.js +++ b/services/notificationService.js @@ -137,10 +137,10 @@ async function sendStoreWatchSummaryNotification({ profileId, entries = [], trig .join('\n'); const prefix = triggeredBy === 'manual' - ? 'Manuell angestoßene Store-Watch-Prüfung abgeschlossen:' + ? 'Manuell angestoßene Store-Watch-Aktualisierung abgeschlossen:' : 'Store-Watch-Prüfung abgeschlossen:'; const title = - triggeredBy === 'manual' ? 'Ad-hoc Store-Watch-Prüfung' : 'Store-Watch-Prüfung'; + triggeredBy === 'manual' ? 'Store-Watch-Aktualisierung' : 'Store-Watch-Prüfung'; const message = `${prefix}\n${lines}`; await notifyChannels(profileId, { title, diff --git a/services/pickupScheduler.js b/services/pickupScheduler.js index 260907d..473bb50 100644 --- a/services/pickupScheduler.js +++ b/services/pickupScheduler.js @@ -5,6 +5,7 @@ const { DEFAULT_SETTINGS } = require('./adminConfig'); const notificationService = require('./notificationService'); const { readConfig, writeConfig } = require('./configStore'); const { readStoreWatch, writeStoreWatch } = require('./storeWatchStore'); +const { setStoreStatus, persistStoreStatusCache } = require('./storeStatusCache'); function wait(ms) { if (!ms || ms <= 0) { @@ -428,6 +429,7 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option const perRequestDelay = Math.max(0, Number(settings?.storeWatchRequestDelayMs) || 0); let changed = false; + let statusCacheUpdated = false; const summary = []; for (let index = 0; index < watchers.length; index += 1) { const watcher = watchers[index]; @@ -449,6 +451,8 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option } watcher.lastStatusCheckAt = checkedAt; changed = true; + setStoreStatus(watcher.storeId, { teamSearchStatus: status, fetchedAt: checkedAt }); + statusCacheUpdated = true; summary.push({ storeId: watcher.storeId, storeName: watcher.storeName, @@ -477,6 +481,9 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option if (changed) { writeStoreWatch(session.profile.id, watchers); } + if (statusCacheUpdated) { + persistStoreStatusCache(); + } if (options.sendSummary && summary.length > 0) { try { await notificationService.sendStoreWatchSummaryNotification({ diff --git a/services/storeStatusCache.js b/services/storeStatusCache.js new file mode 100644 index 0000000..3ee7904 --- /dev/null +++ b/services/storeStatusCache.js @@ -0,0 +1,66 @@ +const { readStoreStatus, writeStoreStatus } = require('./storeStatusStore'); + +const storeStatusCache = new Map(); + +function normalizeStatusEntry(entry = {}) { + const status = Number(entry.teamSearchStatus); + const fetchedAt = Number(entry.fetchedAt) || 0; + return { + teamSearchStatus: Number.isFinite(status) ? status : null, + fetchedAt + }; +} + +(function bootstrapStoreStatusCache() { + try { + const cached = readStoreStatus(); + Object.entries(cached || {}).forEach(([storeId, entry]) => { + if (!storeId) { + return; + } + storeStatusCache.set(String(storeId), normalizeStatusEntry(entry)); + }); + } catch (error) { + console.error('[STORE-STATUS] Bootstrap fehlgeschlagen:', error.message); + } +})(); + +function getStoreStatus(storeId) { + if (!storeId) { + return null; + } + return storeStatusCache.get(String(storeId)) || null; +} + +function setStoreStatus(storeId, data = {}) { + if (!storeId) { + return null; + } + const normalized = normalizeStatusEntry(data); + storeStatusCache.set(String(storeId), normalized); + return normalized; +} + +function bulkSetStoreStatus(entries = []) { + entries.forEach((entry) => { + if (!entry || !entry.storeId) { + return; + } + setStoreStatus(entry.storeId, entry); + }); +} + +function persistStoreStatusCache() { + const payload = {}; + storeStatusCache.forEach((value, key) => { + payload[key] = value; + }); + writeStoreStatus(payload); +} + +module.exports = { + getStoreStatus, + setStoreStatus, + bulkSetStoreStatus, + persistStoreStatusCache +}; diff --git a/src/components/StoreWatchPage.js b/src/components/StoreWatchPage.js index c389f50..a3e0859 100644 --- a/src/components/StoreWatchPage.js +++ b/src/components/StoreWatchPage.js @@ -986,11 +986,11 @@ const StoreWatchPage = ({ checkedAt: Date.now(), stores: summary }); - setStatus('Ad-hoc-Prüfung abgeschlossen. Zusammenfassung versendet.'); + setStatus('Aktualisierung abgeschlossen. Zusammenfassung versendet.'); setTimeout(() => setStatus(''), 4000); await loadSubscriptions(); } catch (err) { - setError(`Ad-hoc-Prüfung fehlgeschlagen: ${err.message}`); + setError(`Aktualisierung fehlgeschlagen: ${err.message}`); } finally { setAdhocChecking(false); } @@ -1019,7 +1019,7 @@ const StoreWatchPage = ({ onClick={handleAdhocWatchCheck} disabled={adhocChecking || subscriptionsLoading || watchList.length === 0} > - {adhocChecking ? 'Prüfe...' : 'Ad-hoc prüfen'} + {adhocChecking ? 'Aktualisiere...' : 'Aktualisieren'}