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),