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
59 changes: 59 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Setting Up the .env File for FastAPI

This guide provides step-by-step instructions to configure your `.env` file and securely use a GitHub Personal Access Token in your FastAPI project.

## 1. Create the `.env` File

1. Navigate to the `backend` directory where your project is set up.
2. Create a new file named `.env` (if it doesn’t already exist).

## 2. Add the GitHub Access Token

1. Open the `.env` file and add the following line:

```text
GITHUB_TOKEN=your_personal_access_token_here
```

2. Replace `your_personal_access_token_here` with your actual GitHub Personal Access Token.

## 3. Generate a GitHub Personal Access Token

1. Go to [GitHub Developer Settings](https://github.com/settings/tokens).
2. Click on **Tokens (classic)** and then **Generate new token** and then generate the classic token.
3. Select the required scopes for your application:
- **For public repositories**: Check `repo`.
- **For additional access**: Check `read:org`, `read:user`, etc., as needed.
4. Copy the generated token (it will only be displayed once).


## 5. Load Environment Variables in FastAPI

Ensure you have these things installed in your project:

```bash
pip install python-dotenv requests fastapi uvicorn

```

### Test API Requests
Run your FastAPI server:

```bash
uvicorn backend.main:app --reload
```

Use `curl` or **Postman** to test the `/api/repo-stats` endpoint:

```bash
curl -X POST http://localhost:8000/api/repo-stats \
-H "Content-Type: application/json" \
-d '{"repo_url": "https://github.com/AOSSIE-Org/Devr.AI/"}'
```

If all the things correctly got setup then you will see the JSON repsponse

---


Now, your FastAPI application is securely set up to use GitHub API credentials from a `.env` file!
4 changes: 4 additions & 0 deletions backend/app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pydantic import BaseModel

class RepoRequest(BaseModel):
repo_url: str
12 changes: 12 additions & 0 deletions backend/app/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from fastapi import APIRouter, HTTPException
from app.models import RepoRequest
from app.utils.github_api import get_repo_stats

router = APIRouter()

@router.post("/repo-stats")
async def repo_stats_endpoint(repo: RepoRequest):
try:
return await get_repo_stats(repo.repo_url)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Empty file added backend/app/utils/__init__.py
Empty file.
121 changes: 121 additions & 0 deletions backend/app/utils/github_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import requests
import os
from urllib.parse import urlparse
from dotenv import load_dotenv

load_dotenv() # Load environment variables

app = FastAPI()

class RepoRequest(BaseModel):
repo_url: str

def parse_github_url(url: str) -> tuple:
"""Extract owner/repo from GitHub URL"""
parsed = urlparse(url)
path = parsed.path.strip('/').split('/')
if len(path) < 2:
raise ValueError("Invalid GitHub URL")
return path[0], path[1]

def github_api_request(endpoint: str) -> dict:
"""Make authenticated GitHub API request"""
headers = {
"Authorization": f"token {os.getenv('GITHUB_TOKEN')}",
"Accept": "application/vnd.github.v3+json"
}
response = requests.get(f"https://api.github.com{endpoint}", headers=headers)
response.raise_for_status()
return response.json()


@app.post("/repo-stats")
async def get_repo_stats(repo_url: str):
try:
owner, repo_name = parse_github_url(repo_url)
# Rest of your function...
# Get basic repo info
repo_info = github_api_request(f"/repos/{owner}/{repo_name}")

# Get contributors
contributors = github_api_request(f"/repos/{owner}/{repo_name}/contributors")

# Get pull requests
prs = github_api_request(f"/repos/{owner}/{repo_name}/pulls?state=all")

# Get issues
issues = github_api_request(f"/repos/{owner}/{repo_name}/issues?state=all")

community_profile = github_api_request(f"/repos/{owner}/{repo_name}/community/profile")

# Recent commits (last 5)
commits = github_api_request(f"/repos/{owner}/{repo_name}/commits?per_page=5")

code_frequency = github_api_request(f"/repos/{owner}/{repo_name}/stats/code_frequency")

return {
"name": repo_info["full_name"],
"stars": repo_info["stargazers_count"],
"forks": repo_info["forks_count"],
"watchers": repo_info["subscribers_count"],
"created_at": repo_info["created_at"],
"updated_at": repo_info["updated_at"],
# Licensing and topics
# "license": repo_info.get("license", {}).get("spdx_id", "No License"),

"topics": repo_info.get("topics", []),

"contributors": [{
"login": c["login"],
"contributions": c["contributions"],
"avatar_url": c["avatar_url"]
} for c in contributors],
"recent_commits": [{
"sha": commit["sha"][:7],
"author": commit["commit"]["author"]["name"],
"message": commit["commit"]["message"],
"date": commit["commit"]["author"]["date"]
} for commit in commits],


"community": {
"health_percentage": community_profile["health_percentage"],
"code_of_conduct": community_profile.get("files", {}).get("code_of_conduct") is not None,
"license": community_profile.get("files", {}).get("license") is not None,
"readme": community_profile.get("files", {}).get("readme") is not None
},
# Issues
"issues": {
"total": len(issues),
"open": sum(1 for issue in issues if issue["state"] == "open"),
"closed": sum(1 for issue in issues if issue["state"] == "closed"),
"labels": list({label["name"] for issue in issues for label in issue["labels"]})
},

# Code statistics
"code_activity": {
"weekly_commits": len(code_frequency) if isinstance(code_frequency, list) else 0,
"total_additions": sum(week[1] for week in code_frequency) if isinstance(code_frequency, list) else 0,
"total_deletions": sum(abs(week[2]) for week in code_frequency) if isinstance(code_frequency, list) else 0
},

# Pull Requests
"pull_requests": {
"total": len(prs),
"merged": sum(1 for pr in prs if pr["merged_at"]),
"draft": sum(1 for pr in prs if pr["draft"]),
"by_state": {
"open": sum(1 for pr in prs if pr["state"] == "open"),
"closed": sum(1 for pr in prs if pr["state"] == "closed")
}
},
}

except requests.HTTPError as e:
raise HTTPException(status_code=e.response.status_code,
detail="GitHub API error")
except ValueError:
raise HTTPException(status_code=400,
detail="Invalid GitHub URL format")
17 changes: 14 additions & 3 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
from fastapi import FastAPI
from routes import router
from fastapi.middleware.cors import CORSMiddleware
from app.routes import router
import uvicorn

app = FastAPI()
# Include GitHub webhook routes
app.include_router(router)

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)


app.include_router(router, prefix="/api")

if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
Expand Down
Loading