fix: proxy forge API requests to avoid CORS, case-insensitive readme

Add /v1/forge/{source}/* proxy endpoint to the API server with an
allowlisted set of hosts. Frontend readme and language requests now
go through the proxy instead of hitting forge APIs directly (Gitea
has no CORS headers). Gitea readme fetch tries README.md, readme.md,
and Readme.md casings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 16:24:32 +03:00
parent f676ecdc19
commit 6b9ce99a06
3 changed files with 77 additions and 18 deletions

View File

@@ -102,19 +102,20 @@ export async function fetchProjects(): Promise<ProjectSummary[]> {
return resp.json();
}
/** Fetch repo README as raw markdown. */
/** Fetch repo README as raw markdown via the forge proxy. */
export async function fetchReadme(source: Source, host: string, repo: string): Promise<string | null> {
if (source === 'github') {
const resp = await fetch(`https://api.github.com/repos/${repo}/readme`, {
headers: { 'Accept': 'application/vnd.github.raw+json' },
});
const resp = await fetch(`${API_BASE}/forge/github/repos/${repo}/readme`);
if (!resp.ok) return null;
return resp.text();
const data = await resp.json();
if (data.encoding === 'base64' && data.content) {
return atob(data.content);
}
return data.content ?? null;
}
if (source === 'gitea') {
// Gitea returns JSON with base64-encoded content. Try common casings.
for (const name of ['README.md', 'readme.md', 'Readme.md']) {
const resp = await fetch(`https://${host}/api/v1/repos/${repo}/contents/${name}`);
const resp = await fetch(`${API_BASE}/forge/gitea/repos/${repo}/contents/${name}?host=${encodeURIComponent(host)}`);
if (!resp.ok) continue;
const data = await resp.json();
if (data.encoding === 'base64' && data.content) {
@@ -127,16 +128,11 @@ export async function fetchReadme(source: Source, host: string, repo: string): P
return null;
}
/** Fetch repo languages as { language: bytes } map. */
/** Fetch repo languages as { language: bytes } map via the forge proxy. */
export async function fetchLanguages(source: Source, host: string, repo: string): Promise<Record<string, number> | null> {
const baseUrl = source === 'github'
? `https://api.github.com/repos/${repo}/languages`
: source === 'gitea'
? `https://${host}/api/v1/repos/${repo}/languages`
: null;
if (!baseUrl) return null;
const resp = await fetch(baseUrl);
if (source !== 'github' && source !== 'gitea') return null;
const hostParam = source === 'gitea' ? `?host=${encodeURIComponent(host)}` : '';
const resp = await fetch(`${API_BASE}/forge/${source}/repos/${repo}/languages${hostParam}`);
if (!resp.ok) return null;
return resp.json();
}