From ccdfeb782d1fdb9d9c5e71e9f6fa716d1895dbbd Mon Sep 17 00:00:00 2001 From: aewing Date: Sun, 28 Dec 2025 06:19:55 +0000 Subject: [PATCH] redis caching updated optimized loading and data readiness --- backend/server.ts | 122 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 36 deletions(-) diff --git a/backend/server.ts b/backend/server.ts index 90a7978..a42818e 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -81,57 +81,36 @@ app.get('/api/jobs', async (c) => { // Fast endpoint: returns all jobs in a single cached request app.get('/api/jobs-all', async (c) => { const cacheKey = 'jobs:all'; - const ttl = 300; // 5 minutes cache + const ttl = 1800; // 30 minutes cache try { - // Try to get from cache first + // Always try cache first for instant response if (isRedisConnected()) { const cached = await getCache(cacheKey); if (cached) { logLine('logs/server.log', `Cache HIT for ${cacheKey} (${cached.items?.length || 0} jobs)`); + + // Return cached data immediately, refresh in background if needed + setImmediate(async () => { + try { + await refreshJobsCache(cacheKey, ttl, c.req.header('Authorization') || ''); + } catch (err) { + // Silently fail background refresh + } + }); + return c.json(cached); } logLine('logs/server.log', `Cache MISS for ${cacheKey} - fetching all jobs`); } - const authHeader = c.req.header('Authorization') || ''; - const allItems: any[] = []; - let page = 1; - const perPage = 500; // Large page size - - // Fetch all pages from PocketBase - while (true) { - const pbUrl = `https://pocketbase.ccllc.pro/api/collections/pbc_1407612689/records?page=${page}&perPage=${perPage}&sort=-Job_Number`; - const response = await fetch(pbUrl, { - headers: authHeader ? { Authorization: authHeader } : {} - }); - - if (!response.ok) { - throw new Error(`PocketBase returned ${response.status}`); - } - - const data = await response.json(); - const items = data.items || []; - - if (items.length === 0) break; - allItems.push(...items); - - if (allItems.length >= (data.totalItems || 0)) break; - page++; - } - - const result = { - page: 1, - perPage: allItems.length, - totalItems: allItems.length, - totalPages: 1, - items: allItems - }; + // No cache available, fetch now + const result = await fetchAllJobsFromPocketBase(c.req.header('Authorization') || ''); // Cache the result if (isRedisConnected()) { await setCache(cacheKey, result, ttl); - logLine('logs/server.log', `Cached ${cacheKey} with ${allItems.length} jobs for ${ttl}s`); + logLine('logs/server.log', `Cached ${cacheKey} with ${result.items.length} jobs for ${ttl}s`); } return c.json(result); @@ -142,6 +121,52 @@ app.get('/api/jobs-all', async (c) => { } }); +// Helper function to fetch all jobs from PocketBase +async function fetchAllJobsFromPocketBase(authHeader: string): Promise { + const allItems: any[] = []; + let page = 1; + const perPage = 500; + + while (true) { + const pbUrl = `https://pocketbase.ccllc.pro/api/collections/pbc_1407612689/records?page=${page}&perPage=${perPage}&sort=-Job_Number`; + const response = await fetch(pbUrl, { + headers: authHeader ? { Authorization: authHeader } : {} + }); + + if (!response.ok) { + throw new Error(`PocketBase returned ${response.status}`); + } + + const data = await response.json(); + const items = data.items || []; + + if (items.length === 0) break; + allItems.push(...items); + + if (allItems.length >= (data.totalItems || 0)) break; + page++; + } + + return { + page: 1, + perPage: allItems.length, + totalItems: allItems.length, + totalPages: 1, + items: allItems + }; +} + +// Helper function to refresh jobs cache in background +async function refreshJobsCache(cacheKey: string, ttl: number, authHeader: string): Promise { + try { + const result = await fetchAllJobsFromPocketBase(authHeader); + await setCache(cacheKey, result, ttl); + logLine('logs/server.log', `Background refresh: cached ${cacheKey} with ${result.items.length} jobs`); + } catch (err) { + logLine('logs/error.log', `Background refresh error: ${(err as Error)?.message || String(err)}`); + } +} + // --- Microsoft Graph helpers --- const getGraphToken = (c?: any) => { @@ -448,6 +473,31 @@ const PORT = Number(process.env.PORT || 3004); logLine('logs/server.log', `Starting frontend server (Redis version) on port ${PORT}`); +// Warm up cache on startup +(async () => { + try { + if (isRedisConnected()) { + logLine('logs/server.log', 'Warming up jobs cache...'); + const result = await fetchAllJobsFromPocketBase(''); + await setCache('jobs:all', result, 1800); + logLine('logs/server.log', `✓ Cache warmed with ${result.items.length} jobs`); + } + } catch (err) { + logLine('logs/error.log', `Cache warmup failed: ${(err as Error)?.message || String(err)}`); + } +})(); + +// Background refresh every 5 minutes to keep cache hot +setInterval(async () => { + try { + if (isRedisConnected()) { + await refreshJobsCache('jobs:all', 1800, ''); + } + } catch (err) { + logLine('logs/error.log', `Periodic refresh error: ${(err as Error)?.message || String(err)}`); + } +}, 5 * 60 * 1000); // Every 5 minutes + // Graceful shutdown logging const shutdown = (signal: string) => { logLine('logs/server.log', `Shutting down due to ${signal}`);