From 09c724e09b3bfbf27b80762b4cc45547153ab1ff Mon Sep 17 00:00:00 2001 From: Dominic Seung Date: Sun, 8 Nov 2026 19:56:16 -0800 Subject: [PATCH] Replace GitHub activity feed with pinned and starred repos --- src/components/GithubFeed.astro | 132 ------------------------------ src/components/GithubRepos.astro | 133 +++++++++++++++++++++++++++++++ src/components/RepoCard.astro | 36 +++++++++ src/pages/index.astro | 8 +- src/types/GitHub.ts | 24 ------ 5 files changed, 173 insertions(+), 160 deletions(-) delete mode 100644 src/components/GithubFeed.astro create mode 100644 src/components/GithubRepos.astro create mode 100644 src/components/RepoCard.astro delete mode 100644 src/types/GitHub.ts diff --git a/src/components/GithubFeed.astro b/src/components/GithubFeed.astro deleted file mode 100644 index e980c13..0000000 --- a/src/components/GithubFeed.astro +++ /dev/null @@ -1,132 +0,0 @@ ---- -export const prerender = false; - -import { Octokit } from "@octokit/rest"; -import { isEvent } from "../types/GitHub"; - -interface DisplayEvent { - actor: { login: string; avatar_url: string }; - verb: string; - object: string; - description?: string; - repo: string; - url: string; - timestamp: string; -} - -const octokit = new Octokit({ - auth: import.meta.env.GITHUB_TOKEN -}); - -async function fetchGitHubEvents() { - const events: DisplayEvent[] = []; - - const res = await octokit.rest.activity.listPublicEventsForUser({ - username: "dsnsgithub" - }); - - const seenUrls = new Set(); - for (const event of res.data) { - if (isEvent(event, "PullRequestEvent")) { - const pullRequest = event.payload.pull_request; - const [owner, repo] = event.repo.name.split("/"); - - if (seenUrls.has(pullRequest.url)) continue; - seenUrls.add(pullRequest.url); - - const { data: prDetails } = await octokit.rest.pulls.get({ - owner, - repo, - pull_number: pullRequest.number - }); - - events.push({ - actor: event.actor, - verb: `${event.payload.action} pull request`, - object: `${prDetails.title} (#${prDetails.number})`, - description: prDetails.body!, - repo: event.repo.name, - url: prDetails.html_url, - timestamp: event.created_at! - }); - } else if (isEvent(event, "IssuesEvent")) { - const issue = event.payload.issue; - - if (seenUrls.has(issue.url)) continue; - seenUrls.add(issue.url); - - events.push({ - actor: event.actor, - verb: `${event.payload.action} issue`, - object: `${issue.title} (#${issue.number})`, - description: issue.body!, - repo: event.repo.name, - url: issue.html_url, - timestamp: event.created_at! - }); - } else if (isEvent(event, "ForkEvent")) { - const fork = event.payload.forkee; - - if (seenUrls.has(fork.html_url!)) continue; - seenUrls.add(fork.html_url!); - - events.push({ - actor: event.actor, - verb: "forked", - object: fork.full_name!, - description: `Created under ${fork.full_name}.`, - repo: event.repo.name, - url: fork.html_url!, - timestamp: event.created_at! - }); - } - } - - return events; -} - -const events = await fetchGitHubEvents(); ---- - - - - diff --git a/src/components/GithubRepos.astro b/src/components/GithubRepos.astro new file mode 100644 index 0000000..59c73ea --- /dev/null +++ b/src/components/GithubRepos.astro @@ -0,0 +1,133 @@ +--- +export const prerender = false; + +import { Octokit } from "@octokit/rest"; +import { Icon } from "astro-icon/components"; +import RepoCard from "./RepoCard.astro"; + +interface DisplayRepo { + owner: string; + name: string; + description: string; + url: string; + stars: number; + language?: string; +} + +const octokit = new Octokit({ + auth: import.meta.env.GITHUB_TOKEN +}); + +const GITHUB_USERNAME = "dsnsgithub"; +const STARRED_COUNT = 6; + +async function fetchPinnedRepos(): Promise { + // Pinned repositories are only exposed through the GraphQL API. + const response = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: ` + query ($login: String!) { + user(login: $login) { + pinnedItems(first: 6, types: REPOSITORY) { + nodes { + ... on Repository { + name + description + url + stargazerCount + primaryLanguage { name } + owner { login } + } + } + } + } + } + `, + variables: { login: GITHUB_USERNAME } + }) + }); + + const json = await response.json(); + const items: Array> = json.data?.user?.pinnedItems?.nodes ?? []; + + return items.map((repo) => ({ + owner: repo.owner.login, + name: repo.name, + description: repo.description ?? "", + url: repo.url, + stars: repo.stargazerCount, + language: repo.primaryLanguage?.name + })); +} + +async function fetchStarredRepos(): Promise { + const res = await octokit.rest.activity.listReposStarredByUser({ + username: GITHUB_USERNAME, + sort: "created", + direction: "desc", + per_page: STARRED_COUNT + }); + + return res.data.map((item) => { + const repo = "repo" in item ? item.repo : item; + + return { + owner: repo.owner.login, + name: repo.name, + description: repo.description ?? "", + url: repo.html_url, + stars: repo.stargazers_count, + language: repo.language ?? undefined + }; + }); +} + +function sortRepos(repos: DisplayRepo[]): DisplayRepo[] { + return [...repos].sort((a, b) => b.stars - a.stars); +} + +const [pinned, starred] = await Promise.all([fetchPinnedRepos(), fetchStarredRepos()]); +--- + +
+ {pinned.length === 0 && starred.length === 0 && ( +

No repositories found.

+ )} + + { + pinned.length > 0 && ( +
+

+ + Pinned Repositories +

+
+ {pinned.map((repo) => ( + + ))} +
+
+ ) + } + + { + starred.length > 0 && ( +
+

+ + Repositories I Find Cool +

+
+ {sortRepos(starred).map((repo) => ( + + ))} +
+
+ ) + } +
diff --git a/src/components/RepoCard.astro b/src/components/RepoCard.astro new file mode 100644 index 0000000..5ca9e30 --- /dev/null +++ b/src/components/RepoCard.astro @@ -0,0 +1,36 @@ +--- +import { Icon } from "astro-icon/components"; + +interface Props { + url: string; + owner: string; + name: string; + description: string; + stars: number; + language?: string; +} + +const { url, owner, name, description, stars, language } = Astro.props; +--- + + +
+

{name}

+

+ + {stars} +

+
+ + {description &&

{description}

} + +
+ {language && {language}} + {owner} +
+
diff --git a/src/pages/index.astro b/src/pages/index.astro index a4e945b..9cfd6db 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -7,7 +7,7 @@ import scheduliIcon from "../assets/schedule.svg"; import betterHudIcon from "../assets/betterhud.png"; import { Icon } from "astro-icon/components"; import DiscordStatus from "../components/DiscordStatus.tsx"; -import GithubFeed from "../components/GithubFeed.astro"; +import GithubRepos from "../components/GithubRepos.astro"; import GithubFeedSkeleton from "../components/GithubFeedSkeleton.astro"; --- @@ -71,10 +71,10 @@ import GithubFeedSkeleton from "../components/GithubFeedSkeleton.astro";
-

GitHub Feed

- +

GitHub

+ -
+
diff --git a/src/types/GitHub.ts b/src/types/GitHub.ts deleted file mode 100644 index 84c7555..0000000 --- a/src/types/GitHub.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { components } from "@octokit/openapi-types"; - -interface EventPayloadMap { - CreateEvent: components["schemas"]["create-event"]; - DeleteEvent: components["schemas"]["delete-event"]; - DiscussionEvent: components["schemas"]["discussion-event"]; - IssuesEvent: components["schemas"]["issues-event"]; - IssueCommentEvent: components["schemas"]["issue-comment-event"]; - ForkEvent: components["schemas"]["fork-event"]; - GollumEvent: components["schemas"]["gollum-event"]; - MemberEvent: components["schemas"]["member-event"]; - PublicEvent: components["schemas"]["public-event"]; - PushEvent: components["schemas"]["push-event"]; - PullRequestEvent: components["schemas"]["pull-request-event"]; - PullRequestReviewCommentEvent: components["schemas"]["pull-request-review-comment-event"]; - PullRequestReviewEvent: components["schemas"]["pull-request-review-event"]; - CommitCommentEvent: components["schemas"]["commit-comment-event"]; - ReleaseEvent: components["schemas"]["release-event"]; - WatchEvent: components["schemas"]["watch-event"]; -}; - -export function isEvent(event: components["schemas"]["event"], type: T): event is components["schemas"]["event"] & { type: T; payload: EventPayloadMap[T] } { - return event.type === type; -}