diff --git a/config/request-logs.json b/config/request-logs.json
new file mode 100644
index 0000000..fa35fa7
--- /dev/null
+++ b/config/request-logs.json
@@ -0,0 +1,702 @@
+[
+ {
+ "id": "b424a895-fb22-4607-b5c9-30ef53d1723e",
+ "timestamp": 1769708958043,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "POST",
+ "path": "/api/user/login",
+ "status": 200,
+ "durationMs": 730,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"name\":\"Meik\",\"avatar\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"isSleeping\":false}"
+ },
+ {
+ "id": "347b319a-ee3e-457f-9d32-db0d13cedaed",
+ "timestamp": 1769708958184,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/user/current/details",
+ "status": 200,
+ "durationMs": 139,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"foodsaver\":true,\"isVerified\":true,\"regionId\":588,\"isSleeping\":0,\"regionName\":\"Rastatt/Baden-Baden\",\"aboutMePublic\":\"\",\"mailboxId\":null,\"hasCalendarToken\":true,\"firstname\":\"Meik\",\"lastname\":\"Drechsler\",\"gender\":1,\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"sleeping\":true,\"lastPassDate\":\"2024-04-30 09:36:50\",\"lastPassUntilValid\":\"2027-04-30T09:36:50+02:00\",\"lastPassUntilValidInDays\":456,\"stats\":{\"weight\":2369.5,\"count\":146},\"permissions\":{\"mayEditUserProfile\":true,\"mayAdministrateUserProfile\":false,\"administrateBlog\":false,\"editQuiz\":false,\"handleReports\":false,\"addStore\":false,\"editContent\":false,\"administrateNewsletterEmail\":false,\"administrateRegions\":false,\"maySearchGlobal\":false},\"hasActiveEmail\":true,\"coordinates\":{\"lat\":48.80617805,\"lon\":8.222051246654733},\"address\":\"Rathausplatz 1\",\"city\":\"Baden-Baden\",\"postcode\":\"76532\",\"email\":\"meikdre@gmx.de\",\"landline\":\"\",\"mobile\":\"+49 176 27186806\",\"birthday\":\"1985-07-27\",\"aboutMeIntern\":\"\",\"role\":1,\"regions\":[{\"id\":880,\"name\":\"Bühl (Baden)\",\"classification\":1,\"isResponsible\":false},{\"id\":2646,\"name\":\"Ettlingen & südl. Landkreis Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":1432,\"name\":\"foodsharing auf Festivals\",\"classification\":1,\"isResponsible\":false},{\"id\":433,\"name\":\"Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":248,\"name\":\"Ortenaukreis\",\"classification\":1,\"isResponsible\":false},{\"id\":588,\"name\":\"Rastatt/Baden-Baden\",\"classification\":1,\"isResponsible\":false},{\"id\":52,\"name\":\"Baden-Württemberg\",\"classification\":5,\"isResponsible\":false},{\"id\":1,\"name\":\"Deutschland\",\"classification\":6,\"isResponsible\":false},{\"id\":741,\"name\":\"Europa\",\"classification\":6,\"isResponsible\":false}],\"groups\":[]}"
+ },
+ {
+ "id": "471c8d61-66c6-4140-9697-65509b95507c",
+ "timestamp": 1769708958288,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 103,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "eb0e7b63-8190-4552-89b2-be285b6940f4",
+ "timestamp": 1769708958340,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 51,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "f3d19980-3197-424b-98d3-302929d53313",
+ "timestamp": 1769708958399,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 59,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "5962575a-22c3-4daf-8ba6-8b434fada09f",
+ "timestamp": 1769708958465,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 66,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "46e81578-d4e0-4ad9-826f-f33fbb6f23d6",
+ "timestamp": 1769708958524,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 59,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "586e3202-34f8-4cf4-b977-1a3691bf8fde",
+ "timestamp": 1769709007537,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "POST",
+ "path": "/api/user/login",
+ "status": 200,
+ "durationMs": 555,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"name\":\"Meik\",\"avatar\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"isSleeping\":false}"
+ },
+ {
+ "id": "5b891310-cf8d-471d-92d0-b6b4e06c25ef",
+ "timestamp": 1769709007590,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/user/current/details",
+ "status": 200,
+ "durationMs": 50,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"foodsaver\":true,\"isVerified\":true,\"regionId\":588,\"isSleeping\":0,\"regionName\":\"Rastatt/Baden-Baden\",\"aboutMePublic\":\"\",\"mailboxId\":null,\"hasCalendarToken\":true,\"firstname\":\"Meik\",\"lastname\":\"Drechsler\",\"gender\":1,\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"sleeping\":true,\"lastPassDate\":\"2024-04-30 09:36:50\",\"lastPassUntilValid\":\"2027-04-30T09:36:50+02:00\",\"lastPassUntilValidInDays\":456,\"stats\":{\"weight\":2369.5,\"count\":146},\"permissions\":{\"mayEditUserProfile\":true,\"mayAdministrateUserProfile\":false,\"administrateBlog\":false,\"editQuiz\":false,\"handleReports\":false,\"addStore\":false,\"editContent\":false,\"administrateNewsletterEmail\":false,\"administrateRegions\":false,\"maySearchGlobal\":false},\"hasActiveEmail\":true,\"coordinates\":{\"lat\":48.80617805,\"lon\":8.222051246654733},\"address\":\"Rathausplatz 1\",\"city\":\"Baden-Baden\",\"postcode\":\"76532\",\"email\":\"meikdre@gmx.de\",\"landline\":\"\",\"mobile\":\"+49 176 27186806\",\"birthday\":\"1985-07-27\",\"aboutMeIntern\":\"\",\"role\":1,\"regions\":[{\"id\":880,\"name\":\"Bühl (Baden)\",\"classification\":1,\"isResponsible\":false},{\"id\":2646,\"name\":\"Ettlingen & südl. Landkreis Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":1432,\"name\":\"foodsharing auf Festivals\",\"classification\":1,\"isResponsible\":false},{\"id\":433,\"name\":\"Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":248,\"name\":\"Ortenaukreis\",\"classification\":1,\"isResponsible\":false},{\"id\":588,\"name\":\"Rastatt/Baden-Baden\",\"classification\":1,\"isResponsible\":false},{\"id\":52,\"name\":\"Baden-Württemberg\",\"classification\":5,\"isResponsible\":false},{\"id\":1,\"name\":\"Deutschland\",\"classification\":6,\"isResponsible\":false},{\"id\":741,\"name\":\"Europa\",\"classification\":6,\"isResponsible\":false}],\"groups\":[]}"
+ },
+ {
+ "id": "55c6174e-29f6-4df2-b159-eb9fcd1e5d6d",
+ "timestamp": 1769709007677,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 81,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "aaa2e79c-96e6-4c42-b42f-6ea126fba856",
+ "timestamp": 1769709007718,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/28513/regularPickup",
+ "status": 200,
+ "durationMs": 121,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "06db08ed-e2f3-46ea-85eb-0d3a789d3d03",
+ "timestamp": 1769709007728,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/44975/regularPickup",
+ "status": 200,
+ "durationMs": 132,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"19:25:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"19:25:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "8bf0e0c3-d336-42ca-80ff-c2214c23b131",
+ "timestamp": 1769709007731,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/43191/regularPickup",
+ "status": 200,
+ "durationMs": 135,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":0,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":1,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "74c79e71-4228-4658-9dea-753b90e0aeea",
+ "timestamp": 1769709007731,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42322/regularPickup",
+ "status": 200,
+ "durationMs": 135,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:30:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "562ffa10-4ad9-4687-9107-f439bbd2251c",
+ "timestamp": 1769709007733,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/63448/regularPickup",
+ "status": 200,
+ "durationMs": 137,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":4,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":\"\"},{\"weekday\":5,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "1abb674b-d193-4ec7-863d-c4d96a76d44e",
+ "timestamp": 1769709007738,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/59378/regularPickup",
+ "status": 200,
+ "durationMs": 141,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":5,\"startTimeOfPickup\":\"08:30:00\",\"maxCountOfSlots\":2,\"description\":\"Abholzeit flexibel zwischen 8 und 9 Uhr\"},{\"weekday\":6,\"startTimeOfPickup\":\"08:30:00\",\"maxCountOfSlots\":2,\"description\":\"Abholzeit flexibel zwischen 8 und 9 Uhr\"}]"
+ },
+ {
+ "id": "b574e699-84f9-4827-9ed2-c533a6d1d82c",
+ "timestamp": 1769709007738,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 142,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "e130aebd-88b3-41c7-be24-e0b9b81b15c6",
+ "timestamp": 1769709007739,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/44972/regularPickup",
+ "status": 200,
+ "durationMs": 143,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"20:25:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"20:25:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "f4860c8e-dc49-408e-a78c-40117c82487a",
+ "timestamp": 1769709007741,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42264/regularPickup",
+ "status": 200,
+ "durationMs": 144,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"14:05:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "2fd51a8c-2ebf-4a49-a54f-9ac09c88e7ad",
+ "timestamp": 1769709012688,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/43191/regularPickup",
+ "status": 200,
+ "durationMs": 5092,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":0,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":1,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "c1970979-b6e0-4ad2-ade4-b287eee58d94",
+ "timestamp": 1769709012689,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/28513/regularPickup",
+ "status": 200,
+ "durationMs": 5093,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "00ef3cee-5ca3-4b8c-b100-dd764d4b5a21",
+ "timestamp": 1769709012694,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/63448/regularPickup",
+ "status": 200,
+ "durationMs": 5098,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":4,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":\"\"},{\"weekday\":5,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "e5591265-b86b-4588-ac2e-ce06a8b654f3",
+ "timestamp": 1769709012698,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/44975/regularPickup",
+ "status": 200,
+ "durationMs": 5102,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"19:25:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"19:25:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "8c2d7b54-795e-4a13-a129-2973c15492a6",
+ "timestamp": 1769709012700,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42264/regularPickup",
+ "status": 200,
+ "durationMs": 5104,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"14:05:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "838e074c-6ebd-416e-9757-da5293b06b96",
+ "timestamp": 1769709012701,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42322/regularPickup",
+ "status": 200,
+ "durationMs": 5105,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:30:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "4ac0c57d-1608-4076-bd01-2858b7a2c913",
+ "timestamp": 1769709012705,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/44972/regularPickup",
+ "status": 200,
+ "durationMs": 5108,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"20:25:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"20:25:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "c534cf74-4eeb-4fb9-a625-c2f30e261e13",
+ "timestamp": 1769709012709,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/59378/regularPickup",
+ "status": 200,
+ "durationMs": 5113,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":5,\"startTimeOfPickup\":\"08:30:00\",\"maxCountOfSlots\":2,\"description\":\"Abholzeit flexibel zwischen 8 und 9 Uhr\"},{\"weekday\":6,\"startTimeOfPickup\":\"08:30:00\",\"maxCountOfSlots\":2,\"description\":\"Abholzeit flexibel zwischen 8 und 9 Uhr\"}]"
+ },
+ {
+ "id": "71ba5787-44ee-4717-aa73-28cdd4a9a52e",
+ "timestamp": 1769709027533,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "POST",
+ "path": "/api/user/login",
+ "status": 200,
+ "durationMs": 606,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"name\":\"Meik\",\"avatar\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"isSleeping\":false}"
+ },
+ {
+ "id": "7931c674-352c-47d7-a7c9-5618c29aa733",
+ "timestamp": 1769709027594,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/user/current/details",
+ "status": 200,
+ "durationMs": 60,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"foodsaver\":true,\"isVerified\":true,\"regionId\":588,\"isSleeping\":0,\"regionName\":\"Rastatt/Baden-Baden\",\"aboutMePublic\":\"\",\"mailboxId\":null,\"hasCalendarToken\":true,\"firstname\":\"Meik\",\"lastname\":\"Drechsler\",\"gender\":1,\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"sleeping\":true,\"lastPassDate\":\"2024-04-30 09:36:50\",\"lastPassUntilValid\":\"2027-04-30T09:36:50+02:00\",\"lastPassUntilValidInDays\":456,\"stats\":{\"weight\":2369.5,\"count\":146},\"permissions\":{\"mayEditUserProfile\":true,\"mayAdministrateUserProfile\":false,\"administrateBlog\":false,\"editQuiz\":false,\"handleReports\":false,\"addStore\":false,\"editContent\":false,\"administrateNewsletterEmail\":false,\"administrateRegions\":false,\"maySearchGlobal\":false},\"hasActiveEmail\":true,\"coordinates\":{\"lat\":48.80617805,\"lon\":8.222051246654733},\"address\":\"Rathausplatz 1\",\"city\":\"Baden-Baden\",\"postcode\":\"76532\",\"email\":\"meikdre@gmx.de\",\"landline\":\"\",\"mobile\":\"+49 176 27186806\",\"birthday\":\"1985-07-27\",\"aboutMeIntern\":\"\",\"role\":1,\"regions\":[{\"id\":880,\"name\":\"Bühl (Baden)\",\"classification\":1,\"isResponsible\":false},{\"id\":2646,\"name\":\"Ettlingen & südl. Landkreis Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":1432,\"name\":\"foodsharing auf Festivals\",\"classification\":1,\"isResponsible\":false},{\"id\":433,\"name\":\"Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":248,\"name\":\"Ortenaukreis\",\"classification\":1,\"isResponsible\":false},{\"id\":588,\"name\":\"Rastatt/Baden-Baden\",\"classification\":1,\"isResponsible\":false},{\"id\":52,\"name\":\"Baden-Württemberg\",\"classification\":5,\"isResponsible\":false},{\"id\":1,\"name\":\"Deutschland\",\"classification\":6,\"isResponsible\":false},{\"id\":741,\"name\":\"Europa\",\"classification\":6,\"isResponsible\":false}],\"groups\":[]}"
+ },
+ {
+ "id": "410d3ea0-1d0e-4336-b493-449743d55eaf",
+ "timestamp": 1769709027673,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 77,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "daf4822d-15b4-4809-8399-3bfcd4c92ba6",
+ "timestamp": 1769709027744,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42322/regularPickup",
+ "status": 200,
+ "durationMs": 147,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:30:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "cf1e5adf-8bfc-4be4-8207-cfb3c974da6f",
+ "timestamp": 1769709027748,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/44975/regularPickup",
+ "status": 200,
+ "durationMs": 152,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"19:55:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"19:25:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"19:25:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "8b7380e0-1728-43b5-ad4d-a62ae16c7c9f",
+ "timestamp": 1769709027750,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/28513/regularPickup",
+ "status": 200,
+ "durationMs": 154,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "1614d753-42b5-4ace-8b3a-6c4b681dc5da",
+ "timestamp": 1769709027751,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/44972/regularPickup",
+ "status": 200,
+ "durationMs": 155,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"20:35:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"20:25:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"20:25:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "d67ba029-f168-4396-8298-b4292168ed4b",
+ "timestamp": 1769709027753,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/63448/regularPickup",
+ "status": 200,
+ "durationMs": 157,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":4,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":\"\"},{\"weekday\":5,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"20:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "3b703bcb-3ab3-483d-8037-de2e6aa31b66",
+ "timestamp": 1769709027771,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/59378/regularPickup",
+ "status": 200,
+ "durationMs": 174,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":5,\"startTimeOfPickup\":\"08:30:00\",\"maxCountOfSlots\":2,\"description\":\"Abholzeit flexibel zwischen 8 und 9 Uhr\"},{\"weekday\":6,\"startTimeOfPickup\":\"08:30:00\",\"maxCountOfSlots\":2,\"description\":\"Abholzeit flexibel zwischen 8 und 9 Uhr\"}]"
+ },
+ {
+ "id": "dfd66b19-72b0-493d-92ce-16d0d331dc34",
+ "timestamp": 1769709027773,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/43191/regularPickup",
+ "status": 200,
+ "durationMs": 176,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":0,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":1,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":2,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":6,\"startTimeOfPickup\":\"18:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "a92c9fbe-6312-42fa-8d3a-1a3cd7eb0f72",
+ "timestamp": 1769709027775,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42264/regularPickup",
+ "status": 200,
+ "durationMs": 179,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"14:05:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "e8cdac5a-903f-4563-8e31-58a026a53b30",
+ "timestamp": 1769709027789,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 193,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "6fdaf524-0668-426f-b6e3-f7eb8a126e72",
+ "timestamp": 1769709032762,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/28513/regularPickup",
+ "status": 200,
+ "durationMs": 5166,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "cfffb1d9-2515-4d19-9060-1e84855140ec",
+ "timestamp": 1769709032780,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42322/regularPickup",
+ "status": 200,
+ "durationMs": 5183,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:30:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "5e0f8561-3d50-4bbd-9bc0-cb00fa4f6b21",
+ "timestamp": 1769709369410,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "POST",
+ "path": "/api/user/login",
+ "status": 200,
+ "durationMs": 551,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"name\":\"Meik\",\"avatar\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"isSleeping\":false}"
+ },
+ {
+ "id": "17ecf1c9-cc57-4701-b671-cd097c5e7e82",
+ "timestamp": 1769709369459,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/user/current/details",
+ "status": 200,
+ "durationMs": 48,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"foodsaver\":true,\"isVerified\":true,\"regionId\":588,\"isSleeping\":0,\"regionName\":\"Rastatt/Baden-Baden\",\"aboutMePublic\":\"\",\"mailboxId\":null,\"hasCalendarToken\":true,\"firstname\":\"Meik\",\"lastname\":\"Drechsler\",\"gender\":1,\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"sleeping\":true,\"lastPassDate\":\"2024-04-30 09:36:50\",\"lastPassUntilValid\":\"2027-04-30T09:36:50+02:00\",\"lastPassUntilValidInDays\":456,\"stats\":{\"weight\":2369.5,\"count\":146},\"permissions\":{\"mayEditUserProfile\":true,\"mayAdministrateUserProfile\":false,\"administrateBlog\":false,\"editQuiz\":false,\"handleReports\":false,\"addStore\":false,\"editContent\":false,\"administrateNewsletterEmail\":false,\"administrateRegions\":false,\"maySearchGlobal\":false},\"hasActiveEmail\":true,\"coordinates\":{\"lat\":48.80617805,\"lon\":8.222051246654733},\"address\":\"Rathausplatz 1\",\"city\":\"Baden-Baden\",\"postcode\":\"76532\",\"email\":\"meikdre@gmx.de\",\"landline\":\"\",\"mobile\":\"+49 176 27186806\",\"birthday\":\"1985-07-27\",\"aboutMeIntern\":\"\",\"role\":1,\"regions\":[{\"id\":880,\"name\":\"Bühl (Baden)\",\"classification\":1,\"isResponsible\":false},{\"id\":2646,\"name\":\"Ettlingen & südl. Landkreis Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":1432,\"name\":\"foodsharing auf Festivals\",\"classification\":1,\"isResponsible\":false},{\"id\":433,\"name\":\"Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":248,\"name\":\"Ortenaukreis\",\"classification\":1,\"isResponsible\":false},{\"id\":588,\"name\":\"Rastatt/Baden-Baden\",\"classification\":1,\"isResponsible\":false},{\"id\":52,\"name\":\"Baden-Württemberg\",\"classification\":5,\"isResponsible\":false},{\"id\":1,\"name\":\"Deutschland\",\"classification\":6,\"isResponsible\":false},{\"id\":741,\"name\":\"Europa\",\"classification\":6,\"isResponsible\":false}],\"groups\":[]}"
+ },
+ {
+ "id": "f9914e9b-7ca6-45a4-ae72-6602c1290e1e",
+ "timestamp": 1769709369520,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/33875/regularPickup",
+ "status": 200,
+ "durationMs": 59,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null},{\"weekday\":4,\"startTimeOfPickup\":\"17:00:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "e55b9f40-1cc2-4d4f-b2e0-bcbd4f0ddc92",
+ "timestamp": 1769709369551,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/28513/regularPickup",
+ "status": 200,
+ "durationMs": 30,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":3,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null},{\"weekday\":5,\"startTimeOfPickup\":\"18:30:00\",\"maxCountOfSlots\":2,\"description\":null}]"
+ },
+ {
+ "id": "1595e5c4-3b20-4292-9323-bac20fb87b4c",
+ "timestamp": 1769709369591,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/42322/regularPickup",
+ "status": 200,
+ "durationMs": 39,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"weekday\":1,\"startTimeOfPickup\":\"17:30:00\",\"maxCountOfSlots\":1,\"description\":null}]"
+ },
+ {
+ "id": "44b72749-d589-4d52-a1e7-acc4b46785f3",
+ "timestamp": 1769712932096,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "POST",
+ "path": "/api/user/login",
+ "status": 200,
+ "durationMs": 1014,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"name\":\"Meik\",\"avatar\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"isSleeping\":false}"
+ },
+ {
+ "id": "b9d38f90-cbcc-4584-9adf-42fe13cde2ed",
+ "timestamp": 1769712932180,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/user/current/details",
+ "status": 200,
+ "durationMs": 82,
+ "sessionId": null,
+ "profileId": null,
+ "profileName": null,
+ "responseBody": "{\"id\":839246,\"foodsaver\":true,\"isVerified\":true,\"regionId\":588,\"isSleeping\":0,\"regionName\":\"Rastatt/Baden-Baden\",\"aboutMePublic\":\"\",\"mailboxId\":null,\"hasCalendarToken\":true,\"firstname\":\"Meik\",\"lastname\":\"Drechsler\",\"gender\":1,\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"sleeping\":true,\"lastPassDate\":\"2024-04-30 09:36:50\",\"lastPassUntilValid\":\"2027-04-30T09:36:50+02:00\",\"lastPassUntilValidInDays\":456,\"stats\":{\"weight\":2369.5,\"count\":146},\"permissions\":{\"mayEditUserProfile\":true,\"mayAdministrateUserProfile\":false,\"administrateBlog\":false,\"editQuiz\":false,\"handleReports\":false,\"addStore\":false,\"editContent\":false,\"administrateNewsletterEmail\":false,\"administrateRegions\":false,\"maySearchGlobal\":false},\"hasActiveEmail\":true,\"coordinates\":{\"lat\":48.80617805,\"lon\":8.222051246654733},\"address\":\"Rathausplatz 1\",\"city\":\"Baden-Baden\",\"postcode\":\"76532\",\"email\":\"meikdre@gmx.de\",\"landline\":\"\",\"mobile\":\"+49 176 27186806\",\"birthday\":\"1985-07-27\",\"aboutMeIntern\":\"\",\"role\":1,\"regions\":[{\"id\":880,\"name\":\"Bühl (Baden)\",\"classification\":1,\"isResponsible\":false},{\"id\":2646,\"name\":\"Ettlingen & südl. Landkreis Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":1432,\"name\":\"foodsharing auf Festivals\",\"classification\":1,\"isResponsible\":false},{\"id\":433,\"name\":\"Karlsruhe\",\"classification\":1,\"isResponsible\":false},{\"id\":248,\"name\":\"Ortenaukreis\",\"classification\":1,\"isResponsible\":false},{\"id\":588,\"name\":\"Rastatt/Baden-Baden\",\"classification\":1,\"isResponsible\":false},{\"id\":52,\"name\":\"Baden-Württemberg\",\"classification\":5,\"isResponsible\":false},{\"id\":1,\"name\":\"Deutschland\",\"classification\":6,\"isResponsible\":false},{\"id\":741,\"name\":\"Europa\",\"classification\":6,\"isResponsible\":false}],\"groups\":[]}"
+ },
+ {
+ "id": "b7cf3803-f4ce-4753-8b9d-db25d5e3fc1b",
+ "timestamp": 1769712932225,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/63448/member",
+ "status": 200,
+ "durationMs": 44,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"id\":72367,\"verified\":1,\"telefon\":\"\",\"handy\":\"+491759541740\",\"photo\":\"/api/uploads/3044ca9a-5f50-4dfa-9422-cc07adb1102f\",\"rolle\":2,\"firstName\":\"Petra\",\"name\":\"Petra Lergenmueller\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":14,\"last_fetch\":1768431600,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-11-27 22:50:38\"},{\"id\":480024,\"verified\":1,\"telefon\":\"+49 7221 9229136\",\"handy\":\"+49 162 7976269\",\"photo\":\"/api/uploads/b45de690-af9b-465d-a8ee-d1e0f55efa9a\",\"rolle\":3,\"firstName\":\"Bärbel\",\"name\":\"Bärbel Neumann\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":17,\"last_fetch\":1766962800,\"add_date\":1736031600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-11-27 16:20:21\"},{\"id\":480138,\"verified\":1,\"telefon\":\"\",\"handy\":\"+491736612046\",\"photo\":\"/api/uploads/ad7c4e6a-6354-4d7d-bd5c-d6240321ba19\",\"rolle\":2,\"firstName\":\"Thomas\",\"name\":\"Thomas Steurer\",\"team_active\":1,\"verantwortlich\":1,\"stat_fetchcount\":2,\"last_fetch\":1739574000,\"add_date\":null,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-07 20:38:59\"},{\"id\":499918,\"verified\":1,\"telefon\":\"07221/3731560\",\"handy\":\"+49 1525 1435456\",\"photo\":\"/api/uploads/9a55e28b-43d2-451d-90c0-ba03d5e20eed\",\"rolle\":1,\"firstName\":\"Johanna\",\"name\":\"Johanna Tremmel\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":7,\"last_fetch\":1767826800,\"add_date\":1731106800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-21 18:35:24\"},{\"id\":542557,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 177 7755933\",\"photo\":\"/api/uploads/706555ca-6f18-4e76-9a7f-d2d106ac494f\",\"rolle\":2,\"firstName\":\"Franziska\",\"name\":\"Franziska Specht\",\"team_active\":1,\"verantwortlich\":1,\"stat_fetchcount\":10,\"last_fetch\":1769122800,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-28 17:06:36\"},{\"id\":555500,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1520 4492701\",\"photo\":\"/api/uploads/95e8339a-c11e-473a-a7ed-c71273796319\",\"rolle\":1,\"firstName\":\"Jasmin\",\"name\":\"Jasmin Weber\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1763593200,\"add_date\":1731452400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-16 08:55:10\"},{\"id\":620442,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915170161244\",\"photo\":\"/api/uploads/dede9224-0d2b-4bf4-b9bc-606d35612b7f\",\"rolle\":1,\"firstName\":\"Loredana\",\"name\":\"Loredana Bertram\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1758146400,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-04 13:39:30\"},{\"id\":643488,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 178 4258400\",\"photo\":\"/api/uploads/1edcebf2-84ca-4d19-9b50-8ec5357d2768\",\"rolle\":1,\"firstName\":\"Jonas\",\"name\":\"Jonas Krasowski\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":9,\"last_fetch\":1763679600,\"add_date\":1731970800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-11 21:47:34\"},{\"id\":651263,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 177 1632260\",\"photo\":\"/api/uploads/72fd1f98-47b1-4839-bde4-9b32fa4f2c7f\",\"rolle\":1,\"firstName\":\"Juliana\",\"name\":\"Juliana Wisniowski\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":2,\"last_fetch\":1751061600,\"add_date\":1732489200,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-06 20:56:00\"},{\"id\":656007,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1516 5475914\",\"photo\":\"/api/uploads/ae77edac-c34e-4664-82cd-4a1fb30ea667\",\"rolle\":1,\"firstName\":\"Daniela\",\"name\":\"Daniela Hitscherich\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1767913200,\"add_date\":1731106800,\"is_sleeping\":1,\"hygiene_certificate_until\":\"2026-02-04 15:07:12\"},{\"id\":664322,\"verified\":1,\"telefon\":\"+4972213758070\",\"handy\":\"+49 162 6740943\",\"photo\":\"/api/uploads/c390cfe6-a43b-4215-b3d9-1144eda92ee9\",\"rolle\":1,\"firstName\":\"Christina\",\"name\":\"Christina Huber\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":5,\"last_fetch\":1764284400,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-22 19:09:55\"},{\"id\":678288,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 172 9609400\",\"photo\":\"/api/uploads/bc25737a-1fe5-4657-97ce-91fd7e8b3881\",\"rolle\":1,\"firstName\":\"Susanne\",\"name\":\"Susanne Krupp\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":4,\"last_fetch\":1761778800,\"add_date\":1731106800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-04 22:37:25\"},{\"id\":704922,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 162 9877749\",\"photo\":\"/api/uploads/f2e6f669-895b-45ad-b4b7-bbdb40804e7a\",\"rolle\":2,\"firstName\":\"Marion\",\"name\":\"Marion Barth\",\"team_active\":1,\"verantwortlich\":1,\"stat_fetchcount\":39,\"last_fetch\":1769036400,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-22 11:58:33\"},{\"id\":712859,\"verified\":1,\"telefon\":\"+49 7227 3204\",\"handy\":\"+49 176 81316208\",\"photo\":\"/api/uploads/0dcd2ea5-798c-4edb-b638-a6c9c7061503\",\"rolle\":2,\"firstName\":\"Tanja\",\"name\":\"Tanja Volz\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":4,\"last_fetch\":1758924000,\"add_date\":1731884400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-22 07:48:39\"},{\"id\":717659,\"verified\":1,\"telefon\":\"\",\"handy\":\"+491638902273\",\"photo\":\"/api/uploads/d70850de-2667-426b-9cf9-e5c55da758bc\",\"rolle\":2,\"firstName\":\"Melanie\",\"name\":\"Melanie Schulz\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":10,\"last_fetch\":1761778800,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-22 08:07:48\"},{\"id\":723369,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1523 4543730\",\"photo\":\"/api/uploads/26e8a63f-e97e-4339-8fe2-dffa3f5dac6e\",\"rolle\":1,\"firstName\":\"Anna Maria\",\"name\":\"Anna Maria Grunert\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":18,\"last_fetch\":1768604400,\"add_date\":1734303600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-21 13:37:26\"},{\"id\":756993,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1577 1527363\",\"photo\":\"/api/uploads/2e190577-207f-49fb-a651-2ce2c0896a52\",\"rolle\":1,\"firstName\":\"Andrea\",\"name\":\"Andrea Boos-Klinner\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":7,\"last_fetch\":1767567600,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-29 19:17:23\"},{\"id\":773277,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 63208727\",\"photo\":\"/api/uploads/5f041bd2-8eed-426b-bf6b-6dd1f81baa43\",\"rolle\":1,\"firstName\":\"Martina\",\"name\":\"Martina Ziekursch\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":4,\"last_fetch\":1767308400,\"add_date\":1761170400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-08-21 21:31:54\"},{\"id\":779726,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 55032106\",\"photo\":\"/api/uploads/9f9bf2a4-b6c7-4cdf-b20c-2d663334b97b\",\"rolle\":2,\"firstName\":\"Mandy\",\"name\":\"Mandy Schäfer\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":16,\"last_fetch\":1769209200,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-26 07:35:50\"},{\"id\":781112,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915773864806\",\"photo\":\"/api/uploads/aaf5f832-c3e7-4706-bfc2-a92054c4de86\",\"rolle\":2,\"firstName\":\"Alberto\",\"name\":\"Alberto Bosco\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":7,\"last_fetch\":1755208800,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-02-12 20:52:28\"},{\"id\":782440,\"verified\":1,\"telefon\":\"+4972224084337\",\"handy\":\"+49 176 82103115\",\"photo\":\"/api/uploads/5af437f7-e011-4af0-a5c5-6d24ab127179\",\"rolle\":2,\"firstName\":\"Vanessa\",\"name\":\"Vanessa Kolb\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":4,\"last_fetch\":1752098400,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-04 09:54:16\"},{\"id\":833212,\"verified\":1,\"telefon\":\"+49 7222 157111\",\"handy\":\"+49 176 20420891\",\"photo\":\"/api/uploads/64c804a6-b360-4b54-88a9-fa8864c48e7f\",\"rolle\":2,\"firstName\":\"Marco\",\"name\":\"Marco Kappenberger\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":9,\"last_fetch\":1768518000,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-09 09:41:39\"},{\"id\":836860,\"verified\":1,\"telefon\":\"+49 7223 60631\",\"handy\":\"+49 173 6879116\",\"photo\":\"/api/uploads/5360ed79-70a0-44d5-ab6b-1c6e24a69fcf\",\"rolle\":1,\"firstName\":\"Martina\",\"name\":\"Martina Rohrer\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":12,\"last_fetch\":1769036400,\"add_date\":1731711600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-21 21:16:25\"},{\"id\":839246,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 27186806\",\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"rolle\":1,\"firstName\":\"Meik\",\"name\":\"Meik Drechsler\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":7,\"last_fetch\":1759528800,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-01 17:19:48\"},{\"id\":845416,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 15753058372\",\"photo\":\"/api/uploads/4a37ff30-a184-473d-83e0-9ee1015fd400\",\"rolle\":1,\"firstName\":\"Heidi\",\"name\":\"Heidi Pinnekamp\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":12,\"last_fetch\":1769122800,\"add_date\":1732143600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-09 21:09:58\"},{\"id\":852886,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1590 6331282\",\"photo\":\"/api/uploads/babef915-c92f-47c9-8df8-dddd3e4a312b\",\"rolle\":1,\"firstName\":\"Victoria\",\"name\":\"Victoria Regenold\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1766962800,\"add_date\":1732143600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-08 20:01:49\"},{\"id\":858044,\"verified\":1,\"telefon\":\"+49 160 91135345\",\"handy\":\"+49 160 91135345\",\"photo\":\"/api/uploads/5ed74a18-fc6b-4877-9eee-4f8adbd94837\",\"rolle\":2,\"firstName\":\"Daniel\",\"name\":\"Daniel Krasowski\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":9,\"last_fetch\":1763679600,\"add_date\":1731884400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-10 17:17:18\"},{\"id\":859155,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 84213403\",\"photo\":\"/api/uploads/32b0c11b-4f8a-4ed9-ab7c-d35391ded6d3\",\"rolle\":1,\"firstName\":\"Hanna\",\"name\":\"Hanna Gros\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":22,\"last_fetch\":1768604400,\"add_date\":1731020400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-12 15:11:58\"},{\"id\":924594,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 55033116\",\"photo\":\"/api/uploads/b44… (gekürzt)"
+ },
+ {
+ "id": "a5e16968-443f-4482-a23b-b900065f2fe7",
+ "timestamp": 1769712932272,
+ "direction": "outgoing",
+ "target": "foodsharing.de",
+ "method": "GET",
+ "path": "/api/stores/28513/member",
+ "status": 200,
+ "durationMs": 42,
+ "sessionId": "debug",
+ "profileId": "839246",
+ "profileName": "debug",
+ "responseBody": "[{\"id\":4594,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4917622779745\",\"photo\":\"/api/uploads/6de61378-a878-4811-bb49-20c0479a966c\",\"rolle\":3,\"firstName\":\"Judith\",\"name\":\"Judith Großmann\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":27,\"last_fetch\":1759701600,\"add_date\":1581116400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-08 11:45:07\"},{\"id\":4901,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 78682991\",\"photo\":\"/api/uploads/b68ad216-13ea-4d9d-8e27-bda8f33e7e27\",\"rolle\":2,\"firstName\":\"Wiebke\",\"name\":\"Wiebke Freund\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":119,\"last_fetch\":1769554800,\"add_date\":1593381600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-08 12:41:36\"},{\"id\":85512,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915126169529\",\"photo\":\"/api/uploads/3c53a0b6-826b-40f4-bd9f-d3906d2bbd85\",\"rolle\":2,\"firstName\":\"Aline\",\"name\":\"Aline Vollmer\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1769122800,\"add_date\":1722808800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-16 10:58:50\"},{\"id\":266968,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4917662317884\",\"photo\":\"/api/uploads/37ddbf3c-4748-4400-b45b-abffed82bda4\",\"rolle\":2,\"firstName\":\"Verena\",\"name\":\"Verena Rau\",\"team_active\":1,\"verantwortlich\":1,\"stat_fetchcount\":31,\"last_fetch\":1756072800,\"add_date\":1581116400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-08 21:45:02\"},{\"id\":287710,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4917631332429\",\"photo\":\"/api/uploads/a8c6de80-ef9a-4dad-9ee9-bc84c8d97a15\",\"rolle\":3,\"firstName\":\"Bülent\",\"name\":\"Bülent Yildiz\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":61,\"last_fetch\":1767308400,\"add_date\":1582758000,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-30 23:51:35\"},{\"id\":293192,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49176 61628971\",\"photo\":\"/api/uploads/b4f56932-20ac-4e9e-b9c1-e9a0d98a73e8\",\"rolle\":2,\"firstName\":\"Tobias\",\"name\":\"Tobias Rau\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":28,\"last_fetch\":1762124400,\"add_date\":1581462000,\"is_sleeping\":1,\"hygiene_certificate_until\":\"2026-06-16 09:44:27\"},{\"id\":312199,\"verified\":1,\"telefon\":\"+49 722282811\",\"handy\":\"+49 176 44411938\",\"photo\":\"/api/uploads/844d8bbc-16d9-4972-8b30-89d55f706727\",\"rolle\":1,\"firstName\":\"Jürgen\",\"name\":\"Jürgen Ströhm\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":43,\"last_fetch\":1765926000,\"add_date\":1588024800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-19 13:20:55\"},{\"id\":350428,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 34367165\",\"photo\":\"/api/uploads/6fcdadc1-6d8d-4f37-89cd-7c9d407e66d1\",\"rolle\":2,\"firstName\":\"Mirko\",\"name\":\"Mirko Haustein\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":62,\"last_fetch\":1767913200,\"add_date\":1581202800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-31 09:30:44\"},{\"id\":352135,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915111073250\",\"photo\":\"/api/uploads/bfc63de1-e823-4b7e-a9a7-7cbed202e204\",\"rolle\":1,\"firstName\":\"Elisabeth\",\"name\":\"Elisabeth Braun\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":46,\"last_fetch\":1755208800,\"add_date\":1581548400,\"is_sleeping\":1,\"hygiene_certificate_until\":\"2026-04-28 17:23:03\"},{\"id\":354690,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4917632596981\",\"photo\":\"/api/uploads/db7dbb15-418d-4b04-8bc2-f3bd854c44ce\",\"rolle\":3,\"firstName\":\"Kristina\",\"name\":\"Kristina Petri\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":41,\"last_fetch\":1763679600,\"add_date\":1581202800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-08 21:06:02\"},{\"id\":355126,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4917661892781\",\"photo\":\"/api/uploads/3c41add7-ff6a-44f4-ba4d-062bdd4d6866\",\"rolle\":2,\"firstName\":\"Vanessa\",\"name\":\"Vanessa Petri\",\"team_active\":1,\"verantwortlich\":1,\"stat_fetchcount\":27,\"last_fetch\":1763679600,\"add_date\":1581202800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-08 20:59:33\"},{\"id\":358959,\"verified\":1,\"telefon\":\"\",\"handy\":\"+491777947596\",\"photo\":\"/api/uploads/42280e9b-6950-40ec-9bf7-675d9504d850\",\"rolle\":1,\"firstName\":\"Sabine\",\"name\":\"Sabine Engel\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":120,\"last_fetch\":1769382000,\"add_date\":1581202800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-19 18:15:21\"},{\"id\":380685,\"verified\":1,\"telefon\":\"\",\"handy\":\"+491739738219\",\"photo\":\"/api/uploads/bbfb7cc4-00ba-4ac4-a7fb-fa8508b62ccb\",\"rolle\":1,\"firstName\":\"Timo\",\"name\":\"Timo Krebs\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":33,\"last_fetch\":1755640800,\"add_date\":1582930800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-01 17:59:50\"},{\"id\":401726,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 179 2305734\",\"photo\":\"/api/uploads/5b7a3a4b-1f40-4095-b62f-362a296a85c3\",\"rolle\":3,\"firstName\":\"Christine\",\"name\":\"Christine Huber\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":50,\"last_fetch\":1769382000,\"add_date\":1627509600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-25 14:54:12\"},{\"id\":403706,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915209685667\",\"photo\":\"/api/uploads/ab3f5dc8-aa13-4d4f-a0bf-c31cf954908f\",\"rolle\":2,\"firstName\":\"Alexandra\",\"name\":\"Alexandra Weber-Holfelder\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":21,\"last_fetch\":1762297200,\"add_date\":1626732000,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-11-10 22:15:22\"},{\"id\":437226,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915736812515\",\"photo\":\"/api/uploads/2e0bcbc5-9d32-47f2-87f4-35060c99a522\",\"rolle\":2,\"firstName\":\"Doris\",\"name\":\"Doris Feichtenbeiner\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":16,\"last_fetch\":1768345200,\"add_date\":1744149600,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-20 17:21:27\"},{\"id\":464357,\"verified\":1,\"telefon\":\"\",\"handy\":\"+4915252763308\",\"photo\":\"/api/uploads/4042c69d-1e38-4bfd-aa06-9699b1b8fead\",\"rolle\":2,\"firstName\":\"Anna\",\"name\":\"Anna Dalibor\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":24,\"last_fetch\":1767567600,\"add_date\":1637708400,\"is_sleeping\":1,\"hygiene_certificate_until\":\"2027-01-04 10:13:09\"},{\"id\":476113,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 42689051\",\"photo\":\"/api/uploads/288396db-5d97-492c-99ab-f974844daacf\",\"rolle\":1,\"firstName\":\"Anna\",\"name\":\"Anna Walz\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":74,\"last_fetch\":1768172400,\"add_date\":1626559200,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-17 12:11:22\"},{\"id\":477046,\"verified\":1,\"telefon\":\"+49 7245 81510\",\"handy\":\"+49 176 42689053\",\"photo\":\"/api/uploads/773f247f-3a5d-4678-8b44-76e0101c81d9\",\"rolle\":1,\"firstName\":\"Michaela\",\"name\":\"Michaela Walz\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":58,\"last_fetch\":1765321200,\"add_date\":1627509600,\"is_sleeping\":1,\"hygiene_certificate_until\":\"2027-01-18 18:59:09\"},{\"id\":596242,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 29730638\",\"photo\":\"/api/uploads/3ab7cc19-3a87-4516-a282-11d40e93f44f\",\"rolle\":2,\"firstName\":\"Claudia\",\"name\":\"Claudia Düpree\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":55,\"last_fetch\":1768777200,\"add_date\":1693864800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-02-18 08:48:12\"},{\"id\":623411,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 178 1769019\",\"photo\":\"/api/uploads/6e81d2ea-aa8a-4717-b087-aec200cc9204\",\"rolle\":1,\"firstName\":\"Julian\",\"name\":\"Julian Leber\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1767135600,\"add_date\":1756332000,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2027-01-11 10:25:24\"},{\"id\":629146,\"verified\":1,\"telefon\":\"\",\"handy\":\"+491774280084\",\"photo\":\"/api/uploads/7ccb1c41-a9db-4cea-a302-c53b13a7c5bc\",\"rolle\":2,\"firstName\":\"Angelika\",\"name\":\"Angelika Felgner\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":3,\"last_fetch\":1763506800,\"add_date\":1756332000,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-01 15:32:20\"},{\"id\":631065,\"verified\":1,\"telefon\":\"+4972225949244\",\"handy\":\"+4917670247773\",\"photo\":\"/api/uploads/536cbd6d-3798-4e4b-bda3-331c7908a1fa\",\"rolle\":2,\"firstName\":\"Jana\",\"name\":\"Jana Rehn\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":8,\"last_fetch\":1763334000,\"add_date\":1750975200,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-17 18:22:26\"},{\"id\":723369,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1523 4543730\",\"photo\":\"/api/uploads/26e8a63f-e97e-4339-8fe2-dffa3f5dac6e\",\"rolle\":1,\"firstName\":\"Anna Maria\",\"name\":\"Anna Maria Grunert\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":4,\"last_fetch\":1765753200,\"add_date\":1756332000,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-12-21 13:37:26\"},{\"id\":744334,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 54462229\",\"photo\":\"/api/uploads/6a90f7df-2568-467e-9df6-32bd6ec14f6a\",\"rolle\":1,\"firstName\":\"Franziska\",\"name\":\"Franziska Vorreiter\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":8,\"last_fetch\":1769554800,\"add_date\":1756418400,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-10 11:49:20\"},{\"id\":774195,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 1577 1344467\",\"photo\":\"/api/uploads/b3663e46-0689-4068-8c2a-ae115e284d2f\",\"rolle\":1,\"firstName\":\"Hanna\",\"name\":\"Hanna Braun\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":35,\"last_fetch\":1768950000,\"add_date\":1693864800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-30 22:29:42\"},{\"id\":801423,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 72476875\",\"photo\":\"/api/uploads/1170c2e8-ad49-4254-8a83-df889d2d2beb\",\"rolle\":2,\"firstName\":\"Diana\",\"name\":\"Diana Maier\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":2,\"last_fetch\":1768950000,\"add_date\":1764802800,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-28 20:35:49\"},{\"id\":810510,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 178 4889206\",\"photo\":\"/api/uploads/fa60aa81-7fe3-469f-8808-137a887fa881\",\"rolle\":1,\"firstName\":\"Florian\",\"name\":\"Florian Pahl\",\"team_active\":1,\"verantwortlich\":0,\"stat_fetchcount\":42,\"last_fetch\":1768777200,\"add_date\":1712095200,\"is_sleeping\":0,\"hygiene_certificate_until\":\"2026-04-18 21:48:45\"},{\"id\":839246,\"verified\":1,\"telefon\":\"\",\"handy\":\"+49 176 27186806\",\"photo\":\"/api/uploads/a66e10af-3fa2-47c6-9089-99a122aa2c4d\",\"rolle\":1,\"firstName\":\"Meik\",\"name\":\"Meik Drechsler\",\"team_active\":1,\"verant… (gekürzt)"
+ }
+]
\ No newline at end of file
diff --git a/server.js b/server.js
index 325bb4d..59ceb2d 100644
--- a/server.js
+++ b/server.js
@@ -11,7 +11,9 @@ const {
scheduleConfig,
runStoreWatchCheck,
runImmediatePickupCheck,
- runDormantMembershipCheck
+ runDormantMembershipCheck,
+ getRegularPickupSchedule,
+ scheduleRegularPickupRefresh
} = require('./services/pickupScheduler');
const adminConfig = require('./services/adminConfig');
const { readNotificationSettings, writeNotificationSettings } = require('./services/userSettingsStore');
@@ -162,6 +164,7 @@ function rescheduleAllSessions() {
const config = readConfig(session.profile.id);
scheduleConfig(session.id, config, settings);
});
+ scheduleRegularPickupRefresh(settings);
}
function mergeStoresIntoConfig(config = [], stores = []) {
@@ -1323,19 +1326,45 @@ app.get('/api/stores/:storeId/regular-pickup', requireAuth, async (req, res) =>
return res.status(400).json({ error: 'Store-ID fehlt' });
}
try {
- const rules = await withSessionRetry(
- req.session,
- () => foodsharingClient.fetchRegularPickup(storeId, req.session.cookieHeader, req.session),
- { label: 'fetchRegularPickup' }
- );
- res.json(Array.isArray(rules) ? rules : []);
+ const result = await getRegularPickupSchedule(req.session, storeId);
+ res.json(result);
} catch (error) {
- res.status(400).json({ error: error.message || 'Regular-Pickup konnte nicht geladen werden' });
+ const message = error?.message || 'Regular-Pickup konnte nicht geladen werden';
+ return res.json({ rules: [], error: message });
}
});
app.get('/api/stores', requireAuth, async (req, res) => {
- res.json(req.session.storesCache?.data || []);
+ const stores = req.session.storesCache?.data || [];
+ let regularPickupMap = {};
+ try {
+ const profileId = req.session.profile?.id;
+ if (profileId) {
+ const config = readConfig(profileId);
+ const storeIds = Array.from(
+ new Set(
+ (Array.isArray(config) ? config : [])
+ .filter((entry) => entry?.id && !entry.hidden)
+ .map((entry) => String(entry.id))
+ )
+ );
+ if (storeIds.length > 0) {
+ const results = await Promise.all(
+ storeIds.map(async (storeId) => {
+ const result = await getRegularPickupSchedule(req.session, storeId);
+ return [storeId, Array.isArray(result.rules) ? result.rules : []];
+ })
+ );
+ regularPickupMap = results.reduce((acc, [storeId, rules]) => {
+ acc[storeId] = rules;
+ return acc;
+ }, {});
+ }
+ }
+ } catch (error) {
+ console.warn('[PICKUP] Regular-Pickup-Map konnte nicht geladen werden:', error.message);
+ }
+ res.json({ stores, regularPickupMap });
});
app.post('/api/stores/refresh', requireAuth, (req, res) => {
@@ -1388,6 +1417,7 @@ async function startServer() {
} catch (error) {
console.error('[RESTORE] Fehler bei der Session-Wiederherstellung:', error.message);
}
+ scheduleRegularPickupRefresh(adminConfig.readSettings());
startBackgroundStoreRefreshTicker();
app.listen(port, () => {
console.log(`Server läuft auf Port ${port}`);
diff --git a/services/adminConfig.js b/services/adminConfig.js
index be53d3b..d6c58f3 100644
--- a/services/adminConfig.js
+++ b/services/adminConfig.js
@@ -8,6 +8,8 @@ const DEFAULT_SETTINGS = {
scheduleCron: '*/10 7-22 * * *',
pickupFallbackCron: '0 7,12,17,22 * * *',
pickupWindowOffsetsMinutes: [-1, -0.5, 0, 0.5, 1, 1.5],
+ regularPickupRefreshCron: '0 3 * * *',
+ dormantMembershipCron: '0 4 */14 * *',
randomDelayMinSeconds: 10,
randomDelayMaxSeconds: 120,
initialDelayMinSeconds: 5,
@@ -133,6 +135,8 @@ function readSettings() {
parsed.pickupWindowOffsetsMinutes,
DEFAULT_SETTINGS.pickupWindowOffsetsMinutes
),
+ regularPickupRefreshCron: parsed.regularPickupRefreshCron || DEFAULT_SETTINGS.regularPickupRefreshCron,
+ dormantMembershipCron: parsed.dormantMembershipCron || DEFAULT_SETTINGS.dormantMembershipCron,
randomDelayMinSeconds: sanitizeNumber(parsed.randomDelayMinSeconds, DEFAULT_SETTINGS.randomDelayMinSeconds),
randomDelayMaxSeconds: sanitizeNumber(parsed.randomDelayMaxSeconds, DEFAULT_SETTINGS.randomDelayMaxSeconds),
initialDelayMinSeconds: sanitizeNumber(parsed.initialDelayMinSeconds, DEFAULT_SETTINGS.initialDelayMinSeconds),
@@ -176,6 +180,8 @@ function writeSettings(patch = {}) {
patch.pickupWindowOffsetsMinutes,
current.pickupWindowOffsetsMinutes
),
+ regularPickupRefreshCron: patch.regularPickupRefreshCron || current.regularPickupRefreshCron,
+ dormantMembershipCron: patch.dormantMembershipCron || current.dormantMembershipCron,
randomDelayMinSeconds: sanitizeNumber(patch.randomDelayMinSeconds, current.randomDelayMinSeconds),
randomDelayMaxSeconds: sanitizeNumber(patch.randomDelayMaxSeconds, current.randomDelayMaxSeconds),
initialDelayMinSeconds: sanitizeNumber(patch.initialDelayMinSeconds, current.initialDelayMinSeconds),
diff --git a/services/foodsharingClient.js b/services/foodsharingClient.js
index 4a435cf..97e4837 100644
--- a/services/foodsharingClient.js
+++ b/services/foodsharingClient.js
@@ -1,12 +1,19 @@
const axios = require('axios');
+const http = require('http');
+const https = require('https');
const requestLogStore = require('./requestLogStore');
const sessionStore = require('./sessionStore');
const BASE_URL = 'https://foodsharing.de';
+const keepAliveHttpAgent = new http.Agent({ keepAlive: true, maxSockets: 10 });
+const keepAliveHttpsAgent = new https.Agent({ keepAlive: true, maxSockets: 10 });
+
const client = axios.create({
baseURL: BASE_URL,
timeout: 20000,
+ httpAgent: keepAliveHttpAgent,
+ httpsAgent: keepAliveHttpsAgent,
headers: {
'User-Agent': 'pickup-config/1.0 (+https://foodsharing.de)',
Accept: 'application/json, text/plain, */*'
diff --git a/services/notificationService.js b/services/notificationService.js
index 57597bb..314882a 100644
--- a/services/notificationService.js
+++ b/services/notificationService.js
@@ -37,6 +37,23 @@ function formatDateOnly(dateInput) {
}
}
+function extractFirstName(profileName) {
+ if (!profileName || typeof profileName !== 'string') {
+ return null;
+ }
+ const trimmed = profileName.trim();
+ if (!trimmed) {
+ return null;
+ }
+ if (trimmed.includes(',')) {
+ const parts = trimmed.split(',').map((part) => part.trim()).filter(Boolean);
+ if (parts.length > 1) {
+ return parts[1].split(/\s+/)[0] || null;
+ }
+ }
+ return trimmed.split(/\s+/)[0] || null;
+}
+
async function sendNtfyNotification(adminNtfy, userNtfy, payload) {
if (!adminNtfy?.enabled || !userNtfy?.enabled || !userNtfy.topic) {
return;
@@ -66,12 +83,18 @@ async function sendTelegramNotification(adminTelegram, userTelegram, payload) {
return;
}
const endpoint = `https://api.telegram.org/bot${adminTelegram.botToken}/sendMessage`;
+ const profileLabel = extractFirstName(payload?.profileName);
+ const messageParts = [
+ payload?.title ? `*${payload.title}*` : null,
+ profileLabel ? `Profil: ${profileLabel}` : null,
+ payload?.message || null
+ ].filter(Boolean);
await axios.post(
endpoint,
{
chat_id: userTelegram.chatId,
- text: payload.title ? `*${payload.title}*\n${payload.message}` : payload.message,
- parse_mode: payload.title ? 'Markdown' : undefined,
+ text: messageParts.join('\n'),
+ parse_mode: payload?.title ? 'Markdown' : undefined,
disable_web_page_preview: true
},
{ timeout: 15000 }
@@ -104,7 +127,7 @@ async function notifyChannels(profileId, template) {
}
}
-async function sendSlotNotification({ profileId, storeName, pickupDate, onlyNotify, booked, storeId }) {
+async function sendSlotNotification({ profileId, profileName, storeName, pickupDate, onlyNotify, booked, storeId }) {
const dateLabel = formatDateLabel(pickupDate);
const title = onlyNotify
? `Slot verfügbar bei ${storeName}`
@@ -123,7 +146,8 @@ async function sendSlotNotification({ profileId, storeName, pickupDate, onlyNoti
title,
message: fullMessage,
link: storeLink,
- priority: booked ? 'high' : 'default'
+ priority: booked ? 'high' : 'default',
+ profileName
});
}
@@ -137,7 +161,7 @@ function formatStoreWatchStatus(status) {
return 'Status unbekannt';
}
-async function sendStoreWatchNotification({ profileId, storeName, storeId, regionName }) {
+async function sendStoreWatchNotification({ profileId, profileName, storeName, storeId, regionName }) {
const storeLink = storeId ? `https://foodsharing.de/store/${storeId}` : null;
const title = `Team sucht Verstärkung: ${storeName}`;
const regionText = regionName ? ` (${regionName})` : '';
@@ -147,11 +171,12 @@ async function sendStoreWatchNotification({ profileId, storeName, storeId, regio
title,
message,
link: storeLink,
- priority: 'high'
+ priority: 'high',
+ profileName
});
}
-async function sendStoreWatchSummaryNotification({ profileId, entries = [], triggeredBy = 'manual' }) {
+async function sendStoreWatchSummaryNotification({ profileId, profileName, entries = [], triggeredBy = 'manual' }) {
if (!profileId || !Array.isArray(entries) || entries.length === 0) {
return;
}
@@ -174,11 +199,12 @@ async function sendStoreWatchSummaryNotification({ profileId, entries = [], trig
await notifyChannels(profileId, {
title,
message,
- priority: 'default'
+ priority: 'default',
+ profileName
});
}
-async function sendDesiredWindowMissedNotification({ profileId, storeName, desiredWindowLabel }) {
+async function sendDesiredWindowMissedNotification({ profileId, profileName, storeName, desiredWindowLabel }) {
if (!profileId) {
return;
}
@@ -191,7 +217,8 @@ async function sendDesiredWindowMissedNotification({ profileId, storeName, desir
title,
message,
link: null,
- priority: 'default'
+ priority: 'default',
+ profileName
});
}
@@ -228,7 +255,7 @@ async function sendTestNotification(profileId, channel) {
await Promise.all(tasks);
}
-async function sendDormantPickupWarning({ profileId, storeName, storeId, reasonLines = [] }) {
+async function sendDormantPickupWarning({ profileId, profileName, storeName, storeId, reasonLines = [] }) {
if (!profileId || !Array.isArray(reasonLines) || reasonLines.length === 0) {
return;
}
@@ -242,12 +269,14 @@ async function sendDormantPickupWarning({ profileId, storeName, storeId, reasonL
await sendTelegramNotification(adminSettings.notifications?.telegram, userSettings.notifications?.telegram, {
title,
message,
- priority: 'high'
+ priority: 'high',
+ profileName
});
}
async function sendJournalReminderNotification({
profileId,
+ profileName,
storeName,
pickupDate,
reminderDate,
@@ -270,7 +299,8 @@ async function sendJournalReminderNotification({
await sendTelegramNotification(adminSettings.notifications?.telegram, userSettings.notifications?.telegram, {
title,
message: messageLines.join('\n'),
- priority: 'default'
+ priority: 'default',
+ profileName
});
}
diff --git a/services/pickupScheduler.js b/services/pickupScheduler.js
index b8e45bf..b6fc766 100644
--- a/services/pickupScheduler.js
+++ b/services/pickupScheduler.js
@@ -1,4 +1,6 @@
const cron = require('node-cron');
+const fs = require('fs');
+const path = require('path');
const foodsharingClient = require('./foodsharingClient');
const sessionStore = require('./sessionStore');
const { DEFAULT_SETTINGS } = require('./adminConfig');
@@ -23,7 +25,96 @@ const pickupCheckInFlight = new Map();
const pickupCheckLastRun = new Map();
const PICKUP_CHECK_DEDUP_MS = 30 * 1000;
const regularPickupCache = new Map();
+const REGULAR_PICKUP_CACHE_FILE = path.join(__dirname, '..', 'config', 'regular-pickup-cache.json');
+const REGULAR_PICKUP_CACHE_WRITE_DEBOUNCE_MS = 2000;
+let regularPickupCacheWriteTimer = null;
+let regularPickupCacheDirty = false;
const REGULAR_PICKUP_CACHE_TTL_MS = 12 * 60 * 60 * 1000;
+const REGULAR_PICKUP_ERROR_CACHE_TTL_MS = 5 * 60 * 1000;
+const REGULAR_PICKUP_TRANSIENT_ERROR_TTL_MS = 30 * 1000;
+const REGULAR_PICKUP_MAX_CONCURRENT = 3;
+const REGULAR_PICKUP_MIN_DELAY_MS = 150;
+const REGULAR_PICKUP_MAX_DELAY_MS = 350;
+const regularPickupInFlight = new Map();
+const regularPickupQueue = [];
+let regularPickupActive = 0;
+let regularPickupRefreshJob = null;
+const dormantWarningCooldowns = new Map();
+const DORMANT_WARNING_COOLDOWN_MS = 6 * 60 * 60 * 1000;
+
+function ensureRegularPickupCacheDir() {
+ const dir = path.dirname(REGULAR_PICKUP_CACHE_FILE);
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+}
+
+function persistRegularPickupCache() {
+ if (!regularPickupCacheDirty) {
+ return;
+ }
+ try {
+ ensureRegularPickupCacheDir();
+ const now = Date.now();
+ const entries = {};
+ regularPickupCache.forEach((value, key) => {
+ if (value?.expiresAt && value.expiresAt < now) {
+ return;
+ }
+ entries[key] = value;
+ });
+ fs.writeFileSync(
+ REGULAR_PICKUP_CACHE_FILE,
+ JSON.stringify({ version: 1, entries }, null, 2)
+ );
+ regularPickupCacheDirty = false;
+ } catch (error) {
+ console.warn('[PICKUP] Regular-Pickup-Cache konnte nicht geschrieben werden:', error.message);
+ }
+}
+
+function scheduleRegularPickupCachePersist() {
+ if (regularPickupCacheWriteTimer) {
+ return;
+ }
+ regularPickupCacheWriteTimer = setTimeout(() => {
+ regularPickupCacheWriteTimer = null;
+ persistRegularPickupCache();
+ }, REGULAR_PICKUP_CACHE_WRITE_DEBOUNCE_MS);
+}
+
+function markRegularPickupCacheDirty() {
+ regularPickupCacheDirty = true;
+ scheduleRegularPickupCachePersist();
+}
+
+function loadRegularPickupCacheFromDisk() {
+ try {
+ if (!fs.existsSync(REGULAR_PICKUP_CACHE_FILE)) {
+ return;
+ }
+ const raw = fs.readFileSync(REGULAR_PICKUP_CACHE_FILE, 'utf8');
+ const parsed = JSON.parse(raw);
+ const entries = parsed?.entries && typeof parsed.entries === 'object' ? parsed.entries : {};
+ const now = Date.now();
+ Object.entries(entries).forEach(([key, value]) => {
+ if (!value || typeof value !== 'object') {
+ return;
+ }
+ if (value.expiresAt && value.expiresAt < now) {
+ return;
+ }
+ regularPickupCache.set(String(key), {
+ fetchedAt: Number(value.fetchedAt) || Date.now(),
+ expiresAt: Number(value.expiresAt) || null,
+ rules: Array.isArray(value.rules) ? value.rules : [],
+ error: value.error || null
+ });
+ });
+ } catch (error) {
+ console.warn('[PICKUP] Regular-Pickup-Cache konnte nicht geladen werden:', error.message);
+ }
+}
const PICKUP_FALLBACK_RETRY_MS = 60 * 60 * 1000;
const TIME_ZONE = 'Europe/Berlin';
@@ -87,6 +178,12 @@ function randomDelayMs(minSeconds = 10, maxSeconds = 120) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
+function randomDelayMsBetween(minMs, maxMs) {
+ const min = Math.max(0, Number(minMs) || 0);
+ const max = Math.max(min, Number(maxMs) || 0);
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
function getTimeZoneParts(date, timeZone) {
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone,
@@ -250,6 +347,7 @@ function resolveSettings(settings) {
initialDelayMaxSeconds: Number.isFinite(settings.initialDelayMaxSeconds)
? settings.initialDelayMaxSeconds
: DEFAULT_SETTINGS.initialDelayMaxSeconds,
+ dormantMembershipCron: settings.dormantMembershipCron || DEFAULT_SETTINGS.dormantMembershipCron,
storeWatchCron: settings.storeWatchCron || DEFAULT_SETTINGS.storeWatchCron,
storeWatchInitialDelayMinSeconds: Number.isFinite(settings.storeWatchInitialDelayMinSeconds)
? settings.storeWatchInitialDelayMinSeconds
@@ -280,29 +378,121 @@ function resolveSettings(settings) {
};
}
-async function fetchRegularPickupSchedule(session, storeId) {
+function describeRegularPickupError(error) {
+ const status = error?.response?.status;
+ if (status) {
+ return `HTTP ${status}`;
+ }
+ const code = error?.code;
+ if (code) {
+ return code;
+ }
+ const message = error?.message;
+ if (message) {
+ return message;
+ }
+ return 'Unknown error';
+}
+
+function getRegularPickupErrorTtlMs(error) {
+ const status = error?.response?.status;
+ if (status === 403 || status === 404) {
+ return REGULAR_PICKUP_ERROR_CACHE_TTL_MS;
+ }
+ const code = error?.code;
+ if (code) {
+ return REGULAR_PICKUP_TRANSIENT_ERROR_TTL_MS;
+ }
+ return REGULAR_PICKUP_ERROR_CACHE_TTL_MS;
+}
+
+function runWithRegularPickupLimiter(task) {
+ if (regularPickupActive < REGULAR_PICKUP_MAX_CONCURRENT) {
+ regularPickupActive += 1;
+ return Promise.resolve()
+ .then(async () => {
+ const delayMs = randomDelayMsBetween(REGULAR_PICKUP_MIN_DELAY_MS, REGULAR_PICKUP_MAX_DELAY_MS);
+ if (delayMs > 0) {
+ await wait(delayMs);
+ }
+ return task();
+ })
+ .finally(() => {
+ regularPickupActive -= 1;
+ const next = regularPickupQueue.shift();
+ if (next) {
+ next();
+ }
+ });
+ }
+
+ return new Promise((resolve, reject) => {
+ regularPickupQueue.push(() => {
+ runWithRegularPickupLimiter(task).then(resolve).catch(reject);
+ });
+ });
+}
+
+function getRegularPickupCacheEntry(storeId) {
+ const entry = regularPickupCache.get(String(storeId));
+ if (!entry) {
+ return null;
+ }
+ if (entry.expiresAt && entry.expiresAt < Date.now()) {
+ regularPickupCache.delete(String(storeId));
+ markRegularPickupCacheDirty();
+ return null;
+ }
+ return entry;
+}
+
+function setRegularPickupCacheEntry(storeId, rules, ttlMs, error) {
+ regularPickupCache.set(String(storeId), {
+ fetchedAt: Date.now(),
+ expiresAt: Date.now() + ttlMs,
+ rules: Array.isArray(rules) ? rules : [],
+ error: error || null
+ });
+ markRegularPickupCacheDirty();
+}
+
+async function getRegularPickupSchedule(session, storeId) {
if (!session?.profile?.id || !storeId) {
- return [];
+ return { rules: [], error: 'missing-session-or-store', fromCache: false };
}
const key = String(storeId);
- const cached = regularPickupCache.get(key);
- if (cached && Date.now() - cached.fetchedAt <= REGULAR_PICKUP_CACHE_TTL_MS) {
- return cached.rules;
+ const cached = getRegularPickupCacheEntry(key);
+ if (cached) {
+ return { rules: cached.rules, error: cached.error, fromCache: true };
}
- try {
- const rules = await withSessionRetry(
- session,
- () => foodsharingClient.fetchRegularPickup(storeId, session.cookieHeader, session),
- { label: 'fetchRegularPickup' }
- );
- const normalized = Array.isArray(rules) ? rules : [];
- regularPickupCache.set(key, { fetchedAt: Date.now(), rules: normalized });
- return normalized;
- } catch (error) {
- if (cached?.rules) {
- return cached.rules;
+ const existing = regularPickupInFlight.get(key);
+ if (existing) {
+ return existing;
+ }
+ const fetchPromise = runWithRegularPickupLimiter(async () => {
+ try {
+ const rules = await withSessionRetry(
+ session,
+ () => foodsharingClient.fetchRegularPickup(storeId, session.cookieHeader, session),
+ { label: 'fetchRegularPickup' }
+ );
+ const normalized = Array.isArray(rules) ? rules : [];
+ setRegularPickupCacheEntry(key, normalized, REGULAR_PICKUP_CACHE_TTL_MS);
+ return { rules: normalized, error: null, fromCache: false };
+ } catch (error) {
+ const message = describeRegularPickupError(error);
+ const ttl = getRegularPickupErrorTtlMs(error);
+ setRegularPickupCacheEntry(key, [], ttl, message);
+ return { rules: [], error: message, fromCache: false };
+ }
+ });
+ regularPickupInFlight.set(key, fetchPromise);
+ try {
+ return await fetchPromise;
+ } finally {
+ if (regularPickupInFlight.get(key) === fetchPromise) {
+ regularPickupInFlight.delete(key);
}
- return [];
}
}
@@ -327,7 +517,7 @@ async function getNextPickupCheckTime(session, entry, settings) {
return null;
}
- const rules = await fetchRegularPickupSchedule(session, entry.id);
+ const { rules } = await getRegularPickupSchedule(session, entry.id);
if (!Array.isArray(rules) || rules.length === 0) {
return null;
}
@@ -518,6 +708,7 @@ async function handleExpiredDesiredWindow(session, entry) {
try {
await notificationService.sendDesiredWindowMissedNotification({
profileId,
+ profileName: session.profile?.name,
storeName,
desiredWindowLabel: desiredLabel
});
@@ -588,6 +779,7 @@ async function processBooking(session, entry, pickup) {
console.log(`[INFO] Slot gefunden (nur Hinweis) für ${storeName} am ${readableDate}`);
await notificationService.sendSlotNotification({
profileId: session.profile.id,
+ profileName: session.profile?.name,
storeName,
pickupDate: pickup.date,
onlyNotify: true,
@@ -618,6 +810,7 @@ async function processBooking(session, entry, pickup) {
console.log(`[SUCCESS] Slot gebucht für ${storeName} am ${readableDate}`);
await notificationService.sendSlotNotification({
profileId: session.profile.id,
+ profileName: session.profile?.name,
storeName,
pickupDate: pickup.date,
onlyNotify: false,
@@ -775,6 +968,7 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
if (status === 1 && watcher.lastTeamSearchStatus !== 1) {
await notificationService.sendStoreWatchNotification({
profileId: session.profile.id,
+ profileName: session.profile?.name,
storeName: watcher.storeName,
storeId: watcher.storeId,
regionName: watcher.regionName
@@ -824,6 +1018,7 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
try {
await notificationService.sendStoreWatchSummaryNotification({
profileId: session.profile.id,
+ profileName: session.profile?.name,
entries: summary,
triggeredBy: options.triggeredBy || 'manual'
});
@@ -868,6 +1063,51 @@ function scheduleStoreWatchers(sessionId, settings) {
return true;
}
+function scheduleRegularPickupRefresh(settings) {
+ if (regularPickupRefreshJob) {
+ regularPickupRefreshJob.stop();
+ regularPickupRefreshJob = null;
+ }
+ const cronExpression = settings.regularPickupRefreshCron || DEFAULT_SETTINGS.regularPickupRefreshCron;
+ if (!cronExpression) {
+ return null;
+ }
+ regularPickupRefreshJob = cron.schedule(
+ cronExpression,
+ async () => {
+ const sessions = sessionStore.list();
+ const storeSessionMap = new Map();
+ for (const session of sessions) {
+ if (!session?.profile?.id) {
+ continue;
+ }
+ const config = readConfig(session.profile.id);
+ const entries = Array.isArray(config) ? config : [];
+ const storeIds = Array.from(
+ new Set(entries.filter((entry) => entry?.id && !entry.hidden).map((entry) => String(entry.id)))
+ );
+ const sessionUpdatedAt = Number(session.updatedAt) || 0;
+ storeIds.forEach((storeId) => {
+ const existing = storeSessionMap.get(storeId);
+ if (!existing || (Number(existing.updatedAt) || 0) < sessionUpdatedAt) {
+ storeSessionMap.set(storeId, session);
+ }
+ });
+ }
+
+ for (const [storeId, session] of storeSessionMap.entries()) {
+ const ready = await ensureSession(session);
+ if (!ready) {
+ continue;
+ }
+ await getRegularPickupSchedule(session, storeId);
+ }
+ },
+ { timezone: TIME_ZONE }
+ );
+ return regularPickupRefreshJob;
+}
+
function scheduleFallbackPickupChecks(sessionId, settings) {
const cronExpression = settings.pickupFallbackCron || DEFAULT_SETTINGS.pickupFallbackCron;
if (!cronExpression) {
@@ -940,7 +1180,7 @@ function scheduleEntry(sessionId, entry, settings) {
function scheduleConfig(sessionId, config, settings) {
const resolvedSettings = resolveSettings(settings);
sessionStore.clearJobs(sessionId);
- scheduleDormantMembershipCheck(sessionId);
+ scheduleDormantMembershipCheck(sessionId, resolvedSettings);
const watchScheduled = scheduleStoreWatchers(sessionId, resolvedSettings);
scheduleFallbackPickupChecks(sessionId, resolvedSettings);
scheduleJournalReminders(sessionId);
@@ -1101,13 +1341,20 @@ async function checkDormantMembers(sessionId, options = {}) {
}
}
if (reasons.length > 0) {
+ const cooldownKey = `${profileId}:${storeId}`;
+ const lastSentAt = dormantWarningCooldowns.get(cooldownKey);
+ if (lastSentAt && Date.now() - lastSentAt < DORMANT_WARNING_COOLDOWN_MS) {
+ continue;
+ }
try {
await sendDormantPickupWarning({
profileId,
+ profileName: session.profile?.name,
storeName: target.storeName,
storeId,
reasonLines: reasons
});
+ dormantWarningCooldowns.set(cooldownKey, Date.now());
} catch (error) {
console.error(`[DORMANT] Warnung für Store ${storeId} konnte nicht versendet werden:`, error.message);
}
@@ -1122,8 +1369,9 @@ async function checkDormantMembers(sessionId, options = {}) {
}
}
-function scheduleDormantMembershipCheck(sessionId) {
- const cronExpression = '0 4 */14 * *';
+function scheduleDormantMembershipCheck(sessionId, settings) {
+ const cronExpression =
+ settings?.dormantMembershipCron || DEFAULT_SETTINGS.dormantMembershipCron || '0 4 */14 * *';
const job = cron.schedule(
cronExpression,
() => {
@@ -1165,6 +1413,7 @@ async function checkJournalReminders(sessionId) {
return;
}
const profileId = session.profile.id;
+ const profileName = session.profile?.name;
const entries = readJournal(profileId);
if (!Array.isArray(entries) || entries.length === 0) {
return;
@@ -1197,6 +1446,7 @@ async function checkJournalReminders(sessionId) {
}
await sendJournalReminderNotification({
profileId,
+ profileName,
storeName: entry.storeName || `Store ${entry.storeId || ''}`,
pickupDate: occurrence,
reminderDate,
@@ -1235,5 +1485,9 @@ module.exports = {
scheduleConfig,
runStoreWatchCheck,
runImmediatePickupCheck,
- runDormantMembershipCheck
+ runDormantMembershipCheck,
+ getRegularPickupSchedule,
+ scheduleRegularPickupRefresh
};
+
+loadRegularPickupCacheFromDisk();
diff --git a/src/App.js b/src/App.js
index d01e593..2f264f7 100644
--- a/src/App.js
+++ b/src/App.js
@@ -140,6 +140,7 @@ function App() {
setError,
setLoading,
setStores,
+ setRegularPickupMap,
setConfig,
normalizeConfigEntries,
setIsDirty,
@@ -552,45 +553,6 @@ function App() {
const visibleConfig = useMemo(() => config.filter((item) => !item.hidden), [config]);
- useEffect(() => {
- let cancelled = false;
- if (!session?.token || !authorizedFetch || visibleConfig.length === 0) {
- return () => {
- cancelled = true;
- };
- }
- const uniqueIds = Array.from(new Set(visibleConfig.map((item) => String(item.id))));
- const missing = uniqueIds.filter((id) => regularPickupMap[id] === undefined);
- if (missing.length === 0) {
- return () => {
- cancelled = true;
- };
- }
- const fetchSchedules = async () => {
- for (const id of missing) {
- try {
- const response = await authorizedFetch(`/api/stores/${id}/regular-pickup`);
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}`);
- }
- const data = await response.json();
- const rules = Array.isArray(data) ? data : Array.isArray(data?.rules) ? data.rules : [];
- if (!cancelled) {
- setRegularPickupMap((prev) => ({ ...prev, [id]: rules }));
- }
- } catch (err) {
- if (!cancelled) {
- setRegularPickupMap((prev) => ({ ...prev, [id]: [] }));
- }
- }
- }
- };
- fetchSchedules();
- return () => {
- cancelled = true;
- };
- }, [authorizedFetch, regularPickupMap, session?.token, visibleConfig]);
-
const activeRangeEntry = useMemo(() => {
if (!activeRangePicker) {
return null;
diff --git a/src/components/AdminSettingsPanel.js b/src/components/AdminSettingsPanel.js
index a7f1300..5957580 100644
--- a/src/components/AdminSettingsPanel.js
+++ b/src/components/AdminSettingsPanel.js
@@ -103,6 +103,32 @@ const AdminSettingsPanel = ({
/>
+
+ onSettingChange('regularPickupRefreshCron', event.target.value)}
+ className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
+ placeholder="z. B. 0 3 * * *"
+ />
+
+
+
+ onSettingChange('dormantMembershipCron', event.target.value)}
+ className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
+ placeholder="z. B. 0 4 */14 * *"
+ />
+
+
setStatus(''), 3000);
@@ -72,7 +80,7 @@ const useStoreSync = ({
setError(`Fehler beim Laden der Betriebe: ${err.message}`);
}
},
- [sessionToken, authorizedFetch, setStatus, setError, setStores]
+ [sessionToken, authorizedFetch, setStatus, setError, setStores, setRegularPickupMap]
);
const syncStoresWithProgress = useCallback(
diff --git a/src/utils/adminSettings.js b/src/utils/adminSettings.js
index bedcebb..285a085 100644
--- a/src/utils/adminSettings.js
+++ b/src/utils/adminSettings.js
@@ -8,6 +8,8 @@ export const normalizeAdminSettings = (raw) => {
pickupWindowOffsetsMinutes: Array.isArray(raw.pickupWindowOffsetsMinutes)
? raw.pickupWindowOffsetsMinutes.join(', ')
: '',
+ regularPickupRefreshCron: raw.regularPickupRefreshCron || '',
+ dormantMembershipCron: raw.dormantMembershipCron || '',
randomDelayMinSeconds: raw.randomDelayMinSeconds ?? '',
randomDelayMaxSeconds: raw.randomDelayMaxSeconds ?? '',
initialDelayMinSeconds: raw.initialDelayMinSeconds ?? '',
@@ -74,6 +76,8 @@ export const serializeAdminSettings = (adminSettings) => {
scheduleCron: adminSettings.scheduleCron,
pickupFallbackCron: adminSettings.pickupFallbackCron,
pickupWindowOffsetsMinutes: toNumberArray(adminSettings.pickupWindowOffsetsMinutes),
+ regularPickupRefreshCron: adminSettings.regularPickupRefreshCron,
+ dormantMembershipCron: adminSettings.dormantMembershipCron,
randomDelayMinSeconds: toNumberOrUndefined(adminSettings.randomDelayMinSeconds),
randomDelayMaxSeconds: toNumberOrUndefined(adminSettings.randomDelayMaxSeconds),
initialDelayMinSeconds: toNumberOrUndefined(adminSettings.initialDelayMinSeconds),