diff --git a/backend/app/main.py b/backend/app/main.py index ec7cc96..527e4f0 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,6 @@ from fastapi import FastAPI, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware -from .models.schemas import RepositoryAnalysisRequest, RepositoryAnalysisResponse +from .models.schemas import RepositoryAnalysisRequest, RepositoryAnalysisResponse, RepositoryInfoResponse from .services.github import GitHubService from .services.agent import AzureAgentService from .config import CORS_ORIGINS @@ -67,3 +67,26 @@ async def analyze_repository( analysis=f"Error analyzing repository: {str(e)}", error=str(e) ) + +@app.get("/api/repo-info/{owner}/{repo}", response_model=RepositoryInfoResponse) +async def get_repository_info( + owner: str, + repo: str, + github_service: GitHubService = Depends(get_github_service) +): + try: + print(f"Fetching repository data for {owner}/{repo}...") + repo_data = await github_service.get_repository_snapshot(owner, repo) + if not repo_data: + print(f"Repository not found: {owner}/{repo}") + raise HTTPException(status_code=404, detail="Repository not found") + + return RepositoryInfoResponse(**repo_data) + except RuntimeError as e: + error_msg = f"Error fetching repository info: {str(e)}" + print(error_msg) + raise HTTPException(status_code=500, detail=error_msg) + except Exception as e: + error_msg = f"Unexpected error: {str(e)}" + print(error_msg) + raise HTTPException(status_code=500, detail=error_msg) diff --git a/backend/app/models/schemas.py b/backend/app/models/schemas.py index 9f72585..6d37550 100644 --- a/backend/app/models/schemas.py +++ b/backend/app/models/schemas.py @@ -11,3 +11,11 @@ class RepositoryAnalysisResponse(BaseModel): repo_name: str analysis: str error: Optional[str] = None + +class RepositoryInfoResponse(BaseModel): + full_name: str + description: str + language: str + stars: int + default_branch: str + readme: Optional[str] = None diff --git a/backend/app/services/github.py b/backend/app/services/github.py index 0601a55..daac967 100644 --- a/backend/app/services/github.py +++ b/backend/app/services/github.py @@ -1,22 +1,25 @@ import base64 +from functools import lru_cache from typing import Dict, Optional, Any +from githubkit import GitHub -import httpx - -from ..config import GITHUB_TOKEN, GITHUB_API_URL +from ..config import GITHUB_TOKEN from ..constants import DEPENDENCY_FILES +@lru_cache +def gh() -> GitHub: + if not GITHUB_TOKEN: + raise RuntimeError("GITHUB_TOKEN not set") + return GitHub(GITHUB_TOKEN) + + class GitHubService: - """Service for interacting with the GitHub API.""" + """Service for interacting with the GitHub API using githubkit.""" def __init__(self) -> None: - """Initialize the GitHub service with authentication headers.""" - self.headers = { - "Accept": "application/vnd.github.v3+json", - } - if GITHUB_TOKEN: - self.headers["Authorization"] = f"token {GITHUB_TOKEN}" + """Initialize the GitHub service.""" + pass async def get_repository_info(self, owner: str, repo: str) -> Optional[Dict[str, Any]]: """ @@ -29,13 +32,15 @@ async def get_repository_info(self, owner: str, repo: str) -> Optional[Dict[str, Returns: Repository information as a dictionary or None if not found """ - async with httpx.AsyncClient() as client: - response = await client.get( - f"{GITHUB_API_URL}/repos/{owner}/{repo}", - headers=self.headers - ) - if response.status_code == 200: - return response.json() + try: + meta = gh().rest.repos.get(owner=owner, repo=repo).parsed_data + return { + "name": meta.name, + "full_name": meta.full_name, + "description": meta.description or "No description available", + "default_branch": meta.default_branch, + } + except Exception: return None async def get_readme_content(self, owner: str, repo: str) -> Optional[str]: @@ -49,15 +54,10 @@ async def get_readme_content(self, owner: str, repo: str) -> Optional[str]: Returns: README content as a string or None if not found """ - async with httpx.AsyncClient() as client: - response = await client.get( - f"{GITHUB_API_URL}/repos/{owner}/{repo}/readme", - headers=self.headers - ) - if response.status_code == 200: - data = response.json() - if data.get("content") and data.get("encoding") == "base64": - return base64.b64decode(data["content"]).decode("utf-8") + try: + readme_response = gh().rest.repos.get_readme(owner=owner, repo=repo).parsed_data + return base64.b64decode(readme_response.content).decode() + except Exception: return None async def get_requirements(self, owner: str, repo: str) -> Dict[str, str]: @@ -73,15 +73,73 @@ async def get_requirements(self, owner: str, repo: str) -> Dict[str, str]: """ results: Dict[str, str] = {} - async with httpx.AsyncClient() as client: - for file in DEPENDENCY_FILES: - response = await client.get( - f"{GITHUB_API_URL}/repos/{owner}/{repo}/contents/{file}", - headers=self.headers - ) - if response.status_code == 200: - data = response.json() - if data.get("content") and data.get("encoding") == "base64": - results[file] = base64.b64decode(data["content"]).decode("utf-8") + for file in DEPENDENCY_FILES: + try: + content_response = gh().rest.repos.get_content( + owner=owner, + repo=repo, + path=file + ).parsed_data + + if hasattr(content_response, "content") and hasattr(content_response, "encoding"): + if content_response.encoding == "base64": + results[file] = base64.b64decode(content_response.content).decode("utf-8") + except Exception: + continue return results + + async def get_repository_snapshot(self, owner: str, repo: str) -> Optional[Dict[str, Any]]: + """ + Get comprehensive repository information from GitHub API. + + Args: + owner: Repository owner/organization + repo: Repository name + + Returns: + Repository information as a dictionary or None if not found + """ + try: + print(f"Fetching repository data for {owner}/{repo}...") + + token_preview = GITHUB_TOKEN[:5] if GITHUB_TOKEN else "None" + print(f"Creating GitHub client with token: {token_preview}...") + client = gh() + + meta = client.rest.repos.get(owner=owner, repo=repo).parsed_data + print(f"Repository metadata fetched successfully") + + readme = "" + try: + readme_response = client.rest.repos.get_readme(owner=owner, repo=repo).parsed_data + readme = base64.b64decode(readme_response.content).decode() + print(f"README content fetched successfully") + except Exception as e: + print(f"Error fetching README: {str(e)}") + readme = "" + + primary_language = "Unknown" + try: + languages_response = client.rest.repos.list_languages(owner=owner, repo=repo).parsed_data + languages_dict = dict(languages_response) + if languages_dict: + primary_language = max(languages_dict.items(), key=lambda x: x[1])[0] + print(f"Primary language detected: {primary_language}") + else: + print("No language information available") + except Exception as e: + print(f"Error fetching languages: {str(e)}") + + return { + "full_name": meta.full_name, + "description": meta.description or "No description available", + "stars": meta.stargazers_count, + "language": primary_language, + "default_branch": meta.default_branch, + "readme": readme, + } + except Exception as e: + error_message = str(e) + print(f"Error in get_repository_snapshot for {owner}/{repo}: {error_message}") + raise RuntimeError(f"Failed to fetch repository data: {error_message}") diff --git a/frontend/src/components/RepoPage.tsx b/frontend/src/components/RepoPage.tsx index 7d95c6f..83ac36f 100644 --- a/frontend/src/components/RepoPage.tsx +++ b/frontend/src/components/RepoPage.tsx @@ -31,27 +31,39 @@ const RepoPage: React.FC = () => { const [analysisResults, setAnalysisResults] = useState(null); const [modalOpen, setModalOpen] = useState(false); - /* fetch mock repo data */ + /* fetch real repo data */ useEffect(() => { - const timer = setTimeout(() => { + const fetchRepoData = async () => { if (org && repo) { - setRepoData({ - org, - repo, - fullName: `${org}/${repo}`, - description: - 'Repository information would be fetched from GitHub API in a production environment.', - language: 'JavaScript', - stars: 1024, - }); - setLoading(false); + try { + const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000'; + const response = await fetch(`${apiUrl}/api/repo-info/${org}/${repo}`); + + if (response.ok) { + const data = await response.json(); + setRepoData({ + org, + repo, + fullName: data.full_name, + description: data.description, + language: data.language, + stars: data.stars, + }); + } else { + setError('Repository not found or failed to fetch data'); + } + } catch (err) { + setError(`Error fetching repository: ${err instanceof Error ? err.message : String(err)}`); + } finally { + setLoading(false); + } } else { setError('Invalid repository information'); setLoading(false); } - }, 1000); + }; - return () => clearTimeout(timer); + fetchRepoData(); }, [org, repo]); const analyzeRepository = async (agentId: string, repoName: string) => {