735 lines
24 KiB
Python
735 lines
24 KiB
Python
"""
|
||
项目管理相关 API
|
||
"""
|
||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy import select, or_
|
||
from typing import List
|
||
import uuid
|
||
|
||
from app.core.database import get_db
|
||
from app.core.deps import get_current_user
|
||
from app.models.user import User
|
||
from app.models.project import Project, ProjectMember
|
||
from app.models.git_repo import ProjectGitRepo
|
||
from app.schemas.project import (
|
||
ProjectCreate,
|
||
ProjectUpdate,
|
||
ProjectResponse,
|
||
ProjectMemberAdd,
|
||
ProjectMemberUpdate,
|
||
ProjectMemberResponse,
|
||
ProjectShareSettings,
|
||
ProjectShareInfo,
|
||
)
|
||
from app.schemas.response import success_response
|
||
from app.services.storage import storage_service
|
||
from app.services.log_service import log_service
|
||
from app.services.git_service import git_service
|
||
from app.services.notification_service import notification_service
|
||
from app.core.enums import OperationType, ResourceType
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/", response_model=dict)
|
||
async def get_my_projects(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""获取我的项目列表(包括创建的和协作的)"""
|
||
# 查询我创建的项目
|
||
owned_result = await db.execute(
|
||
select(Project).where(Project.owner_id == current_user.id, Project.status == 1)
|
||
)
|
||
owned_projects = owned_result.scalars().all()
|
||
|
||
# 查询我协作的项目
|
||
member_result = await db.execute(
|
||
select(Project)
|
||
.join(ProjectMember, ProjectMember.project_id == Project.id)
|
||
.where(
|
||
ProjectMember.user_id == current_user.id,
|
||
Project.owner_id != current_user.id,
|
||
Project.status == 1
|
||
)
|
||
)
|
||
member_projects = member_result.scalars().all()
|
||
|
||
# 合并结果
|
||
all_projects = owned_projects + member_projects
|
||
projects_data = [ProjectResponse.from_orm(p).dict() for p in all_projects]
|
||
|
||
return success_response(data=projects_data)
|
||
|
||
|
||
@router.get("/my", response_model=dict)
|
||
async def get_owned_projects(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""获取我创建的项目列表"""
|
||
result = await db.execute(
|
||
select(Project).where(Project.owner_id == current_user.id, Project.status == 1)
|
||
)
|
||
projects = result.scalars().all()
|
||
projects_data = [ProjectResponse.from_orm(p).dict() for p in projects]
|
||
return success_response(data=projects_data)
|
||
|
||
|
||
@router.get("/shared", response_model=dict)
|
||
async def get_shared_projects(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""获取我参与的项目列表(不包括我创建的)"""
|
||
result = await db.execute(
|
||
select(Project, User, ProjectMember)
|
||
.join(ProjectMember, ProjectMember.project_id == Project.id)
|
||
.join(User, User.id == Project.owner_id)
|
||
.where(
|
||
ProjectMember.user_id == current_user.id,
|
||
Project.owner_id != current_user.id,
|
||
Project.status == 1
|
||
)
|
||
)
|
||
projects_with_info = result.all()
|
||
|
||
projects_data = []
|
||
for project, owner, member in projects_with_info:
|
||
project_dict = ProjectResponse.from_orm(project).dict()
|
||
project_dict['owner_name'] = owner.username
|
||
project_dict['owner_nickname'] = owner.nickname
|
||
project_dict['user_role'] = member.role # 添加用户角色
|
||
projects_data.append(project_dict)
|
||
|
||
return success_response(data=projects_data)
|
||
|
||
|
||
@router.post("/", response_model=dict)
|
||
async def create_project(
|
||
project_in: ProjectCreate,
|
||
request: Request,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""创建新项目"""
|
||
# 生成 UUID 作为存储键
|
||
storage_key = str(uuid.uuid4())
|
||
|
||
# 创建项目记录
|
||
db_project = Project(
|
||
name=project_in.name,
|
||
description=project_in.description,
|
||
storage_key=storage_key,
|
||
owner_id=current_user.id,
|
||
is_public=project_in.is_public,
|
||
status=1,
|
||
)
|
||
db.add(db_project)
|
||
await db.commit()
|
||
await db.refresh(db_project)
|
||
|
||
# 创建物理文件夹结构
|
||
try:
|
||
storage_service.create_project_structure(storage_key)
|
||
except Exception as e:
|
||
# 如果文件夹创建失败,回滚数据库记录
|
||
await db.delete(db_project)
|
||
await db.commit()
|
||
raise HTTPException(status_code=500, detail=f"项目文件夹创建失败: {str(e)}")
|
||
|
||
# 添加项目所有者为管理员成员
|
||
db_member = ProjectMember(
|
||
project_id=db_project.id,
|
||
user_id=current_user.id,
|
||
role="admin",
|
||
)
|
||
db.add(db_member)
|
||
await db.commit()
|
||
|
||
# 记录操作日志
|
||
await log_service.log_project_operation(
|
||
db=db,
|
||
operation_type=OperationType.CREATE_PROJECT,
|
||
project_id=db_project.id,
|
||
user=current_user,
|
||
detail={"project_name": project_in.name},
|
||
request=request,
|
||
)
|
||
|
||
project_data = ProjectResponse.from_orm(db_project)
|
||
return success_response(data=project_data.dict(), message="项目创建成功")
|
||
|
||
|
||
@router.get("/{project_id}", response_model=dict)
|
||
async def get_project(
|
||
project_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""获取项目详情"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 检查权限(项目所有者或成员可访问)
|
||
if project.owner_id != current_user.id:
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id
|
||
)
|
||
)
|
||
member = member_result.scalar_one_or_none()
|
||
if not member and project.is_public != 1:
|
||
raise HTTPException(status_code=403, detail="无权访问该项目")
|
||
|
||
# 增加访问次数
|
||
project.visit_count += 1
|
||
await db.commit()
|
||
|
||
project_data = ProjectResponse.from_orm(project)
|
||
return success_response(data=project_data.dict())
|
||
|
||
|
||
@router.put("/{project_id}", response_model=dict)
|
||
async def update_project(
|
||
project_id: int,
|
||
project_in: ProjectUpdate,
|
||
request: Request,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""更新项目信息"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 只有项目所有者可以更新
|
||
if project.owner_id != current_user.id:
|
||
raise HTTPException(status_code=403, detail="无权修改该项目")
|
||
|
||
# 更新字段
|
||
update_data = project_in.dict(exclude_unset=True)
|
||
for field, value in update_data.items():
|
||
setattr(project, field, value)
|
||
|
||
await db.commit()
|
||
await db.refresh(project)
|
||
|
||
# 记录操作日志
|
||
await log_service.log_project_operation(
|
||
db=db,
|
||
operation_type=OperationType.UPDATE_PROJECT,
|
||
project_id=project_id,
|
||
user=current_user,
|
||
detail={"updated_fields": list(update_data.keys())},
|
||
request=request,
|
||
)
|
||
|
||
project_data = ProjectResponse.from_orm(project)
|
||
return success_response(data=project_data.dict(), message="项目更新成功")
|
||
|
||
|
||
@router.delete("/{project_id}", response_model=dict)
|
||
async def delete_project(
|
||
project_id: int,
|
||
request: Request,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""删除项目"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 只有项目所有者可以删除
|
||
if project.owner_id != current_user.id:
|
||
raise HTTPException(status_code=403, detail="无权删除该项目")
|
||
|
||
# 检查项目目录下是否有文件(排除_assets目录和隐藏文件)
|
||
project_root = storage_service.get_secure_path(project.storage_key)
|
||
|
||
has_files = False
|
||
if project_root.exists() and project_root.is_dir():
|
||
for item in project_root.iterdir():
|
||
# 跳过 _assets 目录和以.开头的隐藏文件(如.DS_Store)
|
||
if item.name == "_assets" or item.name.startswith("."):
|
||
continue
|
||
has_files = True
|
||
break
|
||
|
||
if has_files:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail="项目目录下存在文件,无法删除。请先清空项目文件。"
|
||
)
|
||
|
||
# 删除物理目录
|
||
if project_root.exists():
|
||
import shutil
|
||
try:
|
||
shutil.rmtree(project_root)
|
||
except Exception as e:
|
||
raise HTTPException(
|
||
status_code=500,
|
||
detail=f"删除项目目录失败: {str(e)}"
|
||
)
|
||
|
||
project_name = project.name
|
||
|
||
# 删除数据库记录
|
||
await db.delete(project)
|
||
await db.commit()
|
||
|
||
# 记录操作日志
|
||
await log_service.log_project_operation(
|
||
db=db,
|
||
operation_type=OperationType.DELETE_PROJECT,
|
||
project_id=project_id,
|
||
user=current_user,
|
||
detail={"project_name": project_name},
|
||
request=request,
|
||
)
|
||
|
||
return success_response(message="项目已删除")
|
||
|
||
|
||
@router.get("/{project_id}/members", response_model=dict)
|
||
async def get_project_members(
|
||
project_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""获取项目成员列表"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 检查权限
|
||
if project.owner_id != current_user.id:
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id
|
||
)
|
||
)
|
||
member = member_result.scalar_one_or_none()
|
||
if not member:
|
||
raise HTTPException(status_code=403, detail="无权访问该项目")
|
||
|
||
# 查询成员列表并关联用户信息
|
||
members_result = await db.execute(
|
||
select(ProjectMember, User)
|
||
.join(User, ProjectMember.user_id == User.id)
|
||
.where(ProjectMember.project_id == project_id)
|
||
)
|
||
members_with_users = members_result.all()
|
||
|
||
# 构建返回数据,包含用户名信息
|
||
members_data = []
|
||
for member, user in members_with_users:
|
||
members_data.append({
|
||
"id": member.id,
|
||
"project_id": member.project_id,
|
||
"user_id": member.user_id,
|
||
"role": member.role,
|
||
"joined_at": member.joined_at.isoformat() if member.joined_at else None,
|
||
"username": user.username,
|
||
"nickname": user.nickname,
|
||
})
|
||
|
||
return success_response(data=members_data)
|
||
|
||
|
||
@router.post("/{project_id}/members", response_model=dict)
|
||
async def add_project_member(
|
||
project_id: int,
|
||
member_in: ProjectMemberAdd,
|
||
request: Request,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""添加项目成员"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 只有项目所有者和管理员可以添加成员
|
||
if project.owner_id != current_user.id:
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id,
|
||
ProjectMember.role == "admin"
|
||
)
|
||
)
|
||
member = member_result.scalar_one_or_none()
|
||
if not member:
|
||
raise HTTPException(status_code=403, detail="无权添加成员")
|
||
|
||
# 检查用户是否已是成员
|
||
existing_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == member_in.user_id
|
||
)
|
||
)
|
||
existing_member = existing_result.scalar_one_or_none()
|
||
if existing_member:
|
||
raise HTTPException(status_code=400, detail="用户已是项目成员")
|
||
|
||
# 添加成员
|
||
db_member = ProjectMember(
|
||
project_id=project_id,
|
||
user_id=member_in.user_id,
|
||
role=member_in.role,
|
||
invited_by=current_user.id,
|
||
)
|
||
db.add(db_member)
|
||
await db.commit()
|
||
await db.refresh(db_member)
|
||
|
||
# 发送通知给被邀请人
|
||
await notification_service.create_notification(
|
||
db=db,
|
||
user_id=member_in.user_id,
|
||
title=f"新项目协作邀请",
|
||
content=f"用户 {current_user.nickname or current_user.username} 邀请您参与项目 [{project.name}] 的协作。",
|
||
category="collaboration",
|
||
link=f"/projects/{project_id}/docs",
|
||
type="info"
|
||
)
|
||
await db.commit()
|
||
|
||
# 记录操作日志
|
||
await log_service.log_member_operation(
|
||
db=db,
|
||
operation_type=OperationType.ADD_MEMBER,
|
||
project_id=project_id,
|
||
target_user_id=member_in.user_id,
|
||
user=current_user,
|
||
detail={"role": member_in.role},
|
||
request=request,
|
||
)
|
||
|
||
member_data = ProjectMemberResponse.from_orm(db_member)
|
||
return success_response(data=member_data.dict(), message="成员添加成功")
|
||
|
||
|
||
@router.delete("/{project_id}/members/{user_id}", response_model=dict)
|
||
async def remove_project_member(
|
||
project_id: int,
|
||
user_id: int,
|
||
request: Request,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""删除项目成员"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 只有项目所有者和管理员可以删除成员
|
||
if project.owner_id != current_user.id:
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id,
|
||
ProjectMember.role == "admin"
|
||
)
|
||
)
|
||
member = member_result.scalar_one_or_none()
|
||
if not member:
|
||
raise HTTPException(status_code=403, detail="无权删除成员")
|
||
|
||
# 不能删除项目所有者
|
||
if user_id == project.owner_id:
|
||
raise HTTPException(status_code=400, detail="不能删除项目所有者")
|
||
|
||
# 查询要删除的成员
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == user_id
|
||
)
|
||
)
|
||
member = member_result.scalar_one_or_none()
|
||
|
||
if not member:
|
||
raise HTTPException(status_code=404, detail="成员不存在")
|
||
|
||
# 删除成员
|
||
await db.delete(member)
|
||
await db.commit()
|
||
|
||
# 记录操作日志
|
||
await log_service.log_member_operation(
|
||
db=db,
|
||
operation_type=OperationType.REMOVE_MEMBER,
|
||
project_id=project_id,
|
||
target_user_id=user_id,
|
||
user=current_user,
|
||
request=request,
|
||
)
|
||
|
||
return success_response(message="成员删除成功")
|
||
|
||
|
||
@router.get("/{project_id}/share", response_model=dict)
|
||
async def get_project_share_info(
|
||
project_id: int,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""获取项目分享信息"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 检查是否是项目所有者或成员
|
||
is_owner = project.owner_id == current_user.id
|
||
if not is_owner:
|
||
# 检查是否是项目成员
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id
|
||
)
|
||
)
|
||
member = member_result.scalar_one_or_none()
|
||
if not member:
|
||
raise HTTPException(status_code=403, detail="无权访问该项目")
|
||
|
||
# 构建分享链接
|
||
share_url = f"/preview/{project_id}"
|
||
|
||
# 只有项目所有者可以看到实际密码,成员只能知道是否设置了密码
|
||
share_info = ProjectShareInfo(
|
||
share_url=share_url,
|
||
has_password=bool(project.access_pass),
|
||
access_pass=project.access_pass if is_owner else None
|
||
)
|
||
|
||
return success_response(data=share_info.dict())
|
||
|
||
|
||
@router.post("/{project_id}/share/settings", response_model=dict)
|
||
async def update_share_settings(
|
||
project_id: int,
|
||
settings: ProjectShareSettings,
|
||
request: Request,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""更新分享设置(设置或取消访问密码)"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 只有项目所有者可以修改分享设置
|
||
if project.owner_id != current_user.id:
|
||
raise HTTPException(status_code=403, detail="只有项目所有者可以修改分享设置")
|
||
|
||
# 更新访问密码
|
||
project.access_pass = settings.access_pass
|
||
await db.commit()
|
||
|
||
# 记录操作日志
|
||
await log_service.log_operation(
|
||
db=db,
|
||
operation_type=OperationType.UPDATE_SHARE_SETTINGS,
|
||
resource_type=ResourceType.SHARE,
|
||
user=current_user,
|
||
resource_id=project_id,
|
||
detail={
|
||
"has_password": bool(settings.access_pass),
|
||
"project_name": project.name,
|
||
},
|
||
request=request,
|
||
)
|
||
|
||
message = "访问密码已取消" if not settings.access_pass else "访问密码已设置"
|
||
return success_response(message=message)
|
||
|
||
|
||
@router.post("/{project_id}/git/pull", response_model=dict)
|
||
async def git_pull(
|
||
project_id: int,
|
||
request: Request,
|
||
repo_id: int = None,
|
||
force: bool = False,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""执行 Git Pull"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 权限检查:需要是所有者或管理员/编辑者
|
||
if project.owner_id != current_user.id:
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id,
|
||
ProjectMember.role.in_(['admin', 'editor'])
|
||
)
|
||
)
|
||
if not member_result.scalar_one_or_none():
|
||
raise HTTPException(status_code=403, detail="无权执行Git操作")
|
||
|
||
# 获取Git仓库配置
|
||
query = select(ProjectGitRepo).where(ProjectGitRepo.project_id == project_id)
|
||
if repo_id:
|
||
query = query.where(ProjectGitRepo.id == repo_id)
|
||
else:
|
||
# 优先使用默认仓库,然后按创建时间倒序(最新的)
|
||
query = query.order_by(ProjectGitRepo.is_default.desc(), ProjectGitRepo.created_at.desc())
|
||
|
||
result = await db.execute(query)
|
||
repos = result.scalars().all()
|
||
|
||
if not repos:
|
||
raise HTTPException(status_code=400, detail="未配置Git仓库")
|
||
|
||
target_repo = repos[0]
|
||
|
||
project_path = storage_service.get_secure_path(project.storage_key)
|
||
|
||
success, msg = await git_service.pull(
|
||
project_path=project_path,
|
||
repo_url=target_repo.repo_url,
|
||
branch=target_repo.branch or "main",
|
||
username=target_repo.username,
|
||
token=target_repo.token,
|
||
force=force
|
||
)
|
||
|
||
if not success:
|
||
raise HTTPException(status_code=500, detail=f"Git Pull失败: {msg}")
|
||
|
||
# 记录日志
|
||
await log_service.log_project_operation(
|
||
db=db,
|
||
operation_type=OperationType.GIT_PULL,
|
||
project_id=project_id,
|
||
user=current_user,
|
||
detail={"repo": target_repo.repo_url, "branch": target_repo.branch, "repo_alias": target_repo.name, "force": force},
|
||
request=request,
|
||
)
|
||
|
||
# 发送通知给其他成员
|
||
await notification_service.notify_project_members(
|
||
db=db,
|
||
project_id=project_id,
|
||
exclude_user_id=current_user.id,
|
||
title=f"项目文档已通过 Git 同步",
|
||
content=f"{current_user.nickname or current_user.username} 执行了 Git Pull,项目 [{project.name}] 的内容已从远程仓库同步更新。",
|
||
link=f"/projects/{project_id}/docs",
|
||
category="project"
|
||
)
|
||
await db.commit()
|
||
|
||
return success_response(message=f"Git Pull 成功 ({target_repo.name})")
|
||
|
||
|
||
@router.post("/{project_id}/git/push", response_model=dict)
|
||
async def git_push(
|
||
project_id: int,
|
||
request: Request,
|
||
repo_id: int = None,
|
||
force: bool = False,
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""执行 Git Push"""
|
||
# 查询项目
|
||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||
project = result.scalar_one_or_none()
|
||
|
||
if not project:
|
||
raise HTTPException(status_code=404, detail="项目不存在")
|
||
|
||
# 权限检查:需要是所有者或管理员/编辑者
|
||
if project.owner_id != current_user.id:
|
||
member_result = await db.execute(
|
||
select(ProjectMember).where(
|
||
ProjectMember.project_id == project_id,
|
||
ProjectMember.user_id == current_user.id,
|
||
ProjectMember.role.in_(['admin', 'editor'])
|
||
)
|
||
)
|
||
if not member_result.scalar_one_or_none():
|
||
raise HTTPException(status_code=403, detail="无权执行Git操作")
|
||
|
||
# 获取Git仓库配置
|
||
query = select(ProjectGitRepo).where(ProjectGitRepo.project_id == project_id)
|
||
if repo_id:
|
||
query = query.where(ProjectGitRepo.id == repo_id)
|
||
else:
|
||
# 优先使用默认仓库
|
||
query = query.order_by(ProjectGitRepo.is_default.desc(), ProjectGitRepo.created_at.desc())
|
||
|
||
result = await db.execute(query)
|
||
repos = result.scalars().all()
|
||
|
||
if not repos:
|
||
raise HTTPException(status_code=400, detail="未配置Git仓库")
|
||
|
||
target_repo = repos[0]
|
||
|
||
project_path = storage_service.get_secure_path(project.storage_key)
|
||
|
||
success, msg = await git_service.push(
|
||
project_path=project_path,
|
||
repo_url=target_repo.repo_url,
|
||
branch=target_repo.branch or "main",
|
||
username=target_repo.username,
|
||
token=target_repo.token,
|
||
force=force
|
||
)
|
||
|
||
if not success:
|
||
raise HTTPException(status_code=500, detail=f"Git Push失败: {msg}")
|
||
|
||
# 记录日志
|
||
await log_service.log_project_operation(
|
||
db=db,
|
||
operation_type=OperationType.GIT_PUSH,
|
||
project_id=project_id,
|
||
user=current_user,
|
||
detail={"repo": target_repo.repo_url, "branch": target_repo.branch, "repo_alias": target_repo.name, "force": force},
|
||
request=request,
|
||
)
|
||
|
||
return success_response(message=f"Git Push 成功 ({target_repo.name})")
|