diff --git a/src/components/Bars.tsx b/src/components/Bars.tsx index d1f0e16..ed16c7d 100644 --- a/src/components/Bars.tsx +++ b/src/components/Bars.tsx @@ -46,43 +46,32 @@ interface ContributionDay { count: number; } -interface GitActivityData { - githubCommits: GitCommit[]; - giteaCommits: GitCommit[]; - githubActivity: ContributionDay[]; - giteaActivity: ContributionDay[]; +// Four independent cached promises — first RightBarContent instance to mount +// starts each fetch; the second gets the already-in-flight promise. +let ghCommitsPromise: Promise | null = null; +let gtCommitsPromise: Promise | null = null; +let ghActivityPromise: Promise | null = null; +let gtActivityPromise: Promise | null = null; + +function getGhCommitsPromise(): Promise { + return (ghCommitsPromise ??= api.gitActivity.getGitHubCommits + .query({ limit: 6 }) + .catch(() => [])); } - -// Shared fetch promise — whichever instance mounts first starts the fetch; -// the second instance awaits the same Promise instead of firing its own requests. -let gitActivityPromise: Promise | null = null; - -function fetchGitActivity(): Promise { - if (gitActivityPromise) return gitActivityPromise; - - gitActivityPromise = (async () => { - const [ghCommits, gtCommits, ghActivity, gtActivity] = await Promise.all([ - api.gitActivity.getGitHubCommits.query({ limit: 6 }).catch(() => []), - api.gitActivity.getGiteaCommits.query({ limit: 6 }).catch(() => []), - api.gitActivity.getGitHubActivity.query().catch(() => []), - api.gitActivity.getGiteaActivity.query().catch(() => []) - ]); - - const displayedGithubCommits = ghCommits.slice(0, 3); - const githubShas = new Set(displayedGithubCommits.map((c) => c.sha)); - const uniqueGiteaCommits = gtCommits - .filter((commit) => !githubShas.has(commit.sha)) - .slice(0, 3); - - return { - githubCommits: displayedGithubCommits, - giteaCommits: uniqueGiteaCommits, - githubActivity: ghActivity, - giteaActivity: gtActivity - }; - })(); - - return gitActivityPromise; +function getGtCommitsPromise(): Promise { + return (gtCommitsPromise ??= api.gitActivity.getGiteaCommits + .query({ limit: 6 }) + .catch(() => [])); +} +function getGhActivityPromise(): Promise { + return (ghActivityPromise ??= api.gitActivity.getGitHubActivity + .query() + .catch(() => [])); +} +function getGtActivityPromise(): Promise { + return (gtActivityPromise ??= api.gitActivity.getGiteaActivity + .query() + .catch(() => [])); } export function RightBarContent() { @@ -93,7 +82,8 @@ export function RightBarContent() { [] ); const [giteaActivity, setGiteaActivity] = createSignal([]); - const [loading, setLoading] = createSignal(true); + const [githubCommitsLoading, setGithubCommitsLoading] = createSignal(true); + const [giteaCommitsLoading, setGiteaCommitsLoading] = createSignal(true); const handleLinkClick = () => { if ( @@ -106,19 +96,22 @@ export function RightBarContent() { onMount(() => { setTimeout(() => { - fetchGitActivity() - .then((data) => { - setGithubCommits(data.githubCommits); - setGiteaCommits(data.giteaCommits); - setGithubActivity(data.githubActivity); - setGiteaActivity(data.giteaActivity); - }) - .catch((error) => { - console.error("Failed to fetch git activity:", error); - }) - .finally(() => { - setLoading(false); - }); + getGhCommitsPromise().then((commits) => { + setGithubCommits(commits.slice(0, 3)); + setGithubCommitsLoading(false); + }); + + // Deduplicate Gitea against whatever GitHub has resolved by the time this lands + getGtCommitsPromise().then((gtCommits) => { + const ghShas = new Set(githubCommits().map((c) => c.sha)); + setGiteaCommits( + gtCommits.filter((c) => !ghShas.has(c.sha)).slice(0, 3) + ); + setGiteaCommitsLoading(false); + }); + + getGhActivityPromise().then((activity) => setGithubActivity(activity)); + getGtActivityPromise().then((activity) => setGiteaActivity(activity)); }, 0); }); @@ -208,7 +201,7 @@ export function RightBarContent() { { - // Use Events API to get recent push events - much more efficient + // Use Events API to get recent push events const eventsResponse = await fetchWithTimeout( `https://api.github.com/users/MikeFreno/events/public?per_page=100`, { @@ -47,31 +47,66 @@ export const gitActivityRouter = createTRPCRouter({ await checkResponse(eventsResponse); const events = await eventsResponse.json(); - const allCommits: GitCommit[] = []; - // Extract commits directly from PushEvent payload — no per-commit API calls needed + // Collect (repo, sha) pairs from push events up front + const toFetch: { repoName: string; sha: string }[] = []; for (const event of events) { if (event.type !== "PushEvent") continue; - if (allCommits.length >= input.limit) break; + if (toFetch.length >= input.limit * 5) break; + toFetch.push({ + repoName: event.repo.name, + sha: event.payload.head + }); + } - const repoName = event.repo.name; - const payloadCommits: any[] = event.payload.commits || []; + // Fetch all commits in parallel instead of serially + const results = await Promise.allSettled( + toFetch.map(({ repoName, sha }) => + fetchWithTimeout( + `https://api.github.com/repos/${repoName}/commits/${sha}`, + { + headers: { + Authorization: `Bearer ${env.GITHUB_API_TOKEN}`, + Accept: "application/vnd.github.v3+json" + }, + timeout: 5000 + } + ) + .then((res) => (res.ok ? res.json() : null)) + .catch(() => null) + ) + ); - for (const payloadCommit of payloadCommits) { - if (allCommits.length >= input.limit) break; + const allCommits: GitCommit[] = []; + for (let i = 0; i < results.length; i++) { + const result = results[i]; + if (result.status === "rejected" || !result.value) continue; + const commit = result.value; + const { repoName } = toFetch[i]; + + if ( + commit.author?.login === "MikeFreno" || + commit.author?.login === "mikefreno" || + commit.commit?.author?.email?.includes("mike") + ) { allCommits.push({ - sha: payloadCommit.sha?.substring(0, 7) || "unknown", - message: payloadCommit.message?.split("\n")[0] || "No message", - author: payloadCommit.author?.name || "Unknown", - // event.created_at is the push timestamp — close enough to commit date - date: event.created_at || new Date().toISOString(), + sha: commit.sha?.substring(0, 7) || "unknown", + message: commit.commit?.message?.split("\n")[0] || "No message", + author: + commit.commit?.author?.name || + commit.author?.login || + "Unknown", + date: commit.commit?.author?.date || new Date().toISOString(), repo: repoName, - url: `https://github.com/${repoName}/commit/${payloadCommit.sha}` + url: `https://github.com/${repoName}/commit/${commit.sha}` }); } } - // Events are already in reverse-chronological order + allCommits.sort( + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() + ); + return allCommits.slice(0, input.limit); }, { maxStaleMs: CACHE_CONFIG.GIT_ACTIVITY_MAX_STALE_MS }