Spaces:
Running
Running
fix(ui): proxy github api requests through backend to avoid rate limit 403s
Browse files
backend/app/main.py
CHANGED
|
@@ -77,10 +77,12 @@ logger.info(f"CORS origins: {settings.cors_origins}")
|
|
| 77 |
from app.routes.auth import router as auth_router
|
| 78 |
from app.routes.documents import router as documents_router
|
| 79 |
from app.routes.chat import router as chat_router
|
|
|
|
| 80 |
|
| 81 |
app.include_router(auth_router, prefix="/api/v1")
|
| 82 |
app.include_router(documents_router, prefix="/api/v1")
|
| 83 |
app.include_router(chat_router, prefix="/api/v1")
|
|
|
|
| 84 |
|
| 85 |
|
| 86 |
# ββ Health Check βββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 77 |
from app.routes.auth import router as auth_router
|
| 78 |
from app.routes.documents import router as documents_router
|
| 79 |
from app.routes.chat import router as chat_router
|
| 80 |
+
from app.routes.github import router as github_router
|
| 81 |
|
| 82 |
app.include_router(auth_router, prefix="/api/v1")
|
| 83 |
app.include_router(documents_router, prefix="/api/v1")
|
| 84 |
app.include_router(chat_router, prefix="/api/v1")
|
| 85 |
+
app.include_router(github_router, prefix="/api/v1")
|
| 86 |
|
| 87 |
|
| 88 |
# ββ Health Check βββββββββββββββββββββββββββββββββββββ
|
backend/app/routes/github.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import time
|
| 3 |
+
import urllib.request
|
| 4 |
+
from urllib.error import URLError, HTTPError
|
| 5 |
+
from fastapi import APIRouter, HTTPException
|
| 6 |
+
|
| 7 |
+
router = APIRouter()
|
| 8 |
+
|
| 9 |
+
CACHE = {
|
| 10 |
+
"contribs": {"data": None, "timestamp": 0},
|
| 11 |
+
"repo": {"data": None, "timestamp": 0}
|
| 12 |
+
}
|
| 13 |
+
TTL = 3600 # 1 hour cache to avoid 403 Rate Limit
|
| 14 |
+
|
| 15 |
+
REPO = "param20h/PDF-Assistant-RAG"
|
| 16 |
+
|
| 17 |
+
def fetch_github(url: str, cache_key: str):
|
| 18 |
+
now = time.time()
|
| 19 |
+
if CACHE[cache_key]["data"] is not None and now - CACHE[cache_key]["timestamp"] < TTL:
|
| 20 |
+
return CACHE[cache_key]["data"]
|
| 21 |
+
|
| 22 |
+
req = urllib.request.Request(url, headers={
|
| 23 |
+
"Accept": "application/vnd.github.v3+json",
|
| 24 |
+
"User-Agent": "PDF-Assistant-RAG"
|
| 25 |
+
})
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
with urllib.request.urlopen(req) as response:
|
| 29 |
+
data = json.loads(response.read().decode())
|
| 30 |
+
CACHE[cache_key]["data"] = data
|
| 31 |
+
CACHE[cache_key]["timestamp"] = now
|
| 32 |
+
return data
|
| 33 |
+
except HTTPError as e:
|
| 34 |
+
# Fallback to cache if rate limited
|
| 35 |
+
if CACHE[cache_key]["data"] is not None:
|
| 36 |
+
return CACHE[cache_key]["data"]
|
| 37 |
+
raise HTTPException(status_code=e.code, detail="GitHub API Error")
|
| 38 |
+
except URLError as e:
|
| 39 |
+
if CACHE[cache_key]["data"] is not None:
|
| 40 |
+
return CACHE[cache_key]["data"]
|
| 41 |
+
raise HTTPException(status_code=500, detail="Failed to connect to GitHub")
|
| 42 |
+
|
| 43 |
+
@router.get("/github/stats")
|
| 44 |
+
def get_github_stats():
|
| 45 |
+
contribs = fetch_github(f"https://api.github.com/repos/{REPO}/contributors?per_page=30", "contribs")
|
| 46 |
+
repo = fetch_github(f"https://api.github.com/repos/{REPO}", "repo")
|
| 47 |
+
|
| 48 |
+
return {
|
| 49 |
+
"contributors": contribs if isinstance(contribs, list) else [],
|
| 50 |
+
"stats": {
|
| 51 |
+
"stargazers_count": repo.get("stargazers_count", 0),
|
| 52 |
+
"forks_count": repo.get("forks_count", 0),
|
| 53 |
+
"open_issues_count": repo.get("open_issues_count", 0)
|
| 54 |
+
}
|
| 55 |
+
}
|
frontend/src/components/layout/ContributorsPanel.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
import { useState, useEffect } from "react";
|
| 4 |
import { GitBranch, Star, GitPullRequest, Users, X, Trophy, ExternalLink } from "lucide-react";
|
| 5 |
import { Button } from "@/components/ui/button";
|
|
|
|
| 6 |
|
| 7 |
interface Contributor {
|
| 8 |
login: string;
|
|
@@ -25,13 +26,10 @@ export default function ContributorsPanel({ onClose }: { onClose: () => void })
|
|
| 25 |
const REPO = "param20h/PDF-Assistant-RAG";
|
| 26 |
|
| 27 |
useEffect(() => {
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
.then(([contribs, repo]) => {
|
| 33 |
-
setContributors(Array.isArray(contribs) ? contribs : []);
|
| 34 |
-
setStats(repo);
|
| 35 |
})
|
| 36 |
.catch(() => {})
|
| 37 |
.finally(() => setLoading(false));
|
|
@@ -58,7 +56,7 @@ export default function ContributorsPanel({ onClose }: { onClose: () => void })
|
|
| 58 |
</div>
|
| 59 |
<div>
|
| 60 |
<h2 className="font-bold text-base">Hall of Fame</h2>
|
| 61 |
-
<p className="text-xs text-muted-foreground">
|
| 62 |
</div>
|
| 63 |
</div>
|
| 64 |
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onClose}>
|
|
|
|
| 3 |
import { useState, useEffect } from "react";
|
| 4 |
import { GitBranch, Star, GitPullRequest, Users, X, Trophy, ExternalLink } from "lucide-react";
|
| 5 |
import { Button } from "@/components/ui/button";
|
| 6 |
+
import { api } from "@/lib/api";
|
| 7 |
|
| 8 |
interface Contributor {
|
| 9 |
login: string;
|
|
|
|
| 26 |
const REPO = "param20h/PDF-Assistant-RAG";
|
| 27 |
|
| 28 |
useEffect(() => {
|
| 29 |
+
api.get<{ contributors: Contributor[], stats: RepoStats }>("/api/v1/github/stats")
|
| 30 |
+
.then((data) => {
|
| 31 |
+
setContributors(Array.isArray(data.contributors) ? data.contributors : []);
|
| 32 |
+
setStats(data.stats);
|
|
|
|
|
|
|
|
|
|
| 33 |
})
|
| 34 |
.catch(() => {})
|
| 35 |
.finally(() => setLoading(false));
|
|
|
|
| 56 |
</div>
|
| 57 |
<div>
|
| 58 |
<h2 className="font-bold text-base">Hall of Fame</h2>
|
| 59 |
+
<p className="text-xs text-muted-foreground">Contributors β thank you! π</p>
|
| 60 |
</div>
|
| 61 |
</div>
|
| 62 |
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onClose}>
|