448 lines
11 KiB
Python
448 lines
11 KiB
Python
from pydantic import BaseModel
|
|
from typing import Optional, List
|
|
from datetime import datetime, time
|
|
from enum import Enum
|
|
|
|
|
|
class TaskTypeEnum(str, Enum):
|
|
ISSUE = "issue"
|
|
MAINTENANCE = "maintenance"
|
|
RESEARCH = "research"
|
|
REVIEW = "review"
|
|
STORY = "story"
|
|
TEST = "test"
|
|
RESOLUTION = "resolution"
|
|
# P7.1: 'task' type removed — defect subtype migrated to issue/defect
|
|
|
|
|
|
class TaskStatusEnum(str, Enum):
|
|
OPEN = "open"
|
|
PENDING = "pending"
|
|
UNDERGOING = "undergoing"
|
|
COMPLETED = "completed"
|
|
CLOSED = "closed"
|
|
|
|
|
|
class TaskPriorityEnum(str, Enum):
|
|
LOW = "low"
|
|
MEDIUM = "medium"
|
|
HIGH = "high"
|
|
CRITICAL = "critical"
|
|
|
|
|
|
# Task schemas
|
|
class TaskBase(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
task_type: TaskTypeEnum = TaskTypeEnum.ISSUE
|
|
task_subtype: Optional[str] = None
|
|
priority: TaskPriorityEnum = TaskPriorityEnum.MEDIUM
|
|
tags: Optional[str] = None
|
|
estimated_effort: Optional[int] = None
|
|
estimated_working_time: Optional[str] = None
|
|
|
|
|
|
class TaskCreate(TaskBase):
|
|
project_code: Optional[str] = None
|
|
milestone_code: Optional[str] = None
|
|
reporter_id: Optional[int] = None
|
|
assignee_id: Optional[int] = None
|
|
type: Optional[TaskTypeEnum] = None
|
|
# Resolution specific
|
|
resolution_summary: Optional[str] = None
|
|
positions: Optional[str] = None
|
|
pending_matters: Optional[str] = None
|
|
|
|
|
|
class TaskUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
task_type: Optional[TaskTypeEnum] = None
|
|
type: Optional[TaskTypeEnum] = None
|
|
task_subtype: Optional[str] = None
|
|
status: Optional[TaskStatusEnum] = None
|
|
priority: Optional[TaskPriorityEnum] = None
|
|
assignee_id: Optional[int] = None
|
|
taken_by: Optional[str] = None
|
|
tags: Optional[str] = None
|
|
estimated_effort: Optional[int] = None
|
|
# Resolution specific
|
|
resolution_summary: Optional[str] = None
|
|
positions: Optional[str] = None
|
|
pending_matters: Optional[str] = None
|
|
|
|
|
|
class TaskResponse(TaskBase):
|
|
status: TaskStatusEnum
|
|
task_code: Optional[str] = None
|
|
code: Optional[str] = None
|
|
type: Optional[str] = None
|
|
due_date: Optional[datetime] = None
|
|
project_code: Optional[str] = None
|
|
milestone_code: Optional[str] = None
|
|
reporter_id: int
|
|
assignee_id: Optional[int] = None
|
|
taken_by: Optional[str] = None
|
|
created_by_id: Optional[int] = None
|
|
estimated_working_time: Optional[time] = None
|
|
resolution_summary: Optional[str] = None
|
|
positions: Optional[str] = None
|
|
pending_matters: Optional[str] = None
|
|
# BE-PR-008: Proposal Accept tracking
|
|
source_proposal_code: Optional[str] = None
|
|
source_essential_code: Optional[str] = None
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Comment schemas
|
|
class CommentBase(BaseModel):
|
|
content: str
|
|
|
|
|
|
class CommentCreate(CommentBase):
|
|
task_id: int
|
|
author_id: int
|
|
|
|
|
|
class CommentUpdate(BaseModel):
|
|
content: Optional[str] = None
|
|
|
|
|
|
class CommentResponse(CommentBase):
|
|
id: int
|
|
task_id: int
|
|
author_id: int
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Project schemas
|
|
class ProjectBase(BaseModel):
|
|
name: str
|
|
owner_name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
repo: Optional[str] = None
|
|
sub_projects: Optional[list[str]] = None
|
|
related_projects: Optional[list[str]] = None
|
|
|
|
|
|
class ProjectCreate(ProjectBase):
|
|
owner_id: int
|
|
|
|
|
|
class ProjectUpdate(BaseModel):
|
|
description: Optional[str] = None
|
|
owner_name: Optional[str] = None
|
|
repo: Optional[str] = None
|
|
sub_projects: Optional[list[str]] = None
|
|
related_projects: Optional[list[str]] = None
|
|
|
|
|
|
class ProjectResponse(BaseModel):
|
|
id: int
|
|
name: str
|
|
owner_name: Optional[str] = None
|
|
project_code: Optional[str] = None
|
|
description: Optional[str] = None
|
|
repo: Optional[str] = None
|
|
sub_projects: Optional[list[str]] = None
|
|
related_projects: Optional[list[str]] = None
|
|
owner_id: int
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# User schemas
|
|
class UserBase(BaseModel):
|
|
username: str
|
|
email: str
|
|
full_name: Optional[str] = None
|
|
|
|
|
|
class UserCreate(UserBase):
|
|
password: Optional[str] = None
|
|
role_id: Optional[int] = None
|
|
discord_user_id: Optional[str] = None
|
|
# Agent binding (both must be provided or both omitted)
|
|
agent_id: Optional[str] = None
|
|
claw_identifier: Optional[str] = None
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
full_name: Optional[str] = None
|
|
email: Optional[str] = None
|
|
password: Optional[str] = None
|
|
role_id: Optional[int] = None
|
|
is_active: Optional[bool] = None
|
|
discord_user_id: Optional[str] = None
|
|
|
|
|
|
class UserResponse(UserBase):
|
|
id: int
|
|
is_active: bool
|
|
is_admin: bool
|
|
role_id: Optional[int] = None
|
|
role_name: Optional[str] = None
|
|
agent_id: Optional[str] = None
|
|
discord_user_id: Optional[str] = None
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Project Member schemas
|
|
class ProjectMemberBase(BaseModel):
|
|
user_id: int
|
|
role: str = "dev"
|
|
|
|
|
|
class ProjectMemberCreate(ProjectMemberBase):
|
|
pass
|
|
|
|
|
|
class ProjectMemberResponse(BaseModel):
|
|
id: int
|
|
user_id: int
|
|
username: Optional[str] = None
|
|
full_name: Optional[str] = None
|
|
project_id: int
|
|
role: str = "dev"
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class MilestoneStatusEnum(str, Enum):
|
|
OPEN = "open"
|
|
FREEZE = "freeze"
|
|
UNDERGOING = "undergoing"
|
|
COMPLETED = "completed"
|
|
CLOSED = "closed"
|
|
|
|
|
|
# Milestone schemas
|
|
class MilestoneBase(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
status: Optional[MilestoneStatusEnum] = MilestoneStatusEnum.OPEN
|
|
due_date: Optional[datetime] = None
|
|
planned_release_date: Optional[datetime] = None
|
|
depend_on_milestones: Optional[List[str]] = None
|
|
depend_on_tasks: Optional[List[int]] = None
|
|
|
|
|
|
class MilestoneCreate(MilestoneBase):
|
|
project_id: Optional[int] = None
|
|
pass
|
|
|
|
|
|
class MilestoneUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
status: Optional[MilestoneStatusEnum] = None
|
|
due_date: Optional[datetime] = None
|
|
planned_release_date: Optional[datetime] = None
|
|
depend_on_milestones: Optional[List[str]] = None
|
|
depend_on_tasks: Optional[List[int]] = None
|
|
|
|
|
|
class MilestoneResponse(MilestoneBase):
|
|
milestone_code: Optional[str] = None
|
|
code: Optional[str] = None
|
|
project_code: Optional[str] = None
|
|
created_by_id: Optional[int] = None
|
|
started_at: Optional[datetime] = None
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# Proposal schemas (renamed from Propose)
|
|
|
|
class ProposalStatusEnum(str, Enum):
|
|
OPEN = "open"
|
|
ACCEPTED = "accepted"
|
|
REJECTED = "rejected"
|
|
|
|
|
|
class ProposalBase(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
|
|
|
|
class ProposalCreate(ProposalBase):
|
|
pass
|
|
|
|
|
|
class ProposalUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
|
|
|
|
class ProposalResponse(ProposalBase):
|
|
proposal_code: Optional[str] = None # preferred name
|
|
propose_code: Optional[str] = None # backward compat alias (same value)
|
|
status: ProposalStatusEnum
|
|
project_code: Optional[str] = None
|
|
created_by_id: Optional[int] = None
|
|
created_by_username: Optional[str] = None
|
|
feat_task_id: Optional[str] = None # DEPRECATED (BE-PR-010): legacy field, read-only. Use generated_tasks instead.
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Essential schemas (under Proposal)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class EssentialTypeEnum(str, Enum):
|
|
FEATURE = "feature"
|
|
IMPROVEMENT = "improvement"
|
|
REFACTOR = "refactor"
|
|
|
|
|
|
class EssentialBase(BaseModel):
|
|
title: str
|
|
type: EssentialTypeEnum
|
|
description: Optional[str] = None
|
|
|
|
|
|
class EssentialCreate(EssentialBase):
|
|
"""Create a new Essential under a Proposal.
|
|
|
|
``proposal_id`` is inferred from the URL path, not the body.
|
|
"""
|
|
pass
|
|
|
|
|
|
class EssentialUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
type: Optional[EssentialTypeEnum] = None
|
|
description: Optional[str] = None
|
|
|
|
|
|
class EssentialResponse(EssentialBase):
|
|
essential_code: str
|
|
proposal_code: Optional[str] = None
|
|
created_by_id: Optional[int] = None
|
|
created_at: datetime
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class GeneratedTaskBrief(BaseModel):
|
|
"""Brief info about a story task generated from Proposal Accept."""
|
|
task_code: Optional[str] = None
|
|
task_type: str
|
|
task_subtype: Optional[str] = None
|
|
title: str
|
|
status: Optional[str] = None
|
|
source_essential_code: Optional[str] = None
|
|
|
|
|
|
class ProposalDetailResponse(ProposalResponse):
|
|
"""Extended Proposal response that embeds its Essential list and generated tasks."""
|
|
|
|
essentials: List[EssentialResponse] = []
|
|
generated_tasks: List[GeneratedTaskBrief] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class GeneratedTaskSummary(BaseModel):
|
|
"""Brief summary of a task generated from a Proposal Essential."""
|
|
task_code: str
|
|
task_type: str
|
|
task_subtype: str
|
|
title: str
|
|
essential_code: str
|
|
|
|
|
|
class ProposalAcceptResponse(ProposalResponse):
|
|
"""Response for Proposal Accept — includes the generated story tasks."""
|
|
|
|
essentials: List[EssentialResponse] = []
|
|
generated_tasks: List[GeneratedTaskSummary] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Agent schemas (BE-CAL-003)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class AgentStatusEnum(str, Enum):
|
|
IDLE = "idle"
|
|
ON_CALL = "on_call"
|
|
BUSY = "busy"
|
|
EXHAUSTED = "exhausted"
|
|
OFFLINE = "offline"
|
|
|
|
|
|
class ExhaustReasonEnum(str, Enum):
|
|
RATE_LIMIT = "rate_limit"
|
|
BILLING = "billing"
|
|
|
|
|
|
class AgentResponse(BaseModel):
|
|
"""Read-only representation of an Agent."""
|
|
id: int
|
|
user_id: int
|
|
agent_id: str
|
|
claw_identifier: str
|
|
status: AgentStatusEnum
|
|
last_heartbeat: Optional[datetime] = None
|
|
exhausted_at: Optional[datetime] = None
|
|
recovery_at: Optional[datetime] = None
|
|
exhaust_reason: Optional[ExhaustReasonEnum] = None
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class AgentStatusUpdate(BaseModel):
|
|
"""Payload for updating an agent's runtime status."""
|
|
status: AgentStatusEnum
|
|
exhaust_reason: Optional[ExhaustReasonEnum] = None
|
|
recovery_at: Optional[datetime] = None
|
|
|
|
|
|
# Backward-compatible aliases
|
|
ProposeStatusEnum = ProposalStatusEnum
|
|
ProposeBase = ProposalBase
|
|
ProposeCreate = ProposalCreate
|
|
ProposeUpdate = ProposalUpdate
|
|
ProposeResponse = ProposalResponse
|
|
|
|
|
|
# Paginated response
|
|
from typing import Generic, TypeVar
|
|
T = TypeVar("T")
|
|
|
|
class PaginatedResponse(BaseModel, Generic[T]):
|
|
items: List[T]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
total_pages: int
|