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)