button zum prüfen
This commit is contained in:
40
server.js
40
server.js
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
66
services/storeStatusCache.js
Normal file
66
services/storeStatusCache.js
Normal 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
|
||||||
|
};
|
||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user