Skip to content

Commit cef6a5c

Browse files
committed
feat: implement background cache updater for RSVP count with improved error handling
1 parent 5eef35f commit cef6a5c

File tree

1 file changed

+33
-21
lines changed

1 file changed

+33
-21
lines changed

src/pages/api/rsvp.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,78 @@
11
// src/pages/api/rsvp.ts
22
import { 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)
3743
declare 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
5360
if (!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+
7886
export 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

Comments
 (0)