Browse Source

arrange for using github access tokens for the repo page

master
Silberengel 4 weeks ago
parent
commit
1042467f9e
  1. 10
      src/lib/components/content/FileExplorer.svelte
  2. 46
      src/lib/services/content/git-repo-fetcher.ts
  3. 17
      src/lib/services/github-api.ts
  4. 7
      src/routes/repos/[naddr]/+page.svelte

10
src/lib/components/content/FileExplorer.svelte

@ -117,9 +117,13 @@
} }
// Use GitHub API helper for GitHub repos (handles rate limiting and token fallback) // Use GitHub API helper for GitHub repos (handles rate limiting and token fallback)
const response = url.includes('github.com') let response: Response;
? await fetchGitHubApi(apiUrl) if (url.includes('github.com')) {
: await fetch(apiUrl); const apiResult = await fetchGitHubApi(apiUrl);
response = apiResult.response;
} else {
response = await fetch(apiUrl);
}
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`); throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);

46
src/lib/services/content/git-repo-fetcher.ts

@ -18,6 +18,7 @@ export interface GitRepoInfo {
content: string; content: string;
format: 'markdown' | 'asciidoc'; format: 'markdown' | 'asciidoc';
}; };
usingGitHubToken?: boolean; // Indicates if GitHub API token was used
} }
export interface GitBranch { export interface GitBranch {
@ -89,19 +90,29 @@ function parseGitUrl(url: string): { platform: string; owner: string; repo: stri
*/ */
async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo | null> { async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo | null> {
try { try {
const repoResponse = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}`); const repoApiResult = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}`);
if (!repoResponse.ok) { if (!repoApiResult.response.ok) {
console.warn(`GitHub API error for repo ${owner}/${repo}: ${repoResponse.status} ${repoResponse.statusText}`); console.warn(`GitHub API error for repo ${owner}/${repo}: ${repoApiResult.response.status} ${repoApiResult.response.statusText}`);
return null; return null;
} }
const repoData = await repoResponse.json(); const repoData = await repoApiResult.response.json();
// Track if any request used a token
let usingToken = repoApiResult.usedToken;
const defaultBranch = repoData.default_branch || 'main'; const defaultBranch = repoData.default_branch || 'main';
const [branchesResponse, commitsResponse, treeResponse] = await Promise.all([ const [branchesResult, commitsResult, treeResult] = await Promise.all([
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/branches`), fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/branches`),
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=10`), fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=10`),
fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/git/trees/${defaultBranch}?recursive=1`).catch(() => null) fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/git/trees/${defaultBranch}?recursive=1`).catch(() => ({ response: null as any, usedToken: false }))
]); ]);
// Update token usage flag if any request used a token
usingToken = usingToken || branchesResult.usedToken || commitsResult.usedToken || (treeResult?.usedToken ?? false);
const branchesResponse = branchesResult.response;
const commitsResponse = commitsResult.response;
const treeResponse = treeResult?.response;
// Check if responses are OK and parse JSON // Check if responses are OK and parse JSON
let branchesData: any[] = []; let branchesData: any[] = [];
@ -206,10 +217,12 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo
const readmeFiles = ['README.adoc', 'README.md', 'README.rst', 'README.txt']; const readmeFiles = ['README.adoc', 'README.md', 'README.rst', 'README.txt'];
for (const readmeFile of readmeFiles) { for (const readmeFile of readmeFiles) {
try { try {
const readmeData = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmeFile}?ref=${defaultBranch}`).then(r => { const readmeResult = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmeFile}?ref=${defaultBranch}`);
if (!r.ok) throw new Error('Not found'); if (readmeResult.usedToken) {
return r.json(); usingToken = true;
}); }
if (!readmeResult.response.ok) throw new Error('Not found');
const readmeData = await readmeResult.response.json();
if (readmeData.content) { if (readmeData.content) {
const content = atob(readmeData.content.replace(/\s/g, '')); const content = atob(readmeData.content.replace(/\s/g, ''));
readme = { readme = {
@ -244,10 +257,12 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo
// If found in tree, fetch it // If found in tree, fetch it
if (readmePath) { if (readmePath) {
try { try {
const readmeData = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmePath}?ref=${defaultBranch}`).then(r => { const readmeResult = await fetchGitHubApi(`https://api.github.com/repos/${owner}/${repo}/contents/${readmePath}?ref=${defaultBranch}`);
if (!r.ok) throw new Error('Not found'); if (readmeResult.usedToken) {
return r.json(); usingToken = true;
}); }
if (!readmeResult.response.ok) throw new Error('Not found');
const readmeData = await readmeResult.response.json();
if (readmeData.content) { if (readmeData.content) {
const content = atob(readmeData.content.replace(/\s/g, '')); const content = atob(readmeData.content.replace(/\s/g, ''));
const format = readmePath.toLowerCase().endsWith('.adoc') ? 'asciidoc' : 'markdown'; const format = readmePath.toLowerCase().endsWith('.adoc') ? 'asciidoc' : 'markdown';
@ -271,7 +286,8 @@ async function fetchFromGitHub(owner: string, repo: string): Promise<GitRepoInfo
branches, branches,
commits, commits,
files, files,
readme readme,
usingGitHubToken: usingToken
}; };
} catch (error) { } catch (error) {
console.error('Error fetching from GitHub:', error); console.error('Error fetching from GitHub:', error);

17
src/lib/services/github-api.ts

@ -73,16 +73,24 @@ async function getGitHubToken(): Promise<string | null> {
} }
} }
/**
* Result of a GitHub API request, including whether a token was used
*/
export interface GitHubApiResponse {
response: Response;
usedToken: boolean;
}
/** /**
* Make a GitHub API request with automatic token fallback on rate limiting * Make a GitHub API request with automatic token fallback on rate limiting
* @param url - GitHub API URL * @param url - GitHub API URL
* @param options - Fetch options (headers will be merged) * @param options - Fetch options (headers will be merged)
* @returns Response object * @returns Response object and whether a token was used
*/ */
export async function fetchGitHubApi( export async function fetchGitHubApi(
url: string, url: string,
options: RequestInit = {} options: RequestInit = {}
): Promise<Response> { ): Promise<GitHubApiResponse> {
// First attempt without token // First attempt without token
let response = await fetch(url, { let response = await fetch(url, {
...options, ...options,
@ -92,6 +100,8 @@ export async function fetchGitHubApi(
} }
}); });
let usedToken = false;
// Check if rate limited // Check if rate limited
if (isRateLimited(response)) { if (isRateLimited(response)) {
console.log('GitHub API rate limit detected, attempting to use saved token...'); console.log('GitHub API rate limit detected, attempting to use saved token...');
@ -101,6 +111,7 @@ export async function fetchGitHubApi(
if (token) { if (token) {
console.log('Using saved GitHub token for authenticated request'); console.log('Using saved GitHub token for authenticated request');
usedToken = true;
// Retry with token (use Bearer for compatibility with both classic and fine-grained tokens) // Retry with token (use Bearer for compatibility with both classic and fine-grained tokens)
response = await fetch(url, { response = await fetch(url, {
@ -133,5 +144,5 @@ export async function fetchGitHubApi(
} }
} }
return response; return { response, usedToken };
} }

7
src/routes/repos/[naddr]/+page.svelte

@ -20,6 +20,7 @@
import { cacheEvent, getEventsByKind } from '../../../lib/services/cache/event-cache.js'; import { cacheEvent, getEventsByKind } from '../../../lib/services/cache/event-cache.js';
import EventMenu from '../../../lib/components/EventMenu.svelte'; import EventMenu from '../../../lib/components/EventMenu.svelte';
import { fetchProfiles } from '../../../lib/services/user-data.js'; import { fetchProfiles } from '../../../lib/services/user-data.js';
import Icon from '../../../lib/components/ui/Icon.svelte';
let naddr = $derived($page.params.naddr); let naddr = $derived($page.params.naddr);
let repoEvent = $state<NostrEvent | null>(null); let repoEvent = $state<NostrEvent | null>(null);
@ -895,6 +896,12 @@
<EventMenu event={repoEvent} showContentActions={true} /> <EventMenu event={repoEvent} showContentActions={true} />
{/if} {/if}
</div> </div>
{#if gitRepo?.usingGitHubToken}
<div class="github-token-notice mb-4 p-3 bg-fog-highlight dark:bg-fog-dark-highlight border border-fog-border dark:border-fog-dark-border rounded text-sm text-fog-text dark:text-fog-dark-text">
<Icon name="key" size={16} class="inline mr-2" />
Using your saved GitHub API token for authenticated requests
</div>
{/if}
{#if getRepoDescription()} {#if getRepoDescription()}
<p class="text-fog-text-light dark:text-fog-dark-text-light mb-4"> <p class="text-fog-text-light dark:text-fog-dark-text-light mb-4">
{getRepoDescription()} {getRepoDescription()}

Loading…
Cancel
Save