diff --git a/lib/cache.ts b/lib/cache.ts new file mode 100644 index 0000000..960cb12 --- /dev/null +++ b/lib/cache.ts @@ -0,0 +1,46 @@ +interface CacheEntry { + data: T; + expiresAt: number; +} + +class InMemoryCache { + private store = new Map>(); + + set(key: string, data: T, ttlMs: number): void { + this.store.set(key, { + data, + expiresAt: Date.now() + ttlMs, + }); + } + + get(key: string): T | null { + const entry = this.store.get(key) as CacheEntry | undefined; + if (!entry) return null; + if (Date.now() > entry.expiresAt) { + this.store.delete(key); + return null; + } + return entry.data; + } + + has(key: string): boolean { + return this.get(key) !== null; + } + + delete(key: string): void { + this.store.delete(key); + } + + clear(): void { + this.store.clear(); + } +} + +// Singleton cache instance shared across API requests +export const cache = new InMemoryCache(); + +export const CACHE_TTL = { + ONE_DAY: 24 * 60 * 60 * 1000, + ONE_HOUR: 60 * 60 * 1000, + FIVE_MINUTES: 5 * 60 * 1000, +}; diff --git a/lib/github.ts b/lib/github.ts index 4023aac..cf99e1c 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -1,5 +1,6 @@ import { ContributionTotals, GitHubUserData, PullRequestNode, RepoNode } from "@/types/github"; import { graphql } from "@octokit/graphql"; +import { cache, CACHE_TTL } from "./cache"; if (!process.env.GITHUB_TOKEN) { throw new Error("Missing GITHUB_TOKEN"); @@ -60,15 +61,26 @@ const QUERY = /* GraphQL */ ` export async function fetchGitHubUserData( username: string ): Promise { + const cacheKey = `github:user:${username.toLowerCase()}`; + + const cached = cache.get(cacheKey); + if (cached) { + return cached; + } + const { user } = await client<{ user: any }>(QUERY, { login: username }); if (!user) { throw new Error("User not found"); } - return { + const data: GitHubUserData = { repos: user.repositories.nodes as RepoNode[], pullRequests: user.pullRequests.nodes as PullRequestNode[], contributions: user.contributionsCollection as ContributionTotals, }; + + cache.set(cacheKey, data, CACHE_TTL.ONE_DAY); + + return data; }