From 4aaffe99c79a44dbcd2743134dbcc8971bd6afea Mon Sep 17 00:00:00 2001 From: Michilis Date: Mon, 16 Feb 2026 22:42:55 +0000 Subject: [PATCH] Fix stale featured event on homepage: revalidate cache when featured event changes - Extract revalidateFrontendCache() to backend/src/lib/revalidate.ts - Call revalidation from site-settings when featuredEventId is set/cleared - Ensures homepage shows updated featured event after admin changes Co-authored-by: Cursor --- backend/src/lib/revalidate.ts | 22 ++++++++++++++++++++++ backend/src/routes/events.ts | 24 +----------------------- backend/src/routes/site-settings.ts | 12 ++++++++++++ 3 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 backend/src/lib/revalidate.ts diff --git a/backend/src/lib/revalidate.ts b/backend/src/lib/revalidate.ts new file mode 100644 index 0000000..5c7421f --- /dev/null +++ b/backend/src/lib/revalidate.ts @@ -0,0 +1,22 @@ +// Trigger frontend cache revalidation (fire-and-forget) +// Revalidates both the sitemap and the next-event data (homepage, llms.txt) +export function revalidateFrontendCache() { + const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3002'; + const secret = process.env.REVALIDATE_SECRET; + if (!secret) { + console.warn('REVALIDATE_SECRET not set, skipping frontend revalidation'); + return; + } + fetch(`${frontendUrl}/api/revalidate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ secret, tag: ['events-sitemap', 'next-event'] }), + }) + .then((res) => { + if (!res.ok) console.error('Frontend revalidation failed:', res.status); + else console.log('Frontend revalidation triggered (sitemap + next-event)'); + }) + .catch((err) => { + console.error('Frontend revalidation error:', err.message); + }); +} diff --git a/backend/src/routes/events.ts b/backend/src/routes/events.ts index b2ca8c6..4a1c1a5 100644 --- a/backend/src/routes/events.ts +++ b/backend/src/routes/events.ts @@ -5,6 +5,7 @@ import { db, dbGet, dbAll, events, tickets, payments, eventPaymentOverrides, ema import { eq, desc, and, gte, sql } from 'drizzle-orm'; import { requireAuth, getAuthUser } from '../lib/auth.js'; import { generateId, getNow, convertBooleansForDb, toDbDate, calculateAvailableSeats } from '../lib/utils.js'; +import { revalidateFrontendCache } from '../lib/revalidate.js'; interface UserContext { id: string; @@ -15,29 +16,6 @@ interface UserContext { const eventsRouter = new Hono<{ Variables: { user: UserContext } }>(); -// Trigger frontend cache revalidation (fire-and-forget) -// Revalidates both the sitemap and the next-event data (homepage, llms.txt) -function revalidateFrontendCache() { - const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3002'; - const secret = process.env.REVALIDATE_SECRET; - if (!secret) { - console.warn('REVALIDATE_SECRET not set, skipping frontend revalidation'); - return; - } - fetch(`${frontendUrl}/api/revalidate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ secret, tag: ['events-sitemap', 'next-event'] }), - }) - .then((res) => { - if (!res.ok) console.error('Frontend revalidation failed:', res.status); - else console.log('Frontend revalidation triggered (sitemap + next-event)'); - }) - .catch((err) => { - console.error('Frontend revalidation error:', err.message); - }); -} - // Helper to normalize event data for API response // PostgreSQL decimal returns strings, booleans are stored as integers function normalizeEvent(event: any) { diff --git a/backend/src/routes/site-settings.ts b/backend/src/routes/site-settings.ts index 2421ad5..db35dc6 100644 --- a/backend/src/routes/site-settings.ts +++ b/backend/src/routes/site-settings.ts @@ -5,6 +5,7 @@ import { db, dbGet, siteSettings, events } from '../db/index.js'; import { eq, and, gte } from 'drizzle-orm'; import { requireAuth } from '../lib/auth.js'; import { generateId, getNow, toDbBool } from '../lib/utils.js'; +import { revalidateFrontendCache } from '../lib/revalidate.js'; interface UserContext { id: string; @@ -172,6 +173,11 @@ siteSettingsRouter.put('/', requireAuth(['admin']), zValidator('json', updateSit (db as any).select().from(siteSettings).where(eq((siteSettings as any).id, existing.id)) ); + // Revalidate frontend cache if featured event changed + if (data.featuredEventId !== undefined) { + revalidateFrontendCache(); + } + return c.json({ settings: updated, message: 'Settings updated successfully' }); }); @@ -216,6 +222,9 @@ siteSettingsRouter.put('/featured-event', requireAuth(['admin']), zValidator('js await (db as any).insert(siteSettings).values(newSettings); + // Revalidate frontend cache so homepage shows the updated featured event + revalidateFrontendCache(); + return c.json({ featuredEventId: eventId, message: eventId ? 'Event set as featured' : 'Featured event removed' }); } @@ -229,6 +238,9 @@ siteSettingsRouter.put('/featured-event', requireAuth(['admin']), zValidator('js }) .where(eq((siteSettings as any).id, existing.id)); + // Revalidate frontend cache so homepage shows the updated featured event + revalidateFrontendCache(); + return c.json({ featuredEventId: eventId, message: eventId ? 'Event set as featured' : 'Featured event removed' }); });