aktueller stand
This commit is contained in:
97
server.js
97
server.js
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user