Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
8 changes: 8 additions & 0 deletions backend/app/models/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
130 changes: 94 additions & 36 deletions backend/app/services/github.py
Original file line number Diff line number Diff line change
@@ -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]]:
"""
Expand All @@ -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]:
Expand All @@ -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]:
Expand All @@ -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}")
40 changes: 26 additions & 14 deletions frontend/src/components/RepoPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,39 @@ const RepoPage: React.FC = () => {
const [analysisResults, setAnalysisResults] = useState<AnalysisResults | null>(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) => {
Expand Down