8064b64ff5
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路径列表
487 lines
14 KiB
Python
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,
|
|
)
|