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

@@ -7,6 +7,7 @@ const { readConfig, writeConfig } = require('./configStore');
const { readStoreWatch, writeStoreWatch } = require('./storeWatchStore');
const { setStoreStatus, persistStoreStatusCache } = require('./storeStatusCache');
const { sendDormantPickupWarning } = require('./notificationService');
const { ensureSession, withSessionRetry } = require('./sessionRefresh');
function wait(ms) {
if (!ms || ms <= 0) {
@@ -122,43 +123,6 @@ function persistEntryDeactivation(profileId, entryId, options = {}) {
}
}
async function ensureSession(session) {
const profileId = session.profile?.id;
if (!profileId) {
return false;
}
const stillValid = await foodsharingClient.checkSession(session.cookieHeader, profileId);
if (stillValid) {
return true;
}
if (!session.credentials) {
console.warn(`Session ${session.id} kann nicht erneuert werden keine Zugangsdaten gespeichert.`);
return false;
}
try {
const refreshed = await foodsharingClient.login(
session.credentials.email,
session.credentials.password
);
sessionStore.update(session.id, {
cookieHeader: refreshed.cookieHeader,
csrfToken: refreshed.csrfToken,
profile: {
...session.profile,
...refreshed.profile
}
});
console.log(`Session ${session.id} wurde erfolgreich erneuert.`);
return true;
} catch (error) {
console.error(`Session ${session.id} konnte nicht erneuert werden:`, error.message);
return false;
}
}
function toDateValue(input) {
if (!input) {
return null;
@@ -326,12 +290,20 @@ async function processBooking(session, entry, pickup) {
const utcDate = new Date(pickup.date).toISOString();
try {
const allowed = await foodsharingClient.pickupRuleCheck(entry.id, utcDate, session.profile.id, session);
const allowed = await withSessionRetry(
session,
() => foodsharingClient.pickupRuleCheck(entry.id, utcDate, session.profile.id, session),
{ label: 'pickupRuleCheck' }
);
if (!allowed) {
console.warn(`[WARN] Rule-Check fehlgeschlagen für ${storeName} am ${readableDate}`);
return;
}
await foodsharingClient.bookSlot(entry.id, utcDate, session.profile.id, session);
await withSessionRetry(
session,
() => foodsharingClient.bookSlot(entry.id, utcDate, session.profile.id, session),
{ label: 'bookSlot' }
);
console.log(`[SUCCESS] Slot gebucht für ${storeName} am ${readableDate}`);
await notificationService.sendSlotNotification({
profileId: session.profile.id,
@@ -345,6 +317,21 @@ async function processBooking(session, entry, pickup) {
persistEntryDeactivation(session.profile.id, entry.id);
} catch (error) {
console.error(`[ERROR] Buchung für ${storeName} am ${readableDate} fehlgeschlagen:`, error.message);
try {
await notificationService.sendAdminBookingErrorNotification({
profileId: session.profile.id,
profileEmail: session.profile.email,
storeName,
storeId: entry.id,
pickupDate: pickup.date,
error: error.message
});
} catch (notifyError) {
console.error(
`[NOTIFY] Admin-Benachrichtigung für fehlgeschlagene Buchung bei ${storeName} fehlgeschlagen:`,
notifyError.message
);
}
}
}
@@ -368,7 +355,11 @@ async function checkEntry(sessionId, entry, settings) {
}
try {
const pickups = await foodsharingClient.fetchPickups(entry.id, session.cookieHeader);
const pickups = await withSessionRetry(
session,
() => foodsharingClient.fetchPickups(entry.id, session.cookieHeader),
{ label: 'fetchPickups' }
);
let hasProfileId = false;
let availablePickup = null;
@@ -435,7 +426,11 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
for (let index = 0; index < watchers.length; index += 1) {
const watcher = watchers[index];
try {
const details = await foodsharingClient.fetchStoreDetails(watcher.storeId, session.cookieHeader);
const details = await withSessionRetry(
session,
() => foodsharingClient.fetchStoreDetails(watcher.storeId, session.cookieHeader),
{ label: 'fetchStoreDetails' }
);
const status = details?.teamSearchStatus === 1 ? 1 : 0;
const checkedAt = Date.now();
if (status === 1 && watcher.lastTeamSearchStatus !== 1) {
@@ -589,6 +584,15 @@ function setMonthOffset(date, offset) {
return copy;
}
function getMissingLastPickupStoreIds(config = []) {
if (!Array.isArray(config)) {
return [];
}
return config
.filter((entry) => entry && entry.id && !entry.hidden && !entry.skipDormantCheck && !entry.lastPickupAt)
.map((entry) => String(entry.id));
}
async function checkDormantMembers(sessionId) {
const session = sessionStore.get(sessionId);
if (!session?.profile?.id) {
@@ -601,11 +605,15 @@ async function checkDormantMembers(sessionId) {
}
const config = readConfig(profileId);
const skipMap = new Map();
config.forEach((entry) => {
const configEntryMap = new Map();
config.forEach((entry, index) => {
if (entry?.id) {
skipMap.set(String(entry.id), !!entry.skipDormantCheck);
const id = String(entry.id);
skipMap.set(id, !!entry.skipDormantCheck);
configEntryMap.set(id, { entry, index });
}
});
let configChanged = false;
const stores = Array.isArray(session.storesCache?.data) ? session.storesCache.data : [];
if (stores.length === 0) {
@@ -624,7 +632,11 @@ async function checkDormantMembers(sessionId) {
}
let members = [];
try {
members = await foodsharingClient.fetchStoreMembers(storeId, session.cookieHeader);
members = await withSessionRetry(
session,
() => foodsharingClient.fetchStoreMembers(storeId, session.cookieHeader),
{ label: 'fetchStoreMembers' }
);
} catch (error) {
console.warn(`[DORMANT] Mitglieder von Store ${storeId} konnten nicht geladen werden:`, error.message);
continue;
@@ -635,6 +647,14 @@ async function checkDormantMembers(sessionId) {
}
const reasons = [];
const lastFetchMs = memberEntry.last_fetch ? Number(memberEntry.last_fetch) * 1000 : null;
if (Number.isFinite(lastFetchMs)) {
const configEntry = configEntryMap.get(storeId)?.entry;
const lastPickupAt = new Date(lastFetchMs).toISOString();
if (configEntry && configEntry.lastPickupAt !== lastPickupAt) {
configEntry.lastPickupAt = lastPickupAt;
configChanged = true;
}
}
if (!lastFetchMs || lastFetchMs < fourMonthsAgo) {
const lastFetchLabel = lastFetchMs ? new Date(lastFetchMs).toLocaleDateString('de-DE') : 'unbekannt';
reasons.push(`Letzte Abholung: ${lastFetchLabel} (älter als 4 Monate)`);
@@ -660,6 +680,13 @@ async function checkDormantMembers(sessionId) {
}
}
}
if (configChanged) {
try {
writeConfig(profileId, config);
} catch (error) {
console.error(`[DORMANT] Letzte Abholung für Profil ${profileId} konnte nicht gespeichert werden:`, error.message);
}
}
}
function scheduleDormantMembershipCheck(sessionId) {
@@ -674,6 +701,24 @@ function scheduleDormantMembershipCheck(sessionId) {
{ timezone: 'Europe/Berlin' }
);
sessionStore.attachJob(sessionId, job);
const session = sessionStore.get(sessionId);
const profileId = session?.profile?.id;
if (!profileId) {
return;
}
const config = readConfig(profileId);
const missingIds = getMissingLastPickupStoreIds(config);
if (missingIds.length === 0) {
if (session.dormantBootstrapSignature) {
sessionStore.update(sessionId, { dormantBootstrapSignature: null });
}
return;
}
const signature = missingIds.sort().join(',');
if (session.dormantBootstrapSignature === signature) {
return;
}
sessionStore.update(sessionId, { dormantBootstrapSignature: signature });
setTimeout(() => checkDormantMembers(sessionId), randomDelayMs(30, 180));
}