From 7833dca29c6ced46c9a67187b222aea8b302374b Mon Sep 17 00:00:00 2001 From: Matthias Hochmeister Date: Fri, 13 Mar 2026 15:04:40 +0100 Subject: [PATCH] update --- backend/src/jobs/notification-generation.job.ts | 13 ++++++++++--- backend/src/services/bookstack.service.ts | 13 +++++++++++-- frontend/src/pages/MitgliedDetail.tsx | 6 ++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/backend/src/jobs/notification-generation.job.ts b/backend/src/jobs/notification-generation.job.ts index bcf01ba..51f58b7 100644 --- a/backend/src/jobs/notification-generation.job.ts +++ b/backend/src/jobs/notification-generation.job.ts @@ -17,7 +17,9 @@ import nextcloudService from '../services/nextcloud.service'; import logger from '../utils/logger'; const INTERVAL_MS = 15 * 60 * 1000; // 15 minutes -const NEXTCLOUD_INTERVAL_MS = 2 * 60 * 1000; // 2 minutes +const NEXTCLOUD_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes — keep reasonable to avoid rate-limiting +const NEXTCLOUD_BATCH_DELAY_MS = 300; // ms between user batches to avoid hammering Nextcloud +const STARTUP_DELAY_MS = 30 * 1000; // 30 seconds — avoid burst on container restart const ATEMSCHUTZ_THRESHOLD = 60; // days let jobInterval: ReturnType | null = null; @@ -267,6 +269,9 @@ async function generateNextcloudTalkNotifications(): Promise { const users = usersResult.rows; for (let i = 0; i < users.length; i += NEXTCLOUD_BATCH_SIZE) { + if (i > 0) { + await new Promise((r) => setTimeout(r, NEXTCLOUD_BATCH_DELAY_MS)); + } const batch = users.slice(i, i + NEXTCLOUD_BATCH_SIZE); await Promise.allSettled(batch.map((user) => processNextcloudUser(user))); } @@ -318,9 +323,11 @@ export function startNotificationJob(): void { return; } - // Run both once on startup, then repeat on separate intervals. + // Run main job once on startup, then repeat. runNotificationGeneration(); - runNextcloudNotificationGeneration(); + + // Delay initial Nextcloud run to avoid a burst on container restart. + setTimeout(() => runNextcloudNotificationGeneration(), STARTUP_DELAY_MS); jobInterval = setInterval(() => { runNotificationGeneration(); diff --git a/backend/src/services/bookstack.service.ts b/backend/src/services/bookstack.service.ts index 9ec8ede..7249166 100644 --- a/backend/src/services/bookstack.service.ts +++ b/backend/src/services/bookstack.service.ts @@ -86,8 +86,15 @@ function buildHeaders(): Record { * Fetches all BookStack books and returns a map of book_id → book_slug. * The /api/pages list endpoint does not reliably include book_slug, so we * look it up separately and use it when constructing page URLs. + * Cached for 5 minutes to avoid hammering the API on every dashboard load. */ +let bookSlugMapCache: { map: Map; expiresAt: number } | null = null; +const BOOK_SLUG_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes + async function getBookSlugMap(): Promise> { + if (bookSlugMapCache && Date.now() < bookSlugMapCache.expiresAt) { + return bookSlugMapCache.map; + } const { bookstack } = environment; try { const response = await httpClient.get( @@ -95,9 +102,11 @@ async function getBookSlugMap(): Promise> { { params: { count: 500 }, headers: buildHeaders() }, ); const books: Array<{ id: number; slug: string }> = response.data?.data ?? []; - return new Map(books.map((b) => [b.id, b.slug])); + const map = new Map(books.map((b) => [b.id, b.slug])); + bookSlugMapCache = { map, expiresAt: Date.now() + BOOK_SLUG_CACHE_TTL_MS }; + return map; } catch { - return new Map(); + return bookSlugMapCache?.map ?? new Map(); } } diff --git a/frontend/src/pages/MitgliedDetail.tsx b/frontend/src/pages/MitgliedDetail.tsx index dd0785a..61bd1f4 100644 --- a/frontend/src/pages/MitgliedDetail.tsx +++ b/frontend/src/pages/MitgliedDetail.tsx @@ -435,7 +435,7 @@ function MitgliedDetail() { {/* Edit controls */} - {canEdit && ( + {canEdit && (canWrite || !!profile) && ( {editMode ? ( @@ -475,7 +475,9 @@ function MitgliedDetail() { {!profile && ( Für dieses Mitglied wurde noch kein Profil angelegt. - {canWrite && ' Ein Kommandant kann das Profil unter "Stammdaten" erstellen.'} + {canWrite + ? ' Ein Kommandant kann das Profil unter "Stammdaten" erstellen.' + : ' Wende dich an einen Administrator, um dein Profil anlegen zu lassen.'} )}