aktueller stand

This commit is contained in:
2025-12-29 19:51:45 +01:00
parent f4323e20de
commit 41ef5107aa
9 changed files with 438 additions and 98 deletions

View File

@@ -14,6 +14,7 @@ const notificationService = require('./services/notificationService');
const { readStoreWatch, writeStoreWatch, listWatcherProfiles } = require('./services/storeWatchStore');
const { readPreferences, writePreferences, sanitizeLocation } = require('./services/userPreferencesStore');
const requestLogStore = require('./services/requestLogStore');
const { withSessionRetry } = require('./services/sessionRefresh');
const {
getStoreStatus: getCachedStoreStatusEntry,
setStoreStatus: setCachedStoreStatusEntry,
@@ -23,6 +24,7 @@ const {
const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
const adminEmail = (process.env.ADMIN_EMAIL || '').toLowerCase();
const SIXTY_DAYS_MS = 60 * 24 * 60 * 60 * 1000;
const PROFILE_DETAILS_TTL_MS = 6 * 60 * 60 * 1000;
const app = express();
const port = process.env.PORT || 3000;
@@ -106,6 +108,33 @@ function isAdmin(profile) {
return profile.email.toLowerCase() === adminEmail;
}
async function fetchProfileWithCache(session, { force = false } = {}) {
if (!session?.id) {
return null;
}
const cached = session.profileDetailsCache;
const isFresh = cached?.fetchedAt && Date.now() - cached.fetchedAt <= PROFILE_DETAILS_TTL_MS;
if (!force && isFresh && cached?.data) {
return cached.data;
}
try {
const details = await withSessionRetry(
session,
() => foodsharingClient.fetchProfile(session.cookieHeader, { throwOnError: true }),
{ label: 'fetchProfile' }
);
sessionStore.update(session.id, {
profileDetailsCache: { data: details, fetchedAt: Date.now() }
});
return details;
} catch (error) {
if (cached?.data) {
return cached.data;
}
throw error;
}
}
function scheduleWithCurrentSettings(sessionId, config) {
const settings = adminConfig.readSettings();
scheduleConfig(sessionId, config, settings);
@@ -222,7 +251,7 @@ async function ensureStoreLocationIndex(session, { force = false } = {}) {
if (!force && storeLocationIndex.size > 0 && fresh) {
return;
}
const details = await foodsharingClient.fetchProfile(session.cookieHeader);
const details = await fetchProfileWithCache(session);
const regions = Array.isArray(details?.regions)
? details.regions.filter((region) => Number(region?.classification) === 1)
: [];
@@ -232,7 +261,11 @@ async function ensureStoreLocationIndex(session, { force = false } = {}) {
for (const region of regions) {
let payload = getCachedRegionStores(region.id);
if (!payload) {
const result = await foodsharingClient.fetchRegionStores(region.id, session.cookieHeader);
const result = await withSessionRetry(
session,
() => foodsharingClient.fetchRegionStores(region.id, session.cookieHeader),
{ label: 'fetchRegionStores' }
);
payload = {
total: Number(result?.total) || 0,
stores: Array.isArray(result?.stores) ? result.stores : []
@@ -319,10 +352,10 @@ async function notifyWatchersForStatusChanges(changes = [], storeInfoMap = new M
async function refreshStoreStatus(
storeIds = [],
cookieHeader,
session,
{ force = false, storeInfoMap = new Map() } = {}
) {
if (!Array.isArray(storeIds) || storeIds.length === 0 || !cookieHeader) {
if (!Array.isArray(storeIds) || storeIds.length === 0 || !session?.cookieHeader) {
return { refreshed: 0, changes: [] };
}
const now = Date.now();
@@ -336,7 +369,11 @@ async function refreshStoreStatus(
continue;
}
try {
const details = await foodsharingClient.fetchStoreDetails(storeId, cookieHeader);
const details = await withSessionRetry(
session,
() => foodsharingClient.fetchStoreDetails(storeId, session.cookieHeader),
{ label: 'fetchStoreDetails' }
);
const status = Number(details?.teamSearchStatus);
const normalized = Number.isFinite(status) ? status : null;
const previous = entry ? entry.teamSearchStatus : null;
@@ -366,7 +403,7 @@ async function refreshStoreStatus(
return { refreshed, changes };
}
async function enrichStoresWithTeamStatus(stores = [], cookieHeader, { forceRefresh = false } = {}) {
async function enrichStoresWithTeamStatus(stores = [], session, { forceRefresh = false } = {}) {
if (!Array.isArray(stores) || stores.length === 0) {
return { stores, statusMeta: { total: 0, refreshed: 0, fromCache: 0, missing: 0 } };
}
@@ -392,7 +429,7 @@ async function enrichStoresWithTeamStatus(stores = [], cookieHeader, { forceRefr
let refreshed = 0;
let changes = [];
if (staleIds.length > 0) {
const result = await refreshStoreStatus(staleIds, cookieHeader, {
const result = await refreshStoreStatus(staleIds, session, {
force: forceRefresh,
storeInfoMap
});
@@ -502,14 +539,19 @@ async function runStoreRefreshJob(session, job) {
job.status = 'running';
job.startedAt = Date.now();
const settings = adminConfig.readSettings();
const stores = await foodsharingClient.fetchStores(session.cookieHeader, session.profile.id, {
delayBetweenRequestsMs: settings.storePickupCheckDelayMs,
onStoreCheck: (store, processed, total) => {
job.processed = processed;
job.total = total;
job.currentStore = store.name || `Store ${store.id}`;
}
});
const stores = await withSessionRetry(
session,
() =>
foodsharingClient.fetchStores(session.cookieHeader, session.profile.id, {
delayBetweenRequestsMs: settings.storePickupCheckDelayMs,
onStoreCheck: (store, processed, total) => {
job.processed = processed;
job.total = total;
job.currentStore = store.name || `Store ${store.id}`;
}
}),
{ label: 'fetchStores' }
);
job.processed = stores.length;
job.total = stores.length;
job.currentStore = null;
@@ -726,10 +768,15 @@ app.get('/api/auth/session', requireAuth, async (req, res) => {
});
app.get('/api/profile', requireAuth, async (req, res) => {
const details = await foodsharingClient.fetchProfile(req.session.cookieHeader);
res.json({
profile: details || req.session.profile
});
try {
const details = await fetchProfileWithCache(req.session);
res.json({
profile: details || req.session.profile
});
} catch (error) {
console.error('[PROFILE] Profil konnte nicht geladen werden:', error.message);
res.status(500).json({ error: 'Profil konnte nicht geladen werden' });
}
});
app.get('/api/location/nearest-store', requireAuth, async (req, res) => {
@@ -750,7 +797,7 @@ app.get('/api/location/nearest-store', requireAuth, async (req, res) => {
app.get('/api/store-watch/regions', requireAuth, async (req, res) => {
try {
const details = await foodsharingClient.fetchProfile(req.session.cookieHeader);
const details = await fetchProfileWithCache(req.session);
const regions = Array.isArray(details?.regions)
? details.regions.filter((region) => Number(region?.classification) === 1)
: [];
@@ -779,7 +826,11 @@ app.get('/api/store-watch/regions/:regionId/stores', requireAuth, async (req, re
if (!basePayload) {
try {
const result = await foodsharingClient.fetchRegionStores(regionId, req.session.cookieHeader);
const result = await withSessionRetry(
req.session,
() => foodsharingClient.fetchRegionStores(regionId, req.session.cookieHeader),
{ label: 'fetchRegionStores' }
);
basePayload = {
total: Number(result?.total) || 0,
stores: Array.isArray(result?.stores) ? result.stores : []
@@ -799,7 +850,7 @@ app.get('/api/store-watch/regions/:regionId/stores', requireAuth, async (req, re
const { stores: enrichedStores, statusMeta } = await enrichStoresWithTeamStatus(
filteredStores,
req.session.cookieHeader,
req.session,
{ forceRefresh: forceStatusRefresh }
);
@@ -863,7 +914,7 @@ app.get('/api/user/preferences', requireAuth, async (req, res) => {
const preferences = readPreferences(req.session.profile.id);
let location = preferences.location;
try {
const details = await foodsharingClient.fetchProfile(req.session.cookieHeader);
const details = await fetchProfileWithCache(req.session);
const coords = details?.coordinates;
const sanitized = sanitizeLocation({ lat: coords?.lat, lon: coords?.lon });
if (sanitized) {