Add Redis/Valkey caching with sessionStorage client-side cache and improved asset loading

This commit is contained in:
2025-12-28 00:02:45 +00:00
parent d231a0b6ec
commit c65a2000ac
5 changed files with 316 additions and 36 deletions
+123 -3
View File
@@ -3,6 +3,7 @@ import { serveStatic } from 'hono/bun';
import fs from 'fs';
// @ts-ignore Bun project may not have dotenv types configured
import { config } from 'dotenv';
import { getCache, setCache, isRedisConnected } from './redis-cache';
config();
@@ -29,6 +30,119 @@ app.get('/version', (c) => {
}
});
// Jobs endpoint with Redis caching
app.get('/api/jobs', async (c) => {
const page = parseInt(c.req.query('page') || '1');
const perPage = parseInt(c.req.query('perPage') || '50');
const sort = c.req.query('sort') || '-Job_Number';
const cacheKey = `jobs:page:${page}:perPage:${perPage}:sort:${sort}`;
const ttl = 300; // 5 minutes cache
try {
// Try to get from cache first
if (isRedisConnected()) {
const cached = await getCache(cacheKey);
if (cached) {
logLine('logs/server.log', `Cache HIT for ${cacheKey}`);
return c.json(cached);
}
logLine('logs/server.log', `Cache MISS for ${cacheKey}`);
}
// Fetch from PocketBase
const pbUrl = `https://pocketbase.ccllc.pro/api/collections/pbc_1407612689/records?page=${page}&perPage=${perPage}&sort=${sort}`;
const authHeader = c.req.header('Authorization') || '';
const response = await fetch(pbUrl, {
headers: authHeader ? { Authorization: authHeader } : {}
});
if (!response.ok) {
throw new Error(`PocketBase returned ${response.status}`);
}
const data = await response.json();
// Cache the result
if (isRedisConnected()) {
await setCache(cacheKey, data, ttl);
logLine('logs/server.log', `Cached ${cacheKey} for ${ttl}s`);
}
return c.json(data);
} catch (err) {
logLine('logs/error.log', `jobs endpoint error: ${(err as Error)?.message || String(err)}`);
return c.json({ error: 'Failed to fetch jobs' }, 500);
}
});
// 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
try {
// Try to get from cache first
if (isRedisConnected()) {
const cached = await getCache(cacheKey);
if (cached) {
logLine('logs/server.log', `Cache HIT for ${cacheKey} (${cached.items?.length || 0} jobs)`);
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
};
// Cache the result
if (isRedisConnected()) {
await setCache(cacheKey, result, ttl);
logLine('logs/server.log', `Cached ${cacheKey} with ${allItems.length} jobs for ${ttl}s`);
}
return c.json(result);
} catch (err) {
logLine('logs/error.log', `jobs-all endpoint error: ${(err as Error)?.message || String(err)}`);
return c.json({ error: 'Failed to fetch jobs' }, 500);
}
});
// --- Microsoft Graph helpers ---
const getGraphToken = (c?: any) => {
const headerToken = c?.req?.header ? c.req.header('x-graph-token') : '';
@@ -263,6 +377,12 @@ app.post('/log-change', async (c) => {
});
// Serve static files from the frontend directory (must come after API routes)
// Add cache headers for static assets
app.use('/assets/*', async (c, next) => {
await next();
// Cache assets for 1 year
c.header('Cache-Control', 'public, max-age=31536000, immutable');
});
app.use('/*', serveStatic({ root: './frontend' }));
// Error handler
@@ -271,10 +391,10 @@ app.onError((err, c) => {
return c.text('Internal Server Error', 500);
});
// Default to 3003; can be overridden by Environment=PORT
const PORT = Number(process.env.PORT || 3003);
// Default to 3004 for Redis branch; can be overridden by Environment=PORT
const PORT = Number(process.env.PORT || 3004);
logLine('logs/server.log', `Starting frontend server on port ${PORT}`);
logLine('logs/server.log', `Starting frontend server (Redis version) on port ${PORT}`);
// Graceful shutdown logging
const shutdown = (signal: string) => {