11// src/pages/api/rsvp.ts
22import { NextApiRequest , NextApiResponse } from "next" ;
33
4- async function getCount ( ) {
5- let offset ;
4+ // Rewritten to ensure a single background updater reloads the cached count every 30s.
5+
6+ async function getCount ( ) : Promise < number > {
7+ let offset : string | undefined ;
68 let count = 0 ;
79
810 do {
911 const url = new URL ( `https://api.airtable.com/v0/${ process . env . RSVP_AIRTABLE_BASE_ID } /RSVPs` ) ;
1012 if ( offset ) url . searchParams . set ( "offset" , offset ) ;
1113
12- const res = await fetch ( url , {
14+ const res = await fetch ( url . toString ( ) , {
1315 headers : {
1416 Authorization : `Bearer ${ process . env . RSVP_AIRTABLE_API_KEY } ` ,
1517 } ,
1618 } ) ;
19+
1720 if ( ! res . ok ) {
18- console . error ( res . statusText ) ;
19- throw new Error ( res . statusText ) ;
21+ const text = await res . text ( ) . catch ( ( ) => res . statusText ) ;
22+ console . error ( "Airtable request failed:" , res . status , text ) ;
23+ throw new Error ( text || "Airtable request failed" ) ;
2024 }
25+
2126 const data = await res . json ( ) ;
22- count += data . records ?. length ?? 0 ;
27+ count += ( data . records ?. length ?? 0 ) ;
2328 offset = data . offset ;
2429
25- if ( offset ) await new Promise ( ( resolve ) => setTimeout ( resolve , 200 ) ) ;
30+ if ( offset ) await new Promise ( ( r ) => setTimeout ( r , 200 ) ) ;
2631 } while ( offset ) ;
2732
2833 return count ;
2934}
3035
31- let cached = { value : - 1 , updated : 0 } ;
36+ type Cache = { value : number | null ; updated : number } ;
37+ let cached : Cache = { value : null , updated : 0 } ;
3238
33- // Cache duration in milliseconds
34- const CACHE_DURATION = 30000 ;
39+ // Default cache duration 30 seconds; can be overridden with env var (ms)
40+ const CACHE_DURATION_MS = Number ( process . env . RSVP_CACHE_DURATION_MS ) || 30_000 ;
3541
3642// Extend global to hold a single timer across module reloads (avoid duplicate timers in dev)
3743declare global {
44+
3845 var __RSVP_CACHE_TIMER__ : NodeJS . Timeout | undefined ;
3946}
4047
41- async function updateCache ( ) {
48+ async function updateCache ( ) : Promise < void > {
4249 try {
4350 const count = await getCount ( ) ;
4451 cached = { value : count , updated : Date . now ( ) } ;
45- console . log ( "cached value ", cached . value , "updatedAt" , new Date ( cached . updated ) . toISOString ( ) ) ;
52+ console . info ( "[rsvp] cache updated: ", cached . value , new Date ( cached . updated ) . toISOString ( ) ) ;
4653 } catch ( err : unknown ) {
47- console . error ( "updateCache failed:" , err ) ;
48- // keep previous cached value
54+ console . error ( "[rsvp] updateCache failed:" , err ) ;
55+ // keep previous cached value if any
4956 }
5057}
5158
5259// Start background updater once per server instance
5360if ( ! global . __RSVP_CACHE_TIMER__ ) {
5461 // initial immediate update (fire-and-forget)
55- updateCache ( ) . catch ( ( e ) => console . error ( "initial update failed:" , e ) ) ;
62+ updateCache ( ) . catch ( ( e ) => console . error ( "[rsvp] initial update failed:" , e ) ) ;
5663
5764 // schedule periodic updates
5865 global . __RSVP_CACHE_TIMER__ = setInterval ( ( ) => {
59- updateCache ( ) . catch ( ( e ) => console . error ( "scheduled update failed:" , e ) ) ;
60- } , CACHE_DURATION ) ;
66+ updateCache ( ) . catch ( ( e ) => console . error ( "[rsvp] scheduled update failed:" , e ) ) ;
67+ } , CACHE_DURATION_MS ) ;
6168}
6269
6370// Cleanup function to clear the background timer
64- export function shutdownRSVPTimer ( ) {
71+ export function shutdownRSVPTimer ( ) : void {
6572 if ( global . __RSVP_CACHE_TIMER__ ) {
6673 clearInterval ( global . __RSVP_CACHE_TIMER__ ) ;
6774 global . __RSVP_CACHE_TIMER__ = undefined ;
68- console . log ( " RSVP cache timer cleared.") ;
75+ console . info ( "[rsvp] RSVP cache timer cleared.") ;
6976 }
7077}
7178
@@ -75,10 +82,15 @@ if (typeof process !== "undefined" && process.on) {
7582 process . on ( "SIGINT" , shutdownRSVPTimer ) ;
7683 process . on ( "exit" , shutdownRSVPTimer ) ;
7784}
85+
7886export default function handler ( _req : NextApiRequest , res : NextApiResponse ) {
79- if ( cached . value === - 1 ) {
87+ if ( cached . value === null ) {
8088 res . status ( 503 ) . json ( { error : "Service Unavailable: RSVP count not yet available." } ) ;
8189 return ;
8290 }
83- res . status ( 200 ) . json ( { count : cached . value , updatedAt : cached . updated } ) ;
91+
92+ res . status ( 200 ) . json ( {
93+ count : cached . value ,
94+ updatedAt : new Date ( cached . updated ) . toISOString ( ) ,
95+ } ) ;
8496}
0 commit comments