2025-11-26 21:11:12 +08:00
|
|
|
|
from typing import Any, List, Optional
|
2025-11-28 17:38:12 +08:00
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, UploadFile, File
|
|
|
|
|
|
from fastapi.responses import FileResponse
|
2025-11-26 21:11:12 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
from sqlalchemy.future import select
|
|
|
|
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
|
|
from pydantic import BaseModel
|
2025-12-09 17:47:34 +08:00
|
|
|
|
from datetime import datetime, timezone
|
2025-11-28 17:38:12 +08:00
|
|
|
|
import shutil
|
|
|
|
|
|
import os
|
|
|
|
|
|
import uuid
|
2025-12-06 20:47:28 +08:00
|
|
|
|
import json
|
2025-11-26 21:11:12 +08:00
|
|
|
|
|
|
|
|
|
|
from app.api import deps
|
|
|
|
|
|
from app.db.session import get_db, AsyncSessionLocal
|
|
|
|
|
|
from app.models.project import Project
|
|
|
|
|
|
from app.models.user import User
|
|
|
|
|
|
from app.models.audit import AuditTask, AuditIssue
|
|
|
|
|
|
from app.models.user_config import UserConfig
|
2025-12-06 20:47:28 +08:00
|
|
|
|
import zipfile
|
2025-12-10 18:46:33 +08:00
|
|
|
|
from app.services.scanner import scan_repo_task, get_github_files, get_gitlab_files, get_github_branches, get_gitlab_branches, should_exclude, is_text_file
|
2025-11-28 17:38:12 +08:00
|
|
|
|
from app.services.zip_storage import (
|
|
|
|
|
|
save_project_zip, load_project_zip, get_project_zip_meta,
|
|
|
|
|
|
delete_project_zip, has_project_zip
|
|
|
|
|
|
)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
# Schemas
|
|
|
|
|
|
class ProjectCreate(BaseModel):
|
|
|
|
|
|
name: str
|
2025-11-28 17:38:12 +08:00
|
|
|
|
source_type: Optional[str] = "repository" # 'repository' 或 'zip'
|
2025-11-26 21:11:12 +08:00
|
|
|
|
repository_url: Optional[str] = None
|
2025-11-28 17:38:12 +08:00
|
|
|
|
repository_type: Optional[str] = "other" # github, gitlab, other
|
2025-11-26 21:11:12 +08:00
|
|
|
|
description: Optional[str] = None
|
|
|
|
|
|
default_branch: Optional[str] = "main"
|
|
|
|
|
|
programming_languages: Optional[List[str]] = None
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectUpdate(BaseModel):
|
|
|
|
|
|
name: Optional[str] = None
|
2025-11-28 17:38:12 +08:00
|
|
|
|
source_type: Optional[str] = None
|
2025-11-26 21:11:12 +08:00
|
|
|
|
repository_url: Optional[str] = None
|
|
|
|
|
|
repository_type: Optional[str] = None
|
|
|
|
|
|
description: Optional[str] = None
|
|
|
|
|
|
default_branch: Optional[str] = None
|
|
|
|
|
|
programming_languages: Optional[List[str]] = None
|
|
|
|
|
|
|
|
|
|
|
|
class OwnerSchema(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
email: Optional[str] = None
|
|
|
|
|
|
full_name: Optional[str] = None
|
|
|
|
|
|
avatar_url: Optional[str] = None
|
|
|
|
|
|
role: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectResponse(BaseModel):
|
|
|
|
|
|
id: str
|
|
|
|
|
|
name: str
|
|
|
|
|
|
description: Optional[str] = None
|
2025-11-28 17:38:12 +08:00
|
|
|
|
source_type: Optional[str] = "repository" # 'repository' 或 'zip'
|
2025-11-26 21:11:12 +08:00
|
|
|
|
repository_url: Optional[str] = None
|
2025-11-28 17:38:12 +08:00
|
|
|
|
repository_type: Optional[str] = None # github, gitlab, other
|
2025-11-26 21:11:12 +08:00
|
|
|
|
default_branch: Optional[str] = None
|
|
|
|
|
|
programming_languages: Optional[str] = None
|
|
|
|
|
|
owner_id: str
|
|
|
|
|
|
is_active: bool
|
|
|
|
|
|
created_at: datetime
|
|
|
|
|
|
updated_at: Optional[datetime] = None
|
|
|
|
|
|
owner: Optional[OwnerSchema] = None
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
class StatsResponse(BaseModel):
|
|
|
|
|
|
total_projects: int
|
|
|
|
|
|
active_projects: int
|
|
|
|
|
|
total_tasks: int
|
|
|
|
|
|
completed_tasks: int
|
|
|
|
|
|
total_issues: int
|
|
|
|
|
|
resolved_issues: int
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/", response_model=ProjectResponse)
|
|
|
|
|
|
async def create_project(
|
|
|
|
|
|
*,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
project_in: ProjectCreate,
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Create new project.
|
|
|
|
|
|
"""
|
|
|
|
|
|
import json
|
2025-11-28 17:38:12 +08:00
|
|
|
|
# 根据 source_type 设置默认值
|
|
|
|
|
|
source_type = project_in.source_type or "repository"
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
project = Project(
|
|
|
|
|
|
name=project_in.name,
|
2025-11-28 17:38:12 +08:00
|
|
|
|
source_type=source_type,
|
|
|
|
|
|
repository_url=project_in.repository_url if source_type == "repository" else None,
|
|
|
|
|
|
repository_type=project_in.repository_type or "other" if source_type == "repository" else "other",
|
2025-11-26 21:11:12 +08:00
|
|
|
|
description=project_in.description,
|
|
|
|
|
|
default_branch=project_in.default_branch or "main",
|
|
|
|
|
|
programming_languages=json.dumps(project_in.programming_languages or []),
|
|
|
|
|
|
owner_id=current_user.id
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(project)
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
await db.refresh(project)
|
|
|
|
|
|
return project
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/", response_model=List[ProjectResponse])
|
|
|
|
|
|
async def read_projects(
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
skip: int = 0,
|
|
|
|
|
|
limit: int = 100,
|
|
|
|
|
|
include_deleted: bool = False,
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
2025-11-28 01:11:21 +08:00
|
|
|
|
Retrieve projects for current user.
|
2025-11-26 21:11:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
query = select(Project).options(selectinload(Project.owner))
|
2025-11-28 01:11:21 +08:00
|
|
|
|
# 只返回当前用户的项目
|
|
|
|
|
|
query = query.where(Project.owner_id == current_user.id)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
if not include_deleted:
|
|
|
|
|
|
query = query.where(Project.is_active == True)
|
|
|
|
|
|
query = query.order_by(Project.created_at.desc()).offset(skip).limit(limit)
|
|
|
|
|
|
result = await db.execute(query)
|
|
|
|
|
|
return result.scalars().all()
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/deleted", response_model=List[ProjectResponse])
|
|
|
|
|
|
async def read_deleted_projects(
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Retrieve deleted (soft-deleted) projects for current user.
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
|
select(Project)
|
|
|
|
|
|
.options(selectinload(Project.owner))
|
|
|
|
|
|
.where(Project.owner_id == current_user.id)
|
|
|
|
|
|
.where(Project.is_active == False)
|
|
|
|
|
|
.order_by(Project.updated_at.desc())
|
|
|
|
|
|
)
|
|
|
|
|
|
return result.scalars().all()
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/stats", response_model=StatsResponse)
|
|
|
|
|
|
async def get_stats(
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
2025-11-28 01:11:21 +08:00
|
|
|
|
Get statistics for current user.
|
2025-11-26 21:11:12 +08:00
|
|
|
|
"""
|
2025-11-28 01:11:21 +08:00
|
|
|
|
# 只统计当前用户的项目
|
|
|
|
|
|
projects_result = await db.execute(
|
|
|
|
|
|
select(Project).where(Project.owner_id == current_user.id)
|
|
|
|
|
|
)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
projects = projects_result.scalars().all()
|
2025-11-28 01:11:21 +08:00
|
|
|
|
project_ids = [p.id for p in projects]
|
2025-11-26 21:11:12 +08:00
|
|
|
|
|
2025-11-28 01:11:21 +08:00
|
|
|
|
# 只统计当前用户项目的任务
|
|
|
|
|
|
tasks_result = await db.execute(
|
|
|
|
|
|
select(AuditTask).where(AuditTask.project_id.in_(project_ids)) if project_ids else select(AuditTask).where(False)
|
|
|
|
|
|
)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
tasks = tasks_result.scalars().all()
|
2025-11-28 01:11:21 +08:00
|
|
|
|
task_ids = [t.id for t in tasks]
|
2025-11-26 21:11:12 +08:00
|
|
|
|
|
2025-11-28 01:11:21 +08:00
|
|
|
|
# 只统计当前用户任务的问题
|
|
|
|
|
|
issues_result = await db.execute(
|
|
|
|
|
|
select(AuditIssue).where(AuditIssue.task_id.in_(task_ids)) if task_ids else select(AuditIssue).where(False)
|
|
|
|
|
|
)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
issues = issues_result.scalars().all()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"total_projects": len(projects),
|
|
|
|
|
|
"active_projects": len([p for p in projects if p.is_active]),
|
|
|
|
|
|
"total_tasks": len(tasks),
|
|
|
|
|
|
"completed_tasks": len([t for t in tasks if t.status == "completed"]),
|
|
|
|
|
|
"total_issues": len(issues),
|
|
|
|
|
|
"resolved_issues": len([i for i in issues if i.status == "resolved"]),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{id}", response_model=ProjectResponse)
|
|
|
|
|
|
async def read_project(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Get project by ID.
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
|
select(Project)
|
|
|
|
|
|
.options(selectinload(Project.owner))
|
|
|
|
|
|
.where(Project.id == id)
|
|
|
|
|
|
)
|
|
|
|
|
|
project = result.scalars().first()
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
2025-11-28 20:34:15 +08:00
|
|
|
|
|
|
|
|
|
|
# 检查权限:只有项目所有者可以查看
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权查看此项目")
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
return project
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/{id}", response_model=ProjectResponse)
|
|
|
|
|
|
async def update_project(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
*,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
project_in: ProjectUpdate,
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Update project.
|
|
|
|
|
|
"""
|
|
|
|
|
|
import json
|
|
|
|
|
|
result = await db.execute(select(Project).where(Project.id == id))
|
|
|
|
|
|
project = result.scalars().first()
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
2025-11-28 20:34:15 +08:00
|
|
|
|
# 检查权限:只有项目所有者可以更新
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权更新此项目")
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
update_data = project_in.model_dump(exclude_unset=True)
|
|
|
|
|
|
if "programming_languages" in update_data and update_data["programming_languages"] is not None:
|
|
|
|
|
|
update_data["programming_languages"] = json.dumps(update_data["programming_languages"])
|
|
|
|
|
|
|
|
|
|
|
|
for field, value in update_data.items():
|
|
|
|
|
|
setattr(project, field, value)
|
|
|
|
|
|
|
2025-12-09 17:47:34 +08:00
|
|
|
|
project.updated_at = datetime.now(timezone.utc)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
await db.commit()
|
|
|
|
|
|
await db.refresh(project)
|
|
|
|
|
|
return project
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{id}")
|
|
|
|
|
|
async def delete_project(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Soft delete project.
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await db.execute(select(Project).where(Project.id == id))
|
|
|
|
|
|
project = result.scalars().first()
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查权限:只有项目所有者可以删除
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权删除此项目")
|
|
|
|
|
|
|
|
|
|
|
|
project.is_active = False
|
2025-12-09 17:47:34 +08:00
|
|
|
|
project.updated_at = datetime.now(timezone.utc)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
await db.commit()
|
|
|
|
|
|
return {"message": "项目已删除"}
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{id}/restore")
|
|
|
|
|
|
async def restore_project(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Restore soft-deleted project.
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await db.execute(select(Project).where(Project.id == id))
|
|
|
|
|
|
project = result.scalars().first()
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查权限:只有项目所有者可以恢复
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权恢复此项目")
|
|
|
|
|
|
|
|
|
|
|
|
project.is_active = True
|
2025-12-09 17:47:34 +08:00
|
|
|
|
project.updated_at = datetime.now(timezone.utc)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
await db.commit()
|
|
|
|
|
|
return {"message": "项目已恢复"}
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{id}/permanent")
|
|
|
|
|
|
async def permanently_delete_project(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Permanently delete project.
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await db.execute(select(Project).where(Project.id == id))
|
|
|
|
|
|
project = result.scalars().first()
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查权限:只有项目所有者可以永久删除
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权永久删除此项目")
|
|
|
|
|
|
|
2025-11-28 18:01:43 +08:00
|
|
|
|
# 如果是ZIP类型项目,删除关联的ZIP文件和元数据
|
|
|
|
|
|
if project.source_type == "zip":
|
|
|
|
|
|
try:
|
|
|
|
|
|
await delete_project_zip(id)
|
|
|
|
|
|
print(f"[Project] 已删除项目 {id} 的ZIP文件")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"[Warning] 删除ZIP文件失败: {e}")
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
await db.delete(project)
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
return {"message": "项目已永久删除"}
|
|
|
|
|
|
|
2025-12-06 20:47:28 +08:00
|
|
|
|
|
|
|
|
|
|
@router.get("/{id}/files")
|
|
|
|
|
|
async def get_project_files(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
branch: Optional[str] = None,
|
2025-12-10 18:46:33 +08:00
|
|
|
|
exclude_patterns: Optional[str] = None,
|
2025-12-06 20:47:28 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Get list of files in the project.
|
2025-12-10 18:46:33 +08:00
|
|
|
|
可选参数:
|
|
|
|
|
|
- branch: 指定仓库分支(仅对仓库类型项目有效)
|
|
|
|
|
|
- exclude_patterns: JSON 格式的排除模式数组,如 ["node_modules/**", "*.log"]
|
2025-12-06 20:47:28 +08:00
|
|
|
|
"""
|
|
|
|
|
|
project = await db.get(Project, id)
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# Check permissions
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权查看此项目")
|
|
|
|
|
|
|
2025-12-10 18:46:33 +08:00
|
|
|
|
# 解析排除模式
|
|
|
|
|
|
parsed_exclude_patterns = []
|
|
|
|
|
|
if exclude_patterns:
|
|
|
|
|
|
try:
|
|
|
|
|
|
parsed_exclude_patterns = json.loads(exclude_patterns)
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2025-12-06 20:47:28 +08:00
|
|
|
|
files = []
|
|
|
|
|
|
|
|
|
|
|
|
if project.source_type == "zip":
|
|
|
|
|
|
# Handle ZIP project
|
|
|
|
|
|
zip_path = await load_project_zip(id)
|
|
|
|
|
|
print(f"📦 ZIP项目 {id} 文件路径: {zip_path}")
|
|
|
|
|
|
if not zip_path or not os.path.exists(zip_path):
|
|
|
|
|
|
print(f"⚠️ ZIP文件不存在: {zip_path}")
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
|
|
|
|
for file_info in zip_ref.infolist():
|
|
|
|
|
|
if not file_info.is_dir():
|
|
|
|
|
|
name = file_info.filename
|
2025-12-10 18:46:33 +08:00
|
|
|
|
# 使用统一的排除逻辑,支持用户自定义排除模式
|
|
|
|
|
|
if should_exclude(name, parsed_exclude_patterns):
|
|
|
|
|
|
continue
|
|
|
|
|
|
# 只显示支持的代码文件
|
|
|
|
|
|
if not is_text_file(name):
|
2025-12-06 20:47:28 +08:00
|
|
|
|
continue
|
|
|
|
|
|
files.append({"path": name, "size": file_info.file_size})
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Error reading zip file: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail="无法读取项目文件")
|
|
|
|
|
|
|
|
|
|
|
|
elif project.source_type == "repository":
|
|
|
|
|
|
# Handle Repository project
|
|
|
|
|
|
if not project.repository_url:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
# Get tokens from user config
|
|
|
|
|
|
from sqlalchemy.future import select
|
|
|
|
|
|
from app.core.encryption import decrypt_sensitive_data
|
|
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
|
|
|
|
|
|
SENSITIVE_OTHER_FIELDS = ['githubToken', 'gitlabToken']
|
|
|
|
|
|
|
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
|
select(UserConfig).where(UserConfig.user_id == current_user.id)
|
|
|
|
|
|
)
|
|
|
|
|
|
config = result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
|
|
github_token = settings.GITHUB_TOKEN
|
|
|
|
|
|
gitlab_token = settings.GITLAB_TOKEN
|
|
|
|
|
|
|
|
|
|
|
|
if config and config.other_config:
|
|
|
|
|
|
other_config = json.loads(config.other_config)
|
|
|
|
|
|
for field in SENSITIVE_OTHER_FIELDS:
|
|
|
|
|
|
if field in other_config and other_config[field]:
|
|
|
|
|
|
decrypted_val = decrypt_sensitive_data(other_config[field])
|
|
|
|
|
|
if field == 'githubToken':
|
|
|
|
|
|
github_token = decrypted_val
|
|
|
|
|
|
elif field == 'gitlabToken':
|
|
|
|
|
|
gitlab_token = decrypted_val
|
|
|
|
|
|
|
|
|
|
|
|
repo_type = project.repository_type or "other"
|
|
|
|
|
|
# 使用传入的 branch 参数,如果没有则使用项目默认分支
|
|
|
|
|
|
target_branch = branch or project.default_branch or "main"
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
if repo_type == "github":
|
2025-12-10 18:46:33 +08:00
|
|
|
|
# 传入用户自定义排除模式
|
|
|
|
|
|
repo_files = await get_github_files(project.repository_url, target_branch, github_token, parsed_exclude_patterns)
|
2025-12-06 20:47:28 +08:00
|
|
|
|
files = [{"path": f["path"], "size": 0} for f in repo_files]
|
|
|
|
|
|
elif repo_type == "gitlab":
|
2025-12-10 18:46:33 +08:00
|
|
|
|
# 传入用户自定义排除模式
|
|
|
|
|
|
repo_files = await get_gitlab_files(project.repository_url, target_branch, gitlab_token, parsed_exclude_patterns)
|
2025-12-06 20:47:28 +08:00
|
|
|
|
files = [{"path": f["path"], "size": 0} for f in repo_files]
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Error fetching repo files: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"无法获取仓库文件: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
return files
|
|
|
|
|
|
|
|
|
|
|
|
class ScanRequest(BaseModel):
|
|
|
|
|
|
file_paths: Optional[List[str]] = None
|
|
|
|
|
|
full_scan: bool = True
|
|
|
|
|
|
exclude_patterns: Optional[List[str]] = None
|
|
|
|
|
|
branch_name: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
@router.post("/{id}/scan")
|
|
|
|
|
|
async def scan_project(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
background_tasks: BackgroundTasks,
|
2025-12-06 20:47:28 +08:00
|
|
|
|
scan_request: Optional[ScanRequest] = None,
|
2025-11-26 21:11:12 +08:00
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Start a scan task.
|
|
|
|
|
|
"""
|
|
|
|
|
|
project = await db.get(Project, id)
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
2025-12-06 20:47:28 +08:00
|
|
|
|
# 获取分支和排除模式
|
|
|
|
|
|
branch_name = scan_request.branch_name if scan_request else None
|
|
|
|
|
|
exclude_patterns = scan_request.exclude_patterns if scan_request else None
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
# Create Task Record
|
|
|
|
|
|
task = AuditTask(
|
|
|
|
|
|
project_id=project.id,
|
|
|
|
|
|
created_by=current_user.id,
|
|
|
|
|
|
task_type="repository",
|
2025-12-06 20:47:28 +08:00
|
|
|
|
status="pending",
|
|
|
|
|
|
branch_name=branch_name or project.default_branch or "main",
|
|
|
|
|
|
exclude_patterns=json.dumps(exclude_patterns or []),
|
|
|
|
|
|
scan_config=json.dumps(scan_request.dict()) if scan_request else "{}"
|
2025-11-26 21:11:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
db.add(task)
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
await db.refresh(task)
|
|
|
|
|
|
|
2025-11-28 20:34:15 +08:00
|
|
|
|
# 获取用户配置(包含解密敏感字段)
|
|
|
|
|
|
from app.core.encryption import decrypt_sensitive_data
|
|
|
|
|
|
|
|
|
|
|
|
# 需要解密的敏感字段列表
|
|
|
|
|
|
SENSITIVE_LLM_FIELDS = [
|
|
|
|
|
|
'llmApiKey', 'geminiApiKey', 'openaiApiKey', 'claudeApiKey',
|
|
|
|
|
|
'qwenApiKey', 'deepseekApiKey', 'zhipuApiKey', 'moonshotApiKey',
|
|
|
|
|
|
'baiduApiKey', 'minimaxApiKey', 'doubaoApiKey'
|
|
|
|
|
|
]
|
|
|
|
|
|
SENSITIVE_OTHER_FIELDS = ['githubToken', 'gitlabToken']
|
|
|
|
|
|
|
|
|
|
|
|
def decrypt_config(config_dict: dict, sensitive_fields: list) -> dict:
|
|
|
|
|
|
"""解密配置中的敏感字段"""
|
|
|
|
|
|
decrypted = config_dict.copy()
|
|
|
|
|
|
for field in sensitive_fields:
|
|
|
|
|
|
if field in decrypted and decrypted[field]:
|
|
|
|
|
|
decrypted[field] = decrypt_sensitive_data(decrypted[field])
|
|
|
|
|
|
return decrypted
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
result = await db.execute(
|
|
|
|
|
|
select(UserConfig).where(UserConfig.user_id == current_user.id)
|
|
|
|
|
|
)
|
|
|
|
|
|
config = result.scalar_one_or_none()
|
|
|
|
|
|
user_config = {}
|
|
|
|
|
|
if config:
|
2025-11-28 20:34:15 +08:00
|
|
|
|
llm_config = json.loads(config.llm_config) if config.llm_config else {}
|
|
|
|
|
|
other_config = json.loads(config.other_config) if config.other_config else {}
|
|
|
|
|
|
# 解密敏感字段
|
|
|
|
|
|
llm_config = decrypt_config(llm_config, SENSITIVE_LLM_FIELDS)
|
|
|
|
|
|
other_config = decrypt_config(other_config, SENSITIVE_OTHER_FIELDS)
|
2025-11-26 21:11:12 +08:00
|
|
|
|
user_config = {
|
2025-11-28 20:34:15 +08:00
|
|
|
|
'llmConfig': llm_config,
|
|
|
|
|
|
'otherConfig': other_config,
|
2025-11-26 21:11:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-06 20:47:28 +08:00
|
|
|
|
# 将扫描配置注入到 user_config 中,以便 scan_repo_task 使用
|
|
|
|
|
|
if scan_request and scan_request.file_paths:
|
|
|
|
|
|
user_config['scan_config'] = {'file_paths': scan_request.file_paths}
|
|
|
|
|
|
|
2025-11-26 21:11:12 +08:00
|
|
|
|
# Trigger Background Task
|
|
|
|
|
|
background_tasks.add_task(scan_repo_task, task.id, AsyncSessionLocal, user_config)
|
|
|
|
|
|
|
|
|
|
|
|
return {"task_id": task.id, "status": "started"}
|
2025-11-28 17:38:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============ ZIP文件管理端点 ============
|
|
|
|
|
|
|
|
|
|
|
|
class ZipFileMetaResponse(BaseModel):
|
|
|
|
|
|
has_file: bool
|
|
|
|
|
|
original_filename: Optional[str] = None
|
|
|
|
|
|
file_size: Optional[int] = None
|
|
|
|
|
|
uploaded_at: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{id}/zip", response_model=ZipFileMetaResponse)
|
|
|
|
|
|
async def get_project_zip_info(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取项目ZIP文件信息
|
|
|
|
|
|
"""
|
|
|
|
|
|
project = await db.get(Project, id)
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否有ZIP文件
|
|
|
|
|
|
has_file = await has_project_zip(id)
|
|
|
|
|
|
if not has_file:
|
|
|
|
|
|
return {"has_file": False}
|
|
|
|
|
|
|
|
|
|
|
|
# 获取元数据
|
|
|
|
|
|
meta = await get_project_zip_meta(id)
|
|
|
|
|
|
if meta:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"has_file": True,
|
|
|
|
|
|
"original_filename": meta.get("original_filename"),
|
|
|
|
|
|
"file_size": meta.get("file_size"),
|
|
|
|
|
|
"uploaded_at": meta.get("uploaded_at")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {"has_file": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{id}/zip")
|
|
|
|
|
|
async def upload_project_zip(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
file: UploadFile = File(...),
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
上传或更新项目ZIP文件
|
|
|
|
|
|
"""
|
|
|
|
|
|
project = await db.get(Project, id)
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查权限
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权操作此项目")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查项目类型
|
|
|
|
|
|
if project.source_type != "zip":
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="仅ZIP类型项目可以上传ZIP文件")
|
|
|
|
|
|
|
|
|
|
|
|
# 验证文件类型
|
|
|
|
|
|
if not file.filename.lower().endswith('.zip'):
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="请上传ZIP格式文件")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到临时文件
|
|
|
|
|
|
temp_file_id = str(uuid.uuid4())
|
|
|
|
|
|
temp_file_path = f"/tmp/{temp_file_id}.zip"
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(temp_file_path, "wb") as buffer:
|
|
|
|
|
|
shutil.copyfileobj(file.file, buffer)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查文件大小
|
|
|
|
|
|
file_size = os.path.getsize(temp_file_path)
|
|
|
|
|
|
if file_size > 100 * 1024 * 1024: # 100MB limit
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="文件大小不能超过100MB")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到持久化存储
|
|
|
|
|
|
meta = await save_project_zip(id, temp_file_path, file.filename)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"message": "ZIP文件上传成功",
|
|
|
|
|
|
"original_filename": meta["original_filename"],
|
|
|
|
|
|
"file_size": meta["file_size"],
|
|
|
|
|
|
"uploaded_at": meta["uploaded_at"]
|
|
|
|
|
|
}
|
|
|
|
|
|
finally:
|
|
|
|
|
|
# 清理临时文件
|
|
|
|
|
|
if os.path.exists(temp_file_path):
|
|
|
|
|
|
os.remove(temp_file_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{id}/zip")
|
|
|
|
|
|
async def delete_project_zip_file(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
删除项目ZIP文件
|
|
|
|
|
|
"""
|
|
|
|
|
|
project = await db.get(Project, id)
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查权限
|
|
|
|
|
|
if project.owner_id != current_user.id:
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="无权操作此项目")
|
|
|
|
|
|
|
|
|
|
|
|
deleted = await delete_project_zip(id)
|
|
|
|
|
|
|
|
|
|
|
|
if deleted:
|
|
|
|
|
|
return {"message": "ZIP文件已删除"}
|
|
|
|
|
|
else:
|
|
|
|
|
|
return {"message": "没有找到ZIP文件"}
|
2025-12-06 20:47:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============ 分支管理端点 ============
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{id}/branches")
|
|
|
|
|
|
async def get_project_branches(
|
|
|
|
|
|
id: str,
|
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
|
current_user: User = Depends(deps.get_current_user),
|
|
|
|
|
|
) -> Any:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取项目仓库的分支列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
project = await db.get(Project, id)
|
|
|
|
|
|
if not project:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="项目不存在")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否为仓库类型项目
|
|
|
|
|
|
if project.source_type != "repository":
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="仅仓库类型项目支持获取分支")
|
|
|
|
|
|
|
|
|
|
|
|
if not project.repository_url:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail="项目未配置仓库地址")
|
|
|
|
|
|
|
|
|
|
|
|
# 获取用户配置的 Token
|
|
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
from app.core.encryption import decrypt_sensitive_data
|
|
|
|
|
|
|
|
|
|
|
|
config = await db.execute(
|
|
|
|
|
|
select(UserConfig).where(UserConfig.user_id == current_user.id)
|
|
|
|
|
|
)
|
|
|
|
|
|
config = config.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
|
|
github_token = settings.GITHUB_TOKEN
|
|
|
|
|
|
gitlab_token = settings.GITLAB_TOKEN
|
|
|
|
|
|
|
|
|
|
|
|
SENSITIVE_OTHER_FIELDS = ['githubToken', 'gitlabToken']
|
|
|
|
|
|
|
|
|
|
|
|
if config and config.other_config:
|
|
|
|
|
|
import json
|
|
|
|
|
|
other_config = json.loads(config.other_config)
|
|
|
|
|
|
for field in SENSITIVE_OTHER_FIELDS:
|
|
|
|
|
|
if field in other_config and other_config[field]:
|
|
|
|
|
|
decrypted_val = decrypt_sensitive_data(other_config[field])
|
|
|
|
|
|
if field == 'githubToken':
|
|
|
|
|
|
github_token = decrypted_val
|
|
|
|
|
|
elif field == 'gitlabToken':
|
|
|
|
|
|
gitlab_token = decrypted_val
|
|
|
|
|
|
|
|
|
|
|
|
repo_type = project.repository_type or "other"
|
|
|
|
|
|
|
feat(agent): implement comprehensive agent architecture with knowledge base and persistence layer
- Add database migrations for agent checkpoints and tree node tracking
- Implement core agent execution framework with executor, state management, and message handling
- Create knowledge base system with framework-specific modules (Django, FastAPI, Flask, Express, React, Supabase)
- Add vulnerability knowledge modules covering authentication, cryptography, injection, XSS, XXE, SSRF, path traversal, deserialization, and race conditions
- Introduce new agent tools: thinking tool, reporting tool, and agent-specific utilities
- Implement LLM memory compression and prompt caching for improved performance
- Add agent registry and persistence layer for checkpoint management
- Refactor agent implementations (analysis, recon, verification, orchestrator) with enhanced capabilities
- Remove legacy agent implementations (analysis_v2, react_agent)
- Update API endpoints for agent task creation and project management
- Add frontend components for agent task creation and enhanced audit UI
- Consolidate agent service architecture with improved separation of concerns
- This refactoring provides a scalable foundation for multi-agent collaboration with knowledge-driven decision making and state persistence
2025-12-12 15:27:12 +08:00
|
|
|
|
# 详细日志
|
|
|
|
|
|
print(f"[Branch] 项目: {project.name}, 类型: {repo_type}, URL: {project.repository_url}")
|
|
|
|
|
|
print(f"[Branch] GitHub Token: {'已配置' if github_token else '未配置'}, GitLab Token: {'已配置' if gitlab_token else '未配置'}")
|
|
|
|
|
|
|
2025-12-06 20:47:28 +08:00
|
|
|
|
try:
|
|
|
|
|
|
if repo_type == "github":
|
feat(agent): implement comprehensive agent architecture with knowledge base and persistence layer
- Add database migrations for agent checkpoints and tree node tracking
- Implement core agent execution framework with executor, state management, and message handling
- Create knowledge base system with framework-specific modules (Django, FastAPI, Flask, Express, React, Supabase)
- Add vulnerability knowledge modules covering authentication, cryptography, injection, XSS, XXE, SSRF, path traversal, deserialization, and race conditions
- Introduce new agent tools: thinking tool, reporting tool, and agent-specific utilities
- Implement LLM memory compression and prompt caching for improved performance
- Add agent registry and persistence layer for checkpoint management
- Refactor agent implementations (analysis, recon, verification, orchestrator) with enhanced capabilities
- Remove legacy agent implementations (analysis_v2, react_agent)
- Update API endpoints for agent task creation and project management
- Add frontend components for agent task creation and enhanced audit UI
- Consolidate agent service architecture with improved separation of concerns
- This refactoring provides a scalable foundation for multi-agent collaboration with knowledge-driven decision making and state persistence
2025-12-12 15:27:12 +08:00
|
|
|
|
if not github_token:
|
|
|
|
|
|
print("[Branch] 警告: GitHub Token 未配置,可能会遇到 API 限制")
|
2025-12-06 20:47:28 +08:00
|
|
|
|
branches = await get_github_branches(project.repository_url, github_token)
|
|
|
|
|
|
elif repo_type == "gitlab":
|
feat(agent): implement comprehensive agent architecture with knowledge base and persistence layer
- Add database migrations for agent checkpoints and tree node tracking
- Implement core agent execution framework with executor, state management, and message handling
- Create knowledge base system with framework-specific modules (Django, FastAPI, Flask, Express, React, Supabase)
- Add vulnerability knowledge modules covering authentication, cryptography, injection, XSS, XXE, SSRF, path traversal, deserialization, and race conditions
- Introduce new agent tools: thinking tool, reporting tool, and agent-specific utilities
- Implement LLM memory compression and prompt caching for improved performance
- Add agent registry and persistence layer for checkpoint management
- Refactor agent implementations (analysis, recon, verification, orchestrator) with enhanced capabilities
- Remove legacy agent implementations (analysis_v2, react_agent)
- Update API endpoints for agent task creation and project management
- Add frontend components for agent task creation and enhanced audit UI
- Consolidate agent service architecture with improved separation of concerns
- This refactoring provides a scalable foundation for multi-agent collaboration with knowledge-driven decision making and state persistence
2025-12-12 15:27:12 +08:00
|
|
|
|
if not gitlab_token:
|
|
|
|
|
|
print("[Branch] 警告: GitLab Token 未配置,可能无法访问私有仓库")
|
2025-12-06 20:47:28 +08:00
|
|
|
|
branches = await get_gitlab_branches(project.repository_url, gitlab_token)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 对于其他类型,返回默认分支
|
feat(agent): implement comprehensive agent architecture with knowledge base and persistence layer
- Add database migrations for agent checkpoints and tree node tracking
- Implement core agent execution framework with executor, state management, and message handling
- Create knowledge base system with framework-specific modules (Django, FastAPI, Flask, Express, React, Supabase)
- Add vulnerability knowledge modules covering authentication, cryptography, injection, XSS, XXE, SSRF, path traversal, deserialization, and race conditions
- Introduce new agent tools: thinking tool, reporting tool, and agent-specific utilities
- Implement LLM memory compression and prompt caching for improved performance
- Add agent registry and persistence layer for checkpoint management
- Refactor agent implementations (analysis, recon, verification, orchestrator) with enhanced capabilities
- Remove legacy agent implementations (analysis_v2, react_agent)
- Update API endpoints for agent task creation and project management
- Add frontend components for agent task creation and enhanced audit UI
- Consolidate agent service architecture with improved separation of concerns
- This refactoring provides a scalable foundation for multi-agent collaboration with knowledge-driven decision making and state persistence
2025-12-12 15:27:12 +08:00
|
|
|
|
print(f"[Branch] 仓库类型 '{repo_type}' 不支持获取分支,返回默认分支")
|
2025-12-06 20:47:28 +08:00
|
|
|
|
branches = [project.default_branch or "main"]
|
|
|
|
|
|
|
feat(agent): implement comprehensive agent architecture with knowledge base and persistence layer
- Add database migrations for agent checkpoints and tree node tracking
- Implement core agent execution framework with executor, state management, and message handling
- Create knowledge base system with framework-specific modules (Django, FastAPI, Flask, Express, React, Supabase)
- Add vulnerability knowledge modules covering authentication, cryptography, injection, XSS, XXE, SSRF, path traversal, deserialization, and race conditions
- Introduce new agent tools: thinking tool, reporting tool, and agent-specific utilities
- Implement LLM memory compression and prompt caching for improved performance
- Add agent registry and persistence layer for checkpoint management
- Refactor agent implementations (analysis, recon, verification, orchestrator) with enhanced capabilities
- Remove legacy agent implementations (analysis_v2, react_agent)
- Update API endpoints for agent task creation and project management
- Add frontend components for agent task creation and enhanced audit UI
- Consolidate agent service architecture with improved separation of concerns
- This refactoring provides a scalable foundation for multi-agent collaboration with knowledge-driven decision making and state persistence
2025-12-12 15:27:12 +08:00
|
|
|
|
print(f"[Branch] 成功获取 {len(branches)} 个分支")
|
|
|
|
|
|
|
2025-12-06 20:47:28 +08:00
|
|
|
|
# 将默认分支放在第一位
|
|
|
|
|
|
default_branch = project.default_branch or "main"
|
|
|
|
|
|
if default_branch in branches:
|
|
|
|
|
|
branches.remove(default_branch)
|
|
|
|
|
|
branches.insert(0, default_branch)
|
|
|
|
|
|
|
|
|
|
|
|
return {"branches": branches, "default_branch": default_branch}
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
feat(agent): implement comprehensive agent architecture with knowledge base and persistence layer
- Add database migrations for agent checkpoints and tree node tracking
- Implement core agent execution framework with executor, state management, and message handling
- Create knowledge base system with framework-specific modules (Django, FastAPI, Flask, Express, React, Supabase)
- Add vulnerability knowledge modules covering authentication, cryptography, injection, XSS, XXE, SSRF, path traversal, deserialization, and race conditions
- Introduce new agent tools: thinking tool, reporting tool, and agent-specific utilities
- Implement LLM memory compression and prompt caching for improved performance
- Add agent registry and persistence layer for checkpoint management
- Refactor agent implementations (analysis, recon, verification, orchestrator) with enhanced capabilities
- Remove legacy agent implementations (analysis_v2, react_agent)
- Update API endpoints for agent task creation and project management
- Add frontend components for agent task creation and enhanced audit UI
- Consolidate agent service architecture with improved separation of concerns
- This refactoring provides a scalable foundation for multi-agent collaboration with knowledge-driven decision making and state persistence
2025-12-12 15:27:12 +08:00
|
|
|
|
error_msg = str(e)
|
|
|
|
|
|
print(f"[Branch] 获取分支列表失败: {error_msg}")
|
2025-12-06 20:47:28 +08:00
|
|
|
|
# 返回默认分支作为后备
|
|
|
|
|
|
return {
|
|
|
|
|
|
"branches": [project.default_branch or "main"],
|
|
|
|
|
|
"default_branch": project.default_branch or "main",
|
|
|
|
|
|
"error": str(e)
|
|
|
|
|
|
}
|