Files
xiaoxia-saas/apps/api/app/api/routes/project_management.py
T
API文档维护Agent 8064b64ff5 docs: API文档准确性修复
1. 修正上传端点schema命名:新增UploadAssetRequest模型替代自动生成的Body_upload_asset名称
2. 清理认证重复端点:移除/forgot-password和/reset-password旧路由,保留/password/forgot和/password/reset
3. 清理workspace_id残留:从edit_plan/asset_diagnosis/task_center/project_title等schema中移除,project_management和projects中改为可选
4. 更新README:移除过时的多租户/订阅/refresh token描述,修正仓库地址为git.xiaoxiajianji.com,更新API路径列表
2026-06-27 19:02:31 +08:00

487 lines
14 KiB
Python

"""项目管理 API 路由"""
from datetime import datetime
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from packages.adapters.sqlite_tracker.project_management_repositories import (
SQLiteMilestoneRepository,
SQLiteTaskIssueRepository,
SQLiteTaskRepository,
)
from packages.application.get_task_detail_use_case import GetTaskDetailUseCase
from packages.application.project_management_use_cases import (
CreateMilestoneUseCase,
CreateTaskIssueUseCase,
CreateTaskUseCase,
ListProjectMilestonesUseCase,
ListProjectTasksUseCase,
ListTaskIssuesUseCase,
ResolveTaskIssueUseCase,
UpdateTaskProgressUseCase,
UpdateTaskStatusUseCase,
)
from packages.application.update_task_use_case import UpdateTaskUseCase
from packages.domain import TaskPriority, TaskStatus
router = APIRouter()
# 使用 SQLite tracker.db
_task_repo = SQLiteTaskRepository()
_milestone_repo = SQLiteMilestoneRepository()
_issue_repo = SQLiteTaskIssueRepository()
def get_task_repo():
return _task_repo
def get_milestone_repo():
return _milestone_repo
def get_issue_repo():
return _issue_repo
# ========== Request/Response Models ==========
class CreateTaskRequest(BaseModel):
project_id: str
workspace_id: str = ""
name: str
description: str = ""
priority: TaskPriority = TaskPriority.MEDIUM
parent_task_id: str = ""
assignee_user_id: str = ""
class TaskResponse(BaseModel):
id: str
project_id: str
workspace_id: str = ""
name: str
description: str
status: TaskStatus
priority: TaskPriority
parent_task_id: str
assignee_user_id: str
progress: float
planned_start_date: datetime | None
planned_end_date: datetime | None
actual_start_date: datetime | None
actual_end_date: datetime | None
tags: list[str]
created_at: datetime
updated_at: datetime
class UpdateTaskRequest(BaseModel):
name: str | None = None
description: str | None = None
priority: str | None = None
assignee_user_id: str | None = None
class UpdateTaskStatusRequest(BaseModel):
status: TaskStatus
class UpdateTaskProgressRequest(BaseModel):
progress: Annotated[float, Field(ge=0, le=100)]
class CreateMilestoneRequest(BaseModel):
project_id: str
workspace_id: str = ""
name: str
description: str = ""
class MilestoneResponse(BaseModel):
id: str
project_id: str
workspace_id: str = ""
name: str
description: str
target_date: datetime | None
completed: bool
completed_at: datetime | None
created_at: datetime
updated_at: datetime
class CreateTaskIssueRequest(BaseModel):
task_id: str
project_id: str
workspace_id: str = ""
title: str
description: str = ""
created_by_user_id: str = ""
class TaskIssueResponse(BaseModel):
id: str
task_id: str
project_id: str
workspace_id: str = ""
title: str
description: str
resolved: bool
resolved_at: datetime | None
created_by_user_id: str
created_at: datetime
updated_at: datetime
# ========== Task Endpoints ==========
@router.post("/tasks", response_model=TaskResponse)
def create_task(
req: CreateTaskRequest,
task_repo=Depends(get_task_repo),
):
"""创建任务"""
use_case = CreateTaskUseCase(task_repo)
task = use_case.execute(
project_id=req.project_id,
workspace_id=req.workspace_id,
name=req.name,
description=req.description,
priority=req.priority,
parent_task_id=req.parent_task_id,
assignee_user_id=req.assignee_user_id,
)
return TaskResponse(
id=task.id,
project_id=task.project_id,
workspace_id=task.workspace_id,
name=task.name,
description=task.description,
status=task.status,
priority=task.priority,
parent_task_id=task.parent_task_id,
assignee_user_id=task.assignee_user_id,
progress=task.progress,
planned_start_date=task.planned_start_date,
planned_end_date=task.planned_end_date,
actual_start_date=task.actual_start_date,
actual_end_date=task.actual_end_date,
tags=task.tags,
created_at=task.created_at,
updated_at=task.updated_at,
)
@router.get("/tasks", response_model=list[TaskResponse])
def list_tasks(
project_id: str,
task_repo=Depends(get_task_repo),
):
"""获取项目任务列表"""
use_case = ListProjectTasksUseCase(task_repo)
tasks = use_case.execute(project_id)
return [
TaskResponse(
id=t.id,
project_id=t.project_id,
workspace_id=t.workspace_id,
name=t.name,
description=t.description,
status=t.status,
priority=t.priority,
parent_task_id=t.parent_task_id,
assignee_user_id=t.assignee_user_id,
progress=t.progress,
planned_start_date=t.planned_start_date,
planned_end_date=t.planned_end_date,
actual_start_date=t.actual_start_date,
actual_end_date=t.actual_end_date,
tags=t.tags,
created_at=t.created_at,
updated_at=t.updated_at,
)
for t in tasks
]
@router.get("/tasks/{task_id}", response_model=TaskResponse)
def get_task(
task_id: str,
task_repo=Depends(get_task_repo),
):
"""获取任务详情"""
use_case = GetTaskDetailUseCase(task_repo)
try:
task = use_case.execute(task_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return TaskResponse(
id=task.id,
project_id=task.project_id,
workspace_id=task.workspace_id,
name=task.name,
description=task.description,
status=task.status,
priority=task.priority,
parent_task_id=task.parent_task_id,
assignee_user_id=task.assignee_user_id,
progress=task.progress,
planned_start_date=task.planned_start_date,
planned_end_date=task.planned_end_date,
actual_start_date=task.actual_start_date,
actual_end_date=task.actual_end_date,
tags=task.tags,
created_at=task.created_at,
updated_at=task.updated_at,
)
@router.patch("/tasks/{task_id}", response_model=TaskResponse)
def update_task(
task_id: str,
req: UpdateTaskRequest,
task_repo=Depends(get_task_repo),
):
"""更新任务基本信息"""
use_case = UpdateTaskUseCase(task_repo)
try:
task = use_case.execute(
task_id=task_id,
name=req.name,
description=req.description,
priority=req.priority,
assignee_user_id=req.assignee_user_id,
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return TaskResponse(
id=task.id,
project_id=task.project_id,
workspace_id=task.workspace_id,
name=task.name,
description=task.description,
status=task.status,
priority=task.priority,
parent_task_id=task.parent_task_id,
assignee_user_id=task.assignee_user_id,
progress=task.progress,
planned_start_date=task.planned_start_date,
planned_end_date=task.planned_end_date,
actual_start_date=task.actual_start_date,
actual_end_date=task.actual_end_date,
tags=task.tags,
created_at=task.created_at,
updated_at=task.updated_at,
)
@router.patch("/tasks/{task_id}/status", response_model=TaskResponse)
def update_task_status(
task_id: str,
req: UpdateTaskStatusRequest,
task_repo=Depends(get_task_repo),
):
"""更新任务状态"""
use_case = UpdateTaskStatusUseCase(task_repo)
try:
task = use_case.execute(task_id, req.status)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return TaskResponse(
id=task.id,
project_id=task.project_id,
workspace_id=task.workspace_id,
name=task.name,
description=task.description,
status=task.status,
priority=task.priority,
parent_task_id=task.parent_task_id,
assignee_user_id=task.assignee_user_id,
progress=task.progress,
planned_start_date=task.planned_start_date,
planned_end_date=task.planned_end_date,
actual_start_date=task.actual_start_date,
actual_end_date=task.actual_end_date,
tags=task.tags,
created_at=task.created_at,
updated_at=task.updated_at,
)
@router.patch("/tasks/{task_id}/progress", response_model=TaskResponse)
def update_task_progress(
task_id: str,
req: UpdateTaskProgressRequest,
task_repo=Depends(get_task_repo),
):
"""更新任务进度"""
use_case = UpdateTaskProgressUseCase(task_repo)
try:
task = use_case.execute(task_id, req.progress)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return TaskResponse(
id=task.id,
project_id=task.project_id,
workspace_id=task.workspace_id,
name=task.name,
description=task.description,
status=task.status,
priority=task.priority,
parent_task_id=task.parent_task_id,
assignee_user_id=task.assignee_user_id,
progress=task.progress,
planned_start_date=task.planned_start_date,
planned_end_date=task.planned_end_date,
actual_start_date=task.actual_start_date,
actual_end_date=task.actual_end_date,
tags=task.tags,
created_at=task.created_at,
updated_at=task.updated_at,
)
# ========== Milestone Endpoints ==========
@router.post("/milestones", response_model=MilestoneResponse)
def create_milestone(
req: CreateMilestoneRequest,
milestone_repo=Depends(get_milestone_repo),
):
"""创建里程碑"""
use_case = CreateMilestoneUseCase(milestone_repo)
milestone = use_case.execute(
project_id=req.project_id,
workspace_id=req.workspace_id,
name=req.name,
description=req.description,
)
return MilestoneResponse(
id=milestone.id,
project_id=milestone.project_id,
workspace_id=milestone.workspace_id,
name=milestone.name,
description=milestone.description,
target_date=milestone.target_date,
completed=milestone.completed,
completed_at=milestone.completed_at,
created_at=milestone.created_at,
updated_at=milestone.updated_at,
)
@router.get("/milestones", response_model=list[MilestoneResponse])
def list_milestones(
project_id: str,
milestone_repo=Depends(get_milestone_repo),
):
"""获取项目里程碑列表"""
use_case = ListProjectMilestonesUseCase(milestone_repo)
milestones = use_case.execute(project_id)
return [
MilestoneResponse(
id=m.id,
project_id=m.project_id,
workspace_id=m.workspace_id,
name=m.name,
description=m.description,
target_date=m.target_date,
completed=m.completed,
completed_at=m.completed_at,
created_at=m.created_at,
updated_at=m.updated_at,
)
for m in milestones
]
# ========== Task Issue Endpoints ==========
@router.post("/issues", response_model=TaskIssueResponse)
def create_issue(
req: CreateTaskIssueRequest,
issue_repo=Depends(get_issue_repo),
):
"""创建任务问题"""
use_case = CreateTaskIssueUseCase(issue_repo)
issue = use_case.execute(
task_id=req.task_id,
project_id=req.project_id,
workspace_id=req.workspace_id,
title=req.title,
description=req.description,
created_by_user_id=req.created_by_user_id,
)
return TaskIssueResponse(
id=issue.id,
task_id=issue.task_id,
project_id=issue.project_id,
workspace_id=issue.workspace_id,
title=issue.title,
description=issue.description,
resolved=issue.resolved,
resolved_at=issue.resolved_at,
created_by_user_id=issue.created_by_user_id,
created_at=issue.created_at,
updated_at=issue.updated_at,
)
@router.get("/issues", response_model=list[TaskIssueResponse])
def list_issues(
task_id: str,
issue_repo=Depends(get_issue_repo),
):
"""获取任务问题列表"""
use_case = ListTaskIssuesUseCase(issue_repo)
issues = use_case.execute(task_id)
return [
TaskIssueResponse(
id=i.id,
task_id=i.task_id,
project_id=i.project_id,
workspace_id=i.workspace_id,
title=i.title,
description=i.description,
resolved=i.resolved,
resolved_at=i.resolved_at,
created_by_user_id=i.created_by_user_id,
created_at=i.created_at,
updated_at=i.updated_at,
)
for i in issues
]
@router.patch("/issues/{issue_id}/resolve", response_model=TaskIssueResponse)
def resolve_issue(
issue_id: str,
issue_repo=Depends(get_issue_repo),
):
"""解决任务问题"""
use_case = ResolveTaskIssueUseCase(issue_repo)
try:
issue = use_case.execute(issue_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
return TaskIssueResponse(
id=issue.id,
task_id=issue.task_id,
project_id=issue.project_id,
workspace_id=issue.workspace_id,
title=issue.title,
description=issue.description,
resolved=issue.resolved,
resolved_at=issue.resolved_at,
created_by_user_id=issue.created_by_user_id,
created_at=issue.created_at,
updated_at=issue.updated_at,
)