Files
blackroad-operating-system/backend/app/routers/github.py
Claude b22c95b639 Add comprehensive service integrations and games to BlackRoad OS
This massive update transforms BlackRoad OS into a complete virtual operating
system with modern cloud integrations and retro-styled games.

New API Integrations:
- DigitalOcean: Droplet management, spaces, regions, and account info
- GitHub: Repo browsing, commits, PRs, issues, code search, notifications
- Hugging Face: Model browser, inference API, datasets, spaces, trending
- VS Code: Monaco editor integration with file tree and syntax highlighting

Games (SimCity/Sims style):
- Road City: City builder with zones, utilities, services, and resources
- Road Life: Life simulator with characters, needs, skills, and jobs
- RoadCraft: Voxel world builder with block placement

Enhanced Features:
- RoadView Browser: Web proxy with bookmarks, history, tabs, and search
- Device Manager: SSH connections, remote command execution, deployments
- Unified Dashboard: Comprehensive overview of all services and stats

Backend Enhancements:
- 7 new API routers with 100+ endpoints
- Enhanced device management with SSH and deployment capabilities
- Service health monitoring and activity tracking
- Support for DigitalOcean, GitHub, and Hugging Face tokens

Configuration:
- Added environment variables for new API tokens
- All integrations properly registered in main.py
- Comprehensive error handling and validation

This brings the total to 15+ integrated services creating a complete
retro-styled virtual operating system with AI, cloud, games, and dev tools.
2025-11-16 08:33:00 +00:00

434 lines
13 KiB
Python

"""
GitHub Integration API Router
Provides integration with GitHub:
- Repository browsing
- Commits history
- Pull requests
- Issues tracking
- File browsing
- Code search
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List, Optional
import httpx
import os
import base64
from ..database import get_db
from ..auth import get_current_user
from ..models import User
from pydantic import BaseModel
router = APIRouter(prefix="/api/github", tags=["github"])
# GitHub API configuration
GITHUB_API_URL = "https://api.github.com"
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
class RepoCreate(BaseModel):
name: str
description: Optional[str] = None
private: bool = False
auto_init: bool = True
class IssueCreate(BaseModel):
title: str
body: Optional[str] = None
labels: Optional[List[str]] = None
@router.get("/user")
async def get_github_user(
current_user: User = Depends(get_current_user)
):
"""Get authenticated GitHub user information"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(f"{GITHUB_API_URL}/user", headers=headers)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch GitHub user")
return response.json()
@router.get("/repos")
async def list_repositories(
page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user)
):
"""List user's GitHub repositories"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/user/repos",
headers=headers,
params={"page": page, "per_page": per_page, "sort": "updated"}
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch repositories")
repos = response.json()
return {
"repositories": repos,
"total": len(repos),
"page": page
}
@router.post("/repos")
async def create_repository(
repo_data: RepoCreate,
current_user: User = Depends(get_current_user)
):
"""Create a new GitHub repository"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json",
"Content-Type": "application/json"
}
payload = repo_data.dict()
response = await client.post(
f"{GITHUB_API_URL}/user/repos",
headers=headers,
json=payload
)
if response.status_code != 201:
raise HTTPException(status_code=response.status_code, detail="Failed to create repository")
return response.json()
@router.get("/repos/{owner}/{repo}")
async def get_repository(
owner: str,
repo: str,
current_user: User = Depends(get_current_user)
):
"""Get repository details"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/repos/{owner}/{repo}",
headers=headers
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Repository not found")
return response.json()
@router.get("/repos/{owner}/{repo}/commits")
async def list_commits(
owner: str,
repo: str,
page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user)
):
"""List repository commits"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/repos/{owner}/{repo}/commits",
headers=headers,
params={"page": page, "per_page": per_page}
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch commits")
return {
"commits": response.json(),
"page": page
}
@router.get("/repos/{owner}/{repo}/pulls")
async def list_pull_requests(
owner: str,
repo: str,
state: str = Query("open", regex="^(open|closed|all)$"),
page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user)
):
"""List pull requests"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls",
headers=headers,
params={"state": state, "page": page, "per_page": per_page}
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch pull requests")
return {
"pull_requests": response.json(),
"state": state,
"page": page
}
@router.get("/repos/{owner}/{repo}/issues")
async def list_issues(
owner: str,
repo: str,
state: str = Query("open", regex="^(open|closed|all)$"),
page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user)
):
"""List repository issues"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/repos/{owner}/{repo}/issues",
headers=headers,
params={"state": state, "page": page, "per_page": per_page}
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch issues")
return {
"issues": response.json(),
"state": state,
"page": page
}
@router.post("/repos/{owner}/{repo}/issues")
async def create_issue(
owner: str,
repo: str,
issue_data: IssueCreate,
current_user: User = Depends(get_current_user)
):
"""Create a new issue"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json",
"Content-Type": "application/json"
}
payload = issue_data.dict(exclude_none=True)
response = await client.post(
f"{GITHUB_API_URL}/repos/{owner}/{repo}/issues",
headers=headers,
json=payload
)
if response.status_code != 201:
raise HTTPException(status_code=response.status_code, detail="Failed to create issue")
return response.json()
@router.get("/repos/{owner}/{repo}/contents/{path:path}")
async def get_file_contents(
owner: str,
repo: str,
path: str,
current_user: User = Depends(get_current_user)
):
"""Get file or directory contents"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/repos/{owner}/{repo}/contents/{path}",
headers=headers
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="File or directory not found")
data = response.json()
# If it's a file, decode the content
if isinstance(data, dict) and data.get("type") == "file":
try:
content = base64.b64decode(data.get("content", "")).decode("utf-8")
data["decoded_content"] = content
except:
data["decoded_content"] = None
return data
@router.get("/repos/{owner}/{repo}/branches")
async def list_branches(
owner: str,
repo: str,
current_user: User = Depends(get_current_user)
):
"""List repository branches"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/repos/{owner}/{repo}/branches",
headers=headers
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch branches")
return {"branches": response.json()}
@router.get("/search/repositories")
async def search_repositories(
q: str = Query(..., min_length=1),
page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user)
):
"""Search GitHub repositories"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/search/repositories",
headers=headers,
params={"q": q, "page": page, "per_page": per_page}
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Search failed")
return response.json()
@router.get("/search/code")
async def search_code(
q: str = Query(..., min_length=1),
page: int = Query(1, ge=1),
per_page: int = Query(30, ge=1, le=100),
current_user: User = Depends(get_current_user)
):
"""Search code across GitHub"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = await client.get(
f"{GITHUB_API_URL}/search/code",
headers=headers,
params={"q": q, "page": page, "per_page": per_page}
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Code search failed")
return response.json()
@router.get("/notifications")
async def get_notifications(
all: bool = Query(False),
participating: bool = Query(False),
current_user: User = Depends(get_current_user)
):
"""Get user's GitHub notifications"""
if not GITHUB_TOKEN:
raise HTTPException(status_code=400, detail="GitHub token not configured")
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
params = {}
if all:
params["all"] = "true"
if participating:
params["participating"] = "true"
response = await client.get(
f"{GITHUB_API_URL}/notifications",
headers=headers,
params=params
)
if response.status_code != 200:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch notifications")
return {"notifications": response.json()}