Skip to content

Commit c8ba0f0

Browse files
committed
Implement in-memory cache for GitHub feed
Adds a simple in-memory cache to the GitHub feed component to reduce API calls and improve performance. The cache is stored globally and has a TTL of 5 minutes.
1 parent 36ae8fb commit c8ba0f0

1 file changed

Lines changed: 88 additions & 57 deletions

File tree

src/components/GithubFeed.astro

Lines changed: 88 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ const octokit = new Octokit({
2020
2121
const detailCache = new Map<string, { title: string; body: string; merged?: boolean; url: string }>();
2222
23+
// Use globalThis to persist cache across requests
24+
const globalCache = globalThis as any;
25+
if (!globalCache.__githubEventsCache) {
26+
globalCache.__githubEventsCache = {
27+
data: null as DisplayEvent[] | null,
28+
timestamp: 0,
29+
TTL: 5 * 60 * 1000 // 5 minutes in milliseconds
30+
};
31+
}
32+
const eventsCache = globalCache.__githubEventsCache;
33+
2334
const cleanDescription = (text: string | null | undefined) => (text ? text.replace(/[#*`_]/g, "").trim() : "");
2435
2536
const formatDate = (iso: string) =>
@@ -47,66 +58,86 @@ async function fetchDetails(apiUrl: string) {
4758
}
4859
}
4960
50-
let events: DisplayEvent[] = [];
51-
52-
try {
53-
const res = await octokit.rest.activity.listPublicEventsForUser({
54-
username: "dsnsgithub",
55-
per_page: 30,
56-
headers: { "X-GitHub-Api-Version": "2022-11-28" }
57-
});
58-
59-
const allowedActions = ["opened", "closed", "reopened"];
60-
const ignoredTypes = ["PushEvent", "IssueCommentEvent", "PullRequestReviewCommentEvent", "CommitCommentEvent"];
61-
const rawEvents = res.data.filter((e) => !ignoredTypes.includes(e.type!));
62-
63-
const processedPRs = new Set<string>();
64-
65-
for (const event of rawEvents) {
66-
const { payload, actor, repo, created_at, id, type } = event;
67-
if (!created_at || !id) continue;
61+
async function fetchGitHubEvents(): Promise<DisplayEvent[]> {
62+
const events: DisplayEvent[] = [];
6863
69-
const base = {
70-
id,
71-
actor: { login: actor.login, avatar_url: actor.avatar_url },
72-
repo: repo.name,
73-
timestamp: formatDate(created_at)
74-
};
75-
76-
if (type === "PullRequestEvent" || type === "PullRequestReviewEvent") {
77-
const pr = (payload as any).pull_request;
78-
const prKey = `${repo.name}#${pr.number}`;
79-
if (!pr || processedPRs.has(prKey)) continue;
80-
81-
let verb = "";
82-
if (type === "PullRequestEvent" && (payload as any).action === "closed" && pr.merged) {
83-
verb = "merged";
84-
} else if (type === "PullRequestReviewEvent") {
85-
const state = (payload as any).review.state?.toLowerCase();
86-
if (state === "commented") continue;
87-
verb = state.replace("_", " ");
88-
} else if (type === "PullRequestEvent" && allowedActions.includes((payload as any).action)) {
89-
verb = (payload as any).action;
90-
} else continue;
91-
92-
const details = await fetchDetails(pr.url);
93-
processedPRs.add(prKey);
94-
events.push({ ...base, verb: verb + " pull request", object: `${details.title} (#${pr.number})`, description: details.body, url: details.url });
95-
} else if (type === "IssuesEvent") {
96-
const issue = (payload as any).issue;
97-
if (!issue) continue;
98-
const details = await fetchDetails(issue.url);
99-
events.push({ ...base, verb: (payload as any).action + " issue", object: `${details.title} (#${issue.number})`, description: details.body, url: details.url });
100-
} else if (type === "WatchEvent") {
101-
events.push({ ...base, verb: "starred", object: repo.name, url: `https://github.com/${repo.name}` });
102-
} else if (type === "ForkEvent") {
103-
const forkee = (payload as any).forkee;
104-
if (!forkee) continue;
105-
events.push({ ...base, verb: "forked", object: repo.name, description: `Original repository created by ${forkee.full_name}`, url: forkee.html_url });
64+
try {
65+
const res = await octokit.rest.activity.listPublicEventsForUser({
66+
username: "dsnsgithub",
67+
per_page: 30,
68+
headers: { "X-GitHub-Api-Version": "2022-11-28" }
69+
});
70+
71+
const allowedActions = ["opened", "closed", "reopened"];
72+
const ignoredTypes = ["PushEvent", "IssueCommentEvent", "PullRequestReviewCommentEvent", "CommitCommentEvent"];
73+
const rawEvents = res.data.filter((e) => !ignoredTypes.includes(e.type!));
74+
75+
const processedPRs = new Set<string>();
76+
77+
for (const event of rawEvents) {
78+
const { payload, actor, repo, created_at, id, type } = event;
79+
if (!created_at || !id) continue;
80+
81+
const base = {
82+
id,
83+
actor: { login: actor.login, avatar_url: actor.avatar_url },
84+
repo: repo.name,
85+
timestamp: formatDate(created_at)
86+
};
87+
88+
if (type === "PullRequestEvent" || type === "PullRequestReviewEvent") {
89+
const pr = (payload as any).pull_request;
90+
const prKey = `${repo.name}#${pr.number}`;
91+
if (!pr || processedPRs.has(prKey)) continue;
92+
93+
let verb = "";
94+
if (type === "PullRequestEvent" && (payload as any).action === "closed" && pr.merged) {
95+
verb = "merged";
96+
} else if (type === "PullRequestReviewEvent") {
97+
const state = (payload as any).review.state?.toLowerCase();
98+
if (state === "commented") continue;
99+
verb = state.replace("_", " ");
100+
} else if (type === "PullRequestEvent" && allowedActions.includes((payload as any).action)) {
101+
verb = (payload as any).action;
102+
} else continue;
103+
104+
const details = await fetchDetails(pr.url);
105+
processedPRs.add(prKey);
106+
events.push({ ...base, verb: verb + " pull request", object: `${details.title} (#${pr.number})`, description: details.body, url: details.url });
107+
} else if (type === "IssuesEvent") {
108+
const issue = (payload as any).issue;
109+
if (!issue) continue;
110+
const details = await fetchDetails(issue.url);
111+
events.push({ ...base, verb: (payload as any).action + " issue", object: `${details.title} (#${issue.number})`, description: details.body, url: details.url });
112+
} else if (type === "WatchEvent") {
113+
events.push({ ...base, verb: "starred", object: repo.name, url: `https://github.com/${repo.name}` });
114+
} else if (type === "ForkEvent") {
115+
const forkee = (payload as any).forkee;
116+
if (!forkee) continue;
117+
events.push({ ...base, verb: "forked", object: repo.name, description: `Original repository created by ${forkee.full_name}`, url: forkee.html_url });
118+
}
106119
}
120+
} catch (err) {
121+
console.error("GitHub fetch error:", err);
107122
}
108-
} catch (err) {
109-
console.error("GitHub fetch error:", err);
123+
124+
return events;
125+
}
126+
127+
// Check cache and fetch if expired
128+
let events: DisplayEvent[] = [];
129+
const now = Date.now();
130+
131+
if (eventsCache.data && now - eventsCache.timestamp < eventsCache.TTL) {
132+
// Use cached data
133+
events = eventsCache.data;
134+
console.log("Using cached GitHub events");
135+
} else {
136+
// Fetch fresh data and update cache
137+
events = await fetchGitHubEvents();
138+
eventsCache.data = events;
139+
eventsCache.timestamp = now;
140+
console.log("Fetched fresh GitHub events");
110141
}
111142
---
112143

0 commit comments

Comments
 (0)