From 051ad7f184e8f7d05cdb4ad0dad6baf5040f55b0 Mon Sep 17 00:00:00 2001 From: anonymousRecords Date: Sat, 29 Aug 2026 17:24:29 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20GitHub=20=EA=B3=B5=EC=8B=9D=20GraphQ?= =?UTF-8?q?L=20API=EB=A1=9C=20=EC=BB=A8=ED=8A=B8=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=EC=85=98=20=EC=A1=B0=ED=9A=8C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 사용하던 github-contributions-api.deno.dev가 Deno Deploy Classic 서비스 종료(2026-07-20)로 사라지면서 모든 애니메이션 요청이 500으로 실패했다. 해당 API는 GitHub GraphQL의 contributionCalendar 응답을 그대로 중계하던 것이라, 공식 API를 직접 호출해도 데이터 구조는 동일하다. 따라서 응답 파싱 로직과 chunkIntoWeeks는 그대로 두고 요청 경로만 교체했다. - GITHUB_TOKEN 환경변수로 api.github.com/graphql 인증 호출 - GraphQL errors 배열과 user null을 구분해 처리하여 실패 원인이 서버 로그에 드러나도록 개선 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01La1oXcrwEZycnLJGdkFn3y --- lib/api/github.ts | 84 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/lib/api/github.ts b/lib/api/github.ts index ee49f6b..8d8ed79 100644 --- a/lib/api/github.ts +++ b/lib/api/github.ts @@ -6,42 +6,90 @@ export interface ContributionDay { interface RawContributionDay { date: string; contributionCount: number; - color: string; - contributionLevel: - | "NONE" - | "FIRST_QUARTILE" - | "SECOND_QUARTILE" - | "THIRD_QUARTILE" - | "FOURTH_QUARTILE"; } -type RawWeek = RawContributionDay[]; +interface RawWeek { + contributionDays: RawContributionDay[]; +} -interface GitHubContributionsResponse { - contributions: RawWeek[]; +interface ContributionsQueryResponse { + data?: { + user: { + contributionsCollection: { + contributionCalendar: { + weeks: RawWeek[]; + }; + }; + } | null; + }; + errors?: { message: string }[]; } +const CONTRIBUTIONS_QUERY = ` + query ($login: String!) { + user(login: $login) { + contributionsCollection { + contributionCalendar { + weeks { + contributionDays { + date + contributionCount + } + } + } + } + } + } +`; + export async function fetchContributions( username: string, ): Promise { - const res = await fetch( - `https://github-contributions-api.deno.dev/${username}.json`, - ); + const token = process.env.GITHUB_TOKEN; + + if (!token) { + throw new Error("GITHUB_TOKEN is not configured"); + } + + const res = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: CONTRIBUTIONS_QUERY, + variables: { login: username }, + }), + }); if (!res.ok) { - throw new Error(`Failed to fetch contributions for ${username}`); + throw new Error( + `Failed to fetch contributions for ${username} (HTTP ${res.status})`, + ); } - const json: GitHubContributionsResponse = await res.json(); + const json: ContributionsQueryResponse = await res.json(); + + if (json.errors?.length) { + throw new Error( + `Failed to fetch contributions for ${username}: ${json.errors[0].message}`, + ); + } - const rawWeeks = json?.contributions || []; + const rawWeeks = + json.data?.user?.contributionsCollection?.contributionCalendar?.weeks; + + if (!rawWeeks) { + throw new Error(`GitHub user not found: ${username}`); + } const days: ContributionDay[] = rawWeeks - .flat() + .flatMap((week) => week.contributionDays) .filter((day) => !!day?.date && typeof day.contributionCount === "number") .map((day) => ({ date: day.date, - count: day.contributionCount ?? 0, + count: day.contributionCount, })); return days; From d43c577d011da74e02197883752c7d7d372d74c2 Mon Sep 17 00:00:00 2001 From: anonymousRecords Date: Sat, 29 Aug 2026 17:33:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20Preview=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=A0=81=EC=9A=A9=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=9E=AC=EB=B0=B0=ED=8F=AC=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01La1oXcrwEZycnLJGdkFn3y