Compare commits
5 Commits
194cbd6ca8
...
1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 15655e3987 | |||
|
|
5263fa6834 | ||
|
|
923c86a3b3 | ||
| d8b3864411 | |||
|
|
4aaffe99c7 |
@@ -19,7 +19,7 @@ GOOGLE_CLIENT_ID=
|
||||
# Server Configuration
|
||||
PORT=3001
|
||||
API_URL=http://localhost:3001
|
||||
FRONTEND_URL=http://localhost:3002
|
||||
FRONTEND_URL=http://localhost:3019
|
||||
|
||||
# Revalidation secret (shared with frontend for on-demand cache revalidation)
|
||||
# Must match the REVALIDATE_SECRET in frontend/.env
|
||||
|
||||
22
backend/src/lib/revalidate.ts
Normal file
22
backend/src/lib/revalidate.ts
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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' });
|
||||
});
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ Type=simple
|
||||
User=spanglish
|
||||
Group=spanglish
|
||||
WorkingDirectory=/home/spanglish/Spanglish/backend
|
||||
EnvironmentFile=/home/spanglish/Spanglish/backend/.env
|
||||
Environment=NODE_ENV=production
|
||||
Environment=PORT=3018
|
||||
EnvironmentFile=/home/spanglish/Spanglish/backend/.env
|
||||
ExecStart=/usr/bin/node dist/index.js
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
@@ -28,7 +28,7 @@ interface LlmsEvent {
|
||||
async function getNextUpcomingEvent(): Promise<LlmsEvent | null> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/events/next/upcoming`, {
|
||||
next: { tags: ['next-event'] },
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
@@ -41,7 +41,7 @@ async function getNextUpcomingEvent(): Promise<LlmsEvent | null> {
|
||||
async function getUpcomingEvents(): Promise<LlmsEvent[]> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/events?status=published&upcoming=true`, {
|
||||
next: { tags: ['next-event'] },
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return [];
|
||||
const data = await response.json();
|
||||
@@ -115,7 +115,7 @@ function getEventStatus(event: LlmsEvent): string {
|
||||
async function getHomepageFaqs(): Promise<LlmsFaq[]> {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/faq?homepage=true`, {
|
||||
next: { revalidate: 3600 },
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return [];
|
||||
const data = await response.json();
|
||||
@@ -128,6 +128,8 @@ async function getHomepageFaqs(): Promise<LlmsFaq[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET() {
|
||||
const [nextEvent, upcomingEvents, faqs] = await Promise.all([
|
||||
getNextUpcomingEvent(),
|
||||
|
||||
Reference in New Issue
Block a user