button zum prüfen

This commit is contained in:
2025-11-17 21:57:03 +01:00
parent cad72232d9
commit ca81121f3d
5 changed files with 87 additions and 38 deletions

View File

@@ -13,7 +13,11 @@ const { readNotificationSettings, writeNotificationSettings } = require('./servi
const notificationService = require('./services/notificationService'); const notificationService = require('./services/notificationService');
const { readStoreWatch, writeStoreWatch, listWatcherProfiles } = require('./services/storeWatchStore'); const { readStoreWatch, writeStoreWatch, listWatcherProfiles } = require('./services/storeWatchStore');
const { readPreferences, writePreferences, sanitizeLocation } = require('./services/userPreferencesStore'); 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 ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
const adminEmail = (process.env.ADMIN_EMAIL || '').toLowerCase(); const adminEmail = (process.env.ADMIN_EMAIL || '').toLowerCase();
@@ -26,7 +30,6 @@ const cachedStoreSnapshots = new Map();
const regionStoreCache = new Map(); const regionStoreCache = new Map();
const REGION_STORE_CACHE_MS = 15 * 60 * 1000; const REGION_STORE_CACHE_MS = 15 * 60 * 1000;
const STORE_STATUS_MAX_AGE_MS = 60 * 24 * 60 * 60 * 1000; const STORE_STATUS_MAX_AGE_MS = 60 * 24 * 60 * 60 * 1000;
const storeStatusCache = new Map();
const storeLocationIndex = new Map(); const storeLocationIndex = new Map();
let storeLocationIndexUpdatedAt = 0; let storeLocationIndexUpdatedAt = 0;
const STORE_LOCATION_INDEX_TTL_MS = 12 * 60 * 60 * 1000; const STORE_LOCATION_INDEX_TTL_MS = 12 * 60 * 60 * 1000;
@@ -56,33 +59,6 @@ function haversineDistanceKm(lat1, lon1, lat2, lon2) {
return R * c; 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(cors());
app.use(express.json({ limit: '1mb' })); app.use(express.json({ limit: '1mb' }));
app.use(express.static(path.join(__dirname, 'build'))); app.use(express.static(path.join(__dirname, 'build')));
@@ -170,7 +146,7 @@ function setCachedRegionStores(regionId, payload) {
} }
function getCachedStoreStatus(storeId) { function getCachedStoreStatus(storeId) {
return storeStatusCache.get(String(storeId)) || null; return getCachedStoreStatusEntry(storeId);
} }
function ingestStoreLocations(stores = []) { function ingestStoreLocations(stores = []) {
@@ -318,7 +294,7 @@ async function refreshStoreStatus(
const changes = []; const changes = [];
for (const id of storeIds) { for (const id of storeIds) {
const storeId = String(id); 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; const ageOk = entry && now - (entry.fetchedAt || 0) <= STORE_STATUS_MAX_AGE_MS;
if (!force && ageOk) { if (!force && ageOk) {
continue; continue;
@@ -328,7 +304,7 @@ async function refreshStoreStatus(
const status = Number(details?.teamSearchStatus); const status = Number(details?.teamSearchStatus);
const normalized = Number.isFinite(status) ? status : null; const normalized = Number.isFinite(status) ? status : null;
const previous = entry ? entry.teamSearchStatus : null; const previous = entry ? entry.teamSearchStatus : null;
storeStatusCache.set(storeId, { setCachedStoreStatusEntry(storeId, {
teamSearchStatus: normalized, teamSearchStatus: normalized,
fetchedAt: now fetchedAt: now
}); });

View File

@@ -137,10 +137,10 @@ async function sendStoreWatchSummaryNotification({ profileId, entries = [], trig
.join('\n'); .join('\n');
const prefix = const prefix =
triggeredBy === 'manual' triggeredBy === 'manual'
? 'Manuell angestoßene Store-Watch-Prüfung abgeschlossen:' ? 'Manuell angestoßene Store-Watch-Aktualisierung abgeschlossen:'
: 'Store-Watch-Prüfung abgeschlossen:'; : 'Store-Watch-Prüfung abgeschlossen:';
const title = 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}`; const message = `${prefix}\n${lines}`;
await notifyChannels(profileId, { await notifyChannels(profileId, {
title, title,

View File

@@ -5,6 +5,7 @@ const { DEFAULT_SETTINGS } = require('./adminConfig');
const notificationService = require('./notificationService'); const notificationService = require('./notificationService');
const { readConfig, writeConfig } = require('./configStore'); const { readConfig, writeConfig } = require('./configStore');
const { readStoreWatch, writeStoreWatch } = require('./storeWatchStore'); const { readStoreWatch, writeStoreWatch } = require('./storeWatchStore');
const { setStoreStatus, persistStoreStatusCache } = require('./storeStatusCache');
function wait(ms) { function wait(ms) {
if (!ms || ms <= 0) { 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); const perRequestDelay = Math.max(0, Number(settings?.storeWatchRequestDelayMs) || 0);
let changed = false; let changed = false;
let statusCacheUpdated = false;
const summary = []; const summary = [];
for (let index = 0; index < watchers.length; index += 1) { for (let index = 0; index < watchers.length; index += 1) {
const watcher = watchers[index]; const watcher = watchers[index];
@@ -449,6 +451,8 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
} }
watcher.lastStatusCheckAt = checkedAt; watcher.lastStatusCheckAt = checkedAt;
changed = true; changed = true;
setStoreStatus(watcher.storeId, { teamSearchStatus: status, fetchedAt: checkedAt });
statusCacheUpdated = true;
summary.push({ summary.push({
storeId: watcher.storeId, storeId: watcher.storeId,
storeName: watcher.storeName, storeName: watcher.storeName,
@@ -477,6 +481,9 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
if (changed) { if (changed) {
writeStoreWatch(session.profile.id, watchers); writeStoreWatch(session.profile.id, watchers);
} }
if (statusCacheUpdated) {
persistStoreStatusCache();
}
if (options.sendSummary && summary.length > 0) { if (options.sendSummary && summary.length > 0) {
try { try {
await notificationService.sendStoreWatchSummaryNotification({ await notificationService.sendStoreWatchSummaryNotification({

View File

@@ -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
};

View File

@@ -986,11 +986,11 @@ const StoreWatchPage = ({
checkedAt: Date.now(), checkedAt: Date.now(),
stores: summary stores: summary
}); });
setStatus('Ad-hoc-Prüfung abgeschlossen. Zusammenfassung versendet.'); setStatus('Aktualisierung abgeschlossen. Zusammenfassung versendet.');
setTimeout(() => setStatus(''), 4000); setTimeout(() => setStatus(''), 4000);
await loadSubscriptions(); await loadSubscriptions();
} catch (err) { } catch (err) {
setError(`Ad-hoc-Prüfung fehlgeschlagen: ${err.message}`); setError(`Aktualisierung fehlgeschlagen: ${err.message}`);
} finally { } finally {
setAdhocChecking(false); setAdhocChecking(false);
} }
@@ -1019,7 +1019,7 @@ const StoreWatchPage = ({
onClick={handleAdhocWatchCheck} onClick={handleAdhocWatchCheck}
disabled={adhocChecking || subscriptionsLoading || watchList.length === 0} disabled={adhocChecking || subscriptionsLoading || watchList.length === 0}
> >
{adhocChecking ? 'Prüfe...' : 'Ad-hoc prüfen'} {adhocChecking ? 'Aktualisiere...' : 'Aktualisieren'}
</button> </button>
<button <button
type="button" type="button"
@@ -1133,7 +1133,7 @@ const StoreWatchPage = ({
{lastAdhocCheck?.stores?.length > 0 && ( {lastAdhocCheck?.stores?.length > 0 && (
<div className="mt-4 rounded-lg border border-blue-100 bg-blue-50 p-3 text-sm text-blue-900"> <div className="mt-4 rounded-lg border border-blue-100 bg-blue-50 p-3 text-sm text-blue-900">
<p className="text-xs font-semibold text-blue-800"> <p className="text-xs font-semibold text-blue-800">
Letzte Ad-hoc-Prüfung:{' '} Letzte Aktualisierung:{' '}
{new Date(lastAdhocCheck.checkedAt).toLocaleString('de-DE')} {new Date(lastAdhocCheck.checkedAt).toLocaleString('de-DE')}
</p> </p>
<ul className="mt-2 space-y-1 text-xs"> <ul className="mt-2 space-y-1 text-xs">