/ backend / api / repos.py
repos.py
  1  """Repository browsing API - Direct Radicle integration."""
  2  
  3  from typing import Optional
  4  
  5  from fastapi import APIRouter, HTTPException
  6  from pydantic import BaseModel
  7  
  8  from services.radicle import get_radicle_service, RadicleRepo, FileEntry
  9  
 10  router = APIRouter()
 11  
 12  
 13  class RepoResponse(BaseModel):
 14      """Repository response."""
 15  
 16      rid: str
 17      name: str
 18      description: str
 19      default_branch: str
 20      delegates: list[str]
 21  
 22  
 23  class FileEntryResponse(BaseModel):
 24      """File entry response."""
 25  
 26      name: str
 27      path: str
 28      type: str
 29      size: Optional[int] = None
 30  
 31  
 32  class TreeResponse(BaseModel):
 33      """File tree response."""
 34  
 35      rid: str
 36      ref: str
 37      path: str
 38      entries: list[FileEntryResponse]
 39  
 40  
 41  class BlobResponse(BaseModel):
 42      """File content response."""
 43  
 44      rid: str
 45      ref: str
 46      path: str
 47      content: str
 48  
 49  
 50  @router.get("", response_model=list[RepoResponse])
 51  async def list_repos():
 52      """List all tracked Radicle repositories."""
 53      service = get_radicle_service()
 54      repos = await service.list_repos()
 55      return [
 56          RepoResponse(
 57              rid=r.rid,
 58              name=r.name,
 59              description=r.description,
 60              default_branch=r.default_branch,
 61              delegates=r.delegates,
 62          )
 63          for r in repos
 64      ]
 65  
 66  
 67  @router.get("/{rid:path}/info", response_model=RepoResponse)
 68  async def get_repo(rid: str):
 69      """Get repository details by Radicle ID."""
 70      service = get_radicle_service()
 71      repo = await service.get_repo(rid)
 72      if not repo:
 73          raise HTTPException(status_code=404, detail="Repository not found")
 74      return RepoResponse(
 75          rid=repo.rid,
 76          name=repo.name,
 77          description=repo.description,
 78          default_branch=repo.default_branch,
 79          delegates=repo.delegates,
 80      )
 81  
 82  
 83  @router.get("/{rid:path}/tree/{ref}", response_model=TreeResponse)
 84  async def get_tree(rid: str, ref: str, path: str = ""):
 85      """Get file tree for repository at given ref and path."""
 86      service = get_radicle_service()
 87      try:
 88          entries = await service.get_tree(rid, ref, path)
 89          return TreeResponse(
 90              rid=rid,
 91              ref=ref,
 92              path=path,
 93              entries=[
 94                  FileEntryResponse(name=e.name, path=e.path, type=e.type, size=e.size)
 95                  for e in entries
 96              ],
 97          )
 98      except RuntimeError as e:
 99          raise HTTPException(status_code=500, detail=str(e))
100  
101  
102  @router.get("/{rid:path}/blob/{ref}/{file_path:path}", response_model=BlobResponse)
103  async def get_blob(rid: str, ref: str, file_path: str):
104      """Get file content from repository."""
105      service = get_radicle_service()
106      content = await service.get_blob(rid, ref, file_path)
107      if content is None:
108          raise HTTPException(status_code=404, detail="File not found")
109      return BlobResponse(rid=rid, ref=ref, path=file_path, content=content)