""" 项目Git仓库管理 API """ from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, update, func from app.core.database import get_db from app.core.deps import get_current_user from app.models.user import User from app.models.git_repo import ProjectGitRepo from app.schemas.git_repo import GitRepoCreate, GitRepoUpdate, GitRepoResponse, GitRepoTestRequest from app.services.git_service import git_service from app.schemas.response import success_response from app.services.log_service import log_service from app.services.project_service import require_project_read_access, require_project_roles from app.core.enums import OperationType router = APIRouter() def _repo_response(repo: ProjectGitRepo) -> dict: data = GitRepoResponse.from_orm(repo).dict() data["token"] = None data["has_token"] = bool(repo.token) return data @router.get("/projects/{project_id}/git-repos", response_model=dict) async def get_project_git_repos( project_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """获取项目的Git仓库列表""" await require_project_read_access(db, project_id, current_user) result = await db.execute( select(ProjectGitRepo).where(ProjectGitRepo.project_id == project_id).order_by(ProjectGitRepo.created_at) ) repos = result.scalars().all() # 隐藏 token data = [] for repo in repos: data.append(_repo_response(repo)) return success_response(data=data) @router.post("/projects/{project_id}/git-repos", response_model=dict) async def create_git_repo( project_id: int, repo_in: GitRepoCreate, request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """添加Git仓库""" await require_project_roles( db, project_id, current_user, allowed_roles=["admin", "editor"], ) # 如果是设为默认,先取消其他默认 if repo_in.is_default: await db.execute( update(ProjectGitRepo) .where(ProjectGitRepo.project_id == project_id) .values(is_default=0) ) # 检查是否是第一个仓库,如果是,强制设为默认 result = await db.execute(select(func.count()).select_from(ProjectGitRepo).where(ProjectGitRepo.project_id == project_id)) count = result.scalar() is_default = repo_in.is_default if count == 0: is_default = 1 db_repo = ProjectGitRepo( project_id=project_id, name=repo_in.name, repo_url=repo_in.repo_url, branch=repo_in.branch, username=repo_in.username, token=repo_in.token, is_default=is_default, sync_path="" ) db.add(db_repo) await db.commit() await db.refresh(db_repo) return success_response(data=_repo_response(db_repo), message="Git仓库添加成功") @router.post("/projects/{project_id}/git-repos/test-connection", response_model=dict) async def test_git_repo_connection( project_id: int, repo_in: GitRepoTestRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Test remote repository access without saving configuration or changing project files.""" await require_project_roles( db, project_id, current_user, allowed_roles=["admin", "editor"], ) saved_repo = None if repo_in.repo_id: result = await db.execute( select(ProjectGitRepo).where( ProjectGitRepo.id == repo_in.repo_id, ProjectGitRepo.project_id == project_id, ) ) saved_repo = result.scalar_one_or_none() if not saved_repo: raise HTTPException(status_code=404, detail="仓库不存在") repo_url = repo_in.repo_url or (saved_repo.repo_url if saved_repo else None) branch = repo_in.branch or (saved_repo.branch if saved_repo else "main") username = repo_in.username if repo_in.username is not None else (saved_repo.username if saved_repo else None) token = repo_in.token or (saved_repo.token if saved_repo else None) success, message = await git_service.test_connection(repo_url, branch, username, token) if not success: raise HTTPException(status_code=400, detail=f"远端仓库认证失败:{message}") return success_response(message="远端仓库认证成功") @router.put("/projects/{project_id}/git-repos/{repo_id}", response_model=dict) async def update_git_repo( project_id: int, repo_id: int, repo_in: GitRepoUpdate, request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """更新Git仓库""" await require_project_roles( db, project_id, current_user, allowed_roles=["admin", "editor"], ) result = await db.execute(select(ProjectGitRepo).where(ProjectGitRepo.id == repo_id, ProjectGitRepo.project_id == project_id)) repo = result.scalar_one_or_none() if not repo: raise HTTPException(status_code=404, detail="仓库不存在") # 如果设为默认,取消其他默认 if repo_in.is_default == 1: await db.execute( update(ProjectGitRepo) .where(ProjectGitRepo.project_id == project_id) .values(is_default=0) ) update_data = repo_in.dict(exclude_unset=True) # token 不回显;编辑时留空表示继续使用已保存凭据。 if not update_data.get("token"): update_data.pop("token", None) for field, value in update_data.items(): setattr(repo, field, value) await db.commit() await db.refresh(repo) return success_response(data=_repo_response(repo), message="更新成功") @router.delete("/projects/{project_id}/git-repos/{repo_id}", response_model=dict) async def delete_git_repo( project_id: int, repo_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): """删除Git仓库""" await require_project_roles( db, project_id, current_user, allowed_roles=["admin", "editor"], ) result = await db.execute(select(ProjectGitRepo).where(ProjectGitRepo.id == repo_id, ProjectGitRepo.project_id == project_id)) repo = result.scalar_one_or_none() if not repo: raise HTTPException(status_code=404, detail="仓库不存在") await db.delete(repo) await db.commit() return success_response(message="删除成功")