chore: sync running docker state
Commit the app code currently running in Docker, including scheduler, store status, journal reminder, maintenance mode, and Foodsharing API check updates. Remove generated runtime logs from version control and ignore local runtime captures.
This commit is contained in:
@@ -3,6 +3,7 @@ const http = require('http');
|
||||
const https = require('https');
|
||||
const requestLogStore = require('./requestLogStore');
|
||||
const sessionStore = require('./sessionStore');
|
||||
const maintenanceMode = require('./maintenanceMode');
|
||||
|
||||
const BASE_URL = 'https://foodsharing.de';
|
||||
|
||||
@@ -30,21 +31,23 @@ client.interceptors.response.use(
|
||||
(response) => {
|
||||
const startedAt = response?.config?.metadata?.startedAt || Date.now();
|
||||
const metadata = response?.config?.metadata || {};
|
||||
try {
|
||||
requestLogStore.add({
|
||||
direction: 'outgoing',
|
||||
target: 'foodsharing.de',
|
||||
method: (response.config?.method || 'GET').toUpperCase(),
|
||||
path: response.config?.url || '',
|
||||
status: response.status,
|
||||
durationMs: Date.now() - startedAt,
|
||||
sessionId: metadata.sessionId ?? null,
|
||||
profileId: metadata.profileId ?? null,
|
||||
profileName: metadata.profileName ?? null,
|
||||
responseBody: response.data
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('[REQUEST-LOG] Outgoing-Log fehlgeschlagen:', error.message);
|
||||
if (!metadata.skipRequestLog) {
|
||||
try {
|
||||
requestLogStore.add({
|
||||
direction: 'outgoing',
|
||||
target: 'foodsharing.de',
|
||||
method: (response.config?.method || 'GET').toUpperCase(),
|
||||
path: response.config?.url || '',
|
||||
status: response.status,
|
||||
durationMs: Date.now() - startedAt,
|
||||
sessionId: metadata.sessionId ?? null,
|
||||
profileId: metadata.profileId ?? null,
|
||||
profileName: metadata.profileName ?? null,
|
||||
responseBody: response.data
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('[REQUEST-LOG] Outgoing-Log fehlgeschlagen:', error.message);
|
||||
}
|
||||
}
|
||||
updateSessionCookiesFromResponse(response);
|
||||
return response;
|
||||
@@ -52,22 +55,36 @@ client.interceptors.response.use(
|
||||
(error) => {
|
||||
const startedAt = error?.config?.metadata?.startedAt || Date.now();
|
||||
const metadata = error?.config?.metadata || {};
|
||||
try {
|
||||
requestLogStore.add({
|
||||
direction: 'outgoing',
|
||||
target: 'foodsharing.de',
|
||||
method: (error.config?.method || 'GET').toUpperCase(),
|
||||
path: error.config?.url || '',
|
||||
status: error?.response?.status || null,
|
||||
durationMs: Date.now() - startedAt,
|
||||
sessionId: metadata.sessionId ?? null,
|
||||
profileId: metadata.profileId ?? null,
|
||||
profileName: metadata.profileName ?? null,
|
||||
error: error?.message || 'Unbekannter Fehler',
|
||||
responseBody: error?.response?.data
|
||||
});
|
||||
} catch (logError) {
|
||||
console.warn('[REQUEST-LOG] Outgoing-Error-Log fehlgeschlagen:', logError.message);
|
||||
let logEntry = null;
|
||||
if (!metadata.skipRequestLog) {
|
||||
try {
|
||||
logEntry = requestLogStore.add({
|
||||
direction: 'outgoing',
|
||||
target: 'foodsharing.de',
|
||||
method: (error.config?.method || 'GET').toUpperCase(),
|
||||
path: error.config?.url || '',
|
||||
status: error?.response?.status || null,
|
||||
durationMs: Date.now() - startedAt,
|
||||
sessionId: metadata.sessionId ?? null,
|
||||
profileId: metadata.profileId ?? null,
|
||||
profileName: metadata.profileName ?? null,
|
||||
error: error?.message || 'Unbekannter Fehler',
|
||||
responseBody: error?.response?.data
|
||||
});
|
||||
} catch (logError) {
|
||||
console.warn('[REQUEST-LOG] Outgoing-Error-Log fehlgeschlagen:', logError.message);
|
||||
}
|
||||
try {
|
||||
maintenanceMode.recordOutboundFailure(logEntry || {
|
||||
direction: 'outgoing',
|
||||
target: 'foodsharing.de',
|
||||
status: error?.response?.status || null,
|
||||
error: error?.message || 'Unbekannter Fehler',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
} catch (maintenanceError) {
|
||||
console.warn('[MAINTENANCE] Fehlerauswertung fehlgeschlagen:', maintenanceError.message);
|
||||
}
|
||||
}
|
||||
if (error?.response) {
|
||||
updateSessionCookiesFromResponse(error.response);
|
||||
@@ -235,7 +252,7 @@ function updateSessionCookiesFromResponse(response) {
|
||||
|
||||
async function getCurrentUserDetails(cookieHeader, context, options = {}) {
|
||||
const response = await client.get(
|
||||
'/api/user/current/details',
|
||||
'/api/users/current/details',
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
return options.raw ? response : response.data;
|
||||
@@ -245,22 +262,23 @@ async function login(email, password) {
|
||||
const payload = {
|
||||
email,
|
||||
password,
|
||||
remember_me: true
|
||||
code: '',
|
||||
rememberMe: true
|
||||
};
|
||||
|
||||
const headers = {
|
||||
'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"',
|
||||
Origin: BASE_URL,
|
||||
Referer: BASE_URL,
|
||||
DNT: '1',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Linux"',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0',
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
};
|
||||
|
||||
const response = await client.post('/api/user/login', payload, { headers });
|
||||
const response = await client.post('/api/login', payload, { headers });
|
||||
const cookies = response.headers['set-cookie'] || [];
|
||||
const csrfToken = extractCsrfToken(cookies);
|
||||
let cookieHeader = serializeCookies(cookies);
|
||||
let cookieHeader = mergeCookieHeaders('', cookies);
|
||||
const detailsResponse = await getCurrentUserDetails(cookieHeader, null, { raw: true });
|
||||
const detailsCookies = detailsResponse?.headers?.['set-cookie'] || [];
|
||||
if (detailsCookies.length > 0) {
|
||||
@@ -307,6 +325,20 @@ function wait(ms = 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function hasRegularStoreAccess(store) {
|
||||
if (!store) {
|
||||
return false;
|
||||
}
|
||||
if (store.isManaging) {
|
||||
return true;
|
||||
}
|
||||
const membershipStatus = Number(store.membershipStatus);
|
||||
if (!Number.isFinite(membershipStatus)) {
|
||||
return true;
|
||||
}
|
||||
return membershipStatus === 1;
|
||||
}
|
||||
|
||||
async function fetchStores(cookieHeader, profileId, options = {}, context) {
|
||||
if (!profileId) {
|
||||
return [];
|
||||
@@ -320,8 +352,8 @@ async function fetchStores(cookieHeader, profileId, options = {}, context) {
|
||||
: null;
|
||||
try {
|
||||
const response = await client.get(
|
||||
`/api/user/${profileId}/stores`,
|
||||
buildRequestConfig({ cookieHeader, params: { activeStores: 1 }, context })
|
||||
`/api/users/${profileId}/stores`,
|
||||
buildRequestConfig({ cookieHeader, params: { excludeInactive: true }, context })
|
||||
);
|
||||
const stores = Array.isArray(response.data) ? response.data : [];
|
||||
const normalized = stores.map((store) => ({
|
||||
@@ -332,7 +364,7 @@ async function fetchStores(cookieHeader, profileId, options = {}, context) {
|
||||
isManaging: !!store.isManaging,
|
||||
city: store.city || '',
|
||||
street: store.street || '',
|
||||
zip: store.zip || ''
|
||||
zip: store.zipCode || store.zip || ''
|
||||
}));
|
||||
|
||||
return annotateStoresWithPickupSlots(
|
||||
@@ -375,6 +407,14 @@ async function annotateStoresWithPickupSlots(
|
||||
await wait(delayMs);
|
||||
}
|
||||
let hasPickupSlots = null;
|
||||
if (!hasRegularStoreAccess(store)) {
|
||||
hasPickupSlots = false;
|
||||
console.info(
|
||||
`Pickups für Store ${store.id} werden wegen Mitgliedschaftsstatus ${store.membershipStatus} nicht abgefragt.`
|
||||
);
|
||||
annotated.push({ ...store, hasPickupSlots });
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const pickups = await fetchPickups(store.id, cookieHeader, context);
|
||||
hasPickupSlots = Array.isArray(pickups) && pickups.length > 0;
|
||||
@@ -398,6 +438,9 @@ async function fetchPickups(storeId, cookieHeader, context) {
|
||||
`/api/stores/${storeId}/pickups`,
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
if (Array.isArray(response.data)) {
|
||||
return response.data;
|
||||
}
|
||||
return response.data?.pickups || [];
|
||||
}
|
||||
|
||||
@@ -406,21 +449,22 @@ async function fetchRegionStores(regionId, cookieHeader, context) {
|
||||
return { total: 0, stores: [] };
|
||||
}
|
||||
const response = await client.get(
|
||||
`/api/region/${regionId}/stores`,
|
||||
`/api/regions/${regionId}/stores`,
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
const stores = Array.isArray(response.data) ? response.data : Array.isArray(response.data?.stores) ? response.data.stores : [];
|
||||
return {
|
||||
total: Number(response.data?.total) || 0,
|
||||
stores: Array.isArray(response.data?.stores) ? response.data.stores : []
|
||||
total: Number(response.data?.total) || stores.length,
|
||||
stores
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchStoreDetails(storeId, cookieHeader, context) {
|
||||
async function fetchStoreMarker(storeId, cookieHeader, context) {
|
||||
if (!storeId) {
|
||||
return null;
|
||||
}
|
||||
const response = await client.get(
|
||||
`/api/map/stores/${storeId}`,
|
||||
`/api/map/markers/stores/${storeId}`,
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
return response.data || null;
|
||||
@@ -428,14 +472,14 @@ async function fetchStoreDetails(storeId, cookieHeader, context) {
|
||||
|
||||
async function pickupRuleCheck(storeId, utcDate, profileId, session) {
|
||||
const response = await client.get(
|
||||
`/api/stores/${storeId}/pickupRuleCheck/${utcDate}/${profileId}`,
|
||||
`/api/stores/${storeId}/pickups/${utcDate}/eligibility`,
|
||||
buildRequestConfig({
|
||||
cookieHeader: session.cookieHeader,
|
||||
csrfToken: session.csrfToken,
|
||||
context: session
|
||||
})
|
||||
);
|
||||
return response.data?.result === true;
|
||||
return response.data?.isEligible === true || response.data?.result === true;
|
||||
}
|
||||
|
||||
async function fetchStoreMembers(storeId, cookieHeader, context) {
|
||||
@@ -443,7 +487,7 @@ async function fetchStoreMembers(storeId, cookieHeader, context) {
|
||||
return [];
|
||||
}
|
||||
const response = await client.get(
|
||||
`/api/stores/${storeId}/member`,
|
||||
`/api/stores/${storeId}/members`,
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
return Array.isArray(response.data) ? response.data : [];
|
||||
@@ -454,15 +498,41 @@ async function fetchRegularPickup(storeId, cookieHeader, context) {
|
||||
return [];
|
||||
}
|
||||
const response = await client.get(
|
||||
`/api/stores/${storeId}/regularPickup`,
|
||||
`/api/stores/${storeId}/regular-pickups`,
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
return Array.isArray(response.data) ? response.data : [];
|
||||
}
|
||||
|
||||
async function fetchRegisteredPickups(userId, cookieHeader, context) {
|
||||
const normalizedUserId = userId ? String(userId) : 'current';
|
||||
const response = await client.get(
|
||||
`/api/users/${normalizedUserId}/pickups/registered`,
|
||||
buildRequestConfig({ cookieHeader, context })
|
||||
);
|
||||
return Array.isArray(response.data) ? response.data : [];
|
||||
}
|
||||
|
||||
async function fetchConversations(cookieHeader, options = {}, context) {
|
||||
const limit = Math.max(1, Math.min(Number(options.limit) || 20, 100));
|
||||
const offset = Math.max(0, Number(options.offset) || 0);
|
||||
const config = buildRequestConfig({ cookieHeader, params: { limit, offset }, context });
|
||||
config.metadata = { ...(config.metadata || {}), skipRequestLog: true };
|
||||
const response = await client.get('/api/conversations', config);
|
||||
const conversations = Array.isArray(response.data?.conversations)
|
||||
? response.data.conversations
|
||||
: Array.isArray(response.data)
|
||||
? response.data
|
||||
: [];
|
||||
return {
|
||||
conversations,
|
||||
profiles: Array.isArray(response.data?.profiles) ? response.data.profiles : []
|
||||
};
|
||||
}
|
||||
|
||||
async function bookSlot(storeId, utcDate, profileId, session) {
|
||||
await client.post(
|
||||
`/api/stores/${storeId}/pickups/${utcDate}/${profileId}`,
|
||||
`/api/stores/${storeId}/pickups/${utcDate}/users/current`,
|
||||
{},
|
||||
{
|
||||
...buildRequestConfig({
|
||||
@@ -480,9 +550,11 @@ module.exports = {
|
||||
fetchStores,
|
||||
fetchPickups,
|
||||
fetchRegionStores,
|
||||
fetchStoreDetails,
|
||||
fetchStoreMarker,
|
||||
fetchStoreMembers,
|
||||
fetchRegularPickup,
|
||||
fetchRegisteredPickups,
|
||||
fetchConversations,
|
||||
pickupRuleCheck,
|
||||
bookSlot
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user