diff --git a/apps/website/src/lib/server/github-stars.ts b/apps/website/src/lib/server/github-stars.ts new file mode 100644 index 00000000..d8258ec9 --- /dev/null +++ b/apps/website/src/lib/server/github-stars.ts @@ -0,0 +1,68 @@ +const GITHUB_REPO_URL = 'https://api.github.com/repos/classroomio/classroomio'; +const CACHE_TTL_MS = 1000 * 60 * 60 * 3; + +const starsCache = { + stars: 0, + lastUpdated: 0 +}; + +let inFlightFetch: Promise | null = null; + +function isCacheFresh(now: number) { + return starsCache.lastUpdated > 0 && now - starsCache.lastUpdated < CACHE_TTL_MS; +} + +export async function getGithubStars(): Promise { + const now = Date.now(); + + if (isCacheFresh(now)) { + return starsCache.stars; + } + + if (inFlightFetch) { + return inFlightFetch; + } + + inFlightFetch = fetchGithubStars(now).finally(() => { + inFlightFetch = null; + }); + + return inFlightFetch; +} + +async function fetchGithubStars(now: number): Promise { + try { + const response = await fetch(GITHUB_REPO_URL, { + headers: { + Accept: 'application/vnd.github+json', + 'User-Agent': 'classroomio-website' + } + }); + + if (!response.ok) { + console.error('getGithubStars error: GitHub API returned', response.status); + + if (starsCache.lastUpdated > 0) { + return starsCache.stars; + } + + return 0; + } + + const data = await response.json(); + const stars = typeof data?.stargazers_count === 'number' ? data.stargazers_count : 0; + + starsCache.stars = stars; + starsCache.lastUpdated = now; + + return stars; + } catch (error) { + console.error('getGithubStars error:', error); + + if (starsCache.lastUpdated > 0) { + return starsCache.stars; + } + + return 0; + } +} diff --git a/apps/website/src/routes/+layout.server.ts b/apps/website/src/routes/+layout.server.ts new file mode 100644 index 00000000..8389f79e --- /dev/null +++ b/apps/website/src/routes/+layout.server.ts @@ -0,0 +1,7 @@ +import { getGithubStars } from '$lib/server/github-stars'; + +export async function load() { + const stars = await getGithubStars(); + + return { stars }; +} diff --git a/apps/website/src/routes/+layout.ts b/apps/website/src/routes/+layout.ts index 91637aa7..c3e77618 100644 --- a/apps/website/src/routes/+layout.ts +++ b/apps/website/src/routes/+layout.ts @@ -3,50 +3,18 @@ import type { MetaTagsProps } from 'svelte-meta-tags'; export const prerender = true; export async function load({ url }) { - const stars = await getStars(); - return { baseMetaTags: getBaseMetaTags(url), - url: url.pathname, - stars + url: url.pathname }; } -const starsCache = new Map(); -const CACHE_TIME = 1000 * 60 * 60 * 48; // 48 hours -const CACHE_KEY = 'github-stars'; const DEFAULT_META_TITLE = 'ClassroomIO | One Platform for Customer, Partner, and Employee Training'; const DEFAULT_META_DESCRIPTION = 'One platform for customer academies, partner certification, and employee training. Build courses with AI, publish under your domain, and track completions.'; const DEFAULT_OG_IMAGE_URL = 'https://brand.cdn.clsrio.com/og/classroomio-opengraph.jpg'; const DEFAULT_OG_IMAGE_ALT = 'ClassroomIO platform for customer, partner, and employee education'; -async function getStars() { - const now = Date.now(); - - const cacheData = starsCache.get(CACHE_KEY); - if (cacheData && now - cacheData.lastUpdated < CACHE_TIME) { - console.log('Returning cached stars'); - return cacheData.stars; - } - - console.log('Fetching stars from GitHub'); - - let stars = 0; - - try { - const response = await fetch('https://api.github.com/repos/classroomio/classroomio'); - const data = await response.json(); - stars = data?.stargazers_count || 0; - } catch (error) { - console.log('error fetching stars', error); - } - - starsCache.set(CACHE_KEY, { stars, lastUpdated: now }); - - return stars; -} - function getBaseMetaTags(url: URL) { const canonicalUrl = new URL(url.pathname, url.origin).href;