""" Issue Pydantic Schemas Validation schemas for Gitea issue operations. """ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, field_validator from .common import GiteaUser class Label(BaseModel): """Issue/PR label""" model_config = ConfigDict(extra="allow") id: int name: str color: str description: str | None = None url: str | None = None class Milestone(BaseModel): """Issue/PR milestone""" model_config = ConfigDict(extra="allow") id: int title: str description: str | None = None state: str # "open" or "closed" open_issues: int = 0 closed_issues: int = 0 created_at: datetime updated_at: datetime | None = None closed_at: datetime | None = None due_on: datetime | None = None class Issue(BaseModel): """Gitea issue model""" model_config = ConfigDict(extra="allow") id: int number: int user: GiteaUser original_author: str | None = None original_author_id: int | None = None title: str body: str | None = None ref: str | None = None labels: list[Label] = [] milestone: Milestone | None = None assignee: GiteaUser | None = None assignees: list[GiteaUser] = [] state: str # "open" or "closed" is_locked: bool = False comments: int = 0 created_at: datetime updated_at: datetime closed_at: datetime | None = None due_date: datetime | None = None pull_request: dict | None = None # Non-null if this is a PR repository: dict | None = None html_url: str url: str class Comment(BaseModel): """Issue/PR comment""" model_config = ConfigDict(extra="allow") id: int html_url: str pull_request_url: str | None = None issue_url: str | None = None user: GiteaUser original_author: str | None = None original_author_id: int | None = None body: str created_at: datetime updated_at: datetime class CreateIssueRequest(BaseModel): """Request to create an issue""" model_config = ConfigDict(extra="forbid") title: str = Field(..., min_length=1, max_length=255, description="Issue title") body: str | None = Field(None, description="Issue body/description") assignee: str | None = Field(None, description="Username of assignee") assignees: list[str] | None = Field(None, description="List of assignee usernames") closed: bool | None = Field(False, description="Create as closed") due_date: datetime | None = Field(None, description="Due date") labels: list[int] | None = Field(None, description="List of label IDs") milestone: int | None = Field(None, description="Milestone ID") ref: str | None = Field(None, description="Issue ref") class UpdateIssueRequest(BaseModel): """Request to update an issue""" model_config = ConfigDict(extra="forbid") title: str | None = Field(None, min_length=1, max_length=255, description="Issue title") body: str | None = Field(None, description="Issue body/description") assignee: str | None = Field(None, description="Username of assignee") assignees: list[str] | None = Field(None, description="List of assignee usernames") state: str | None = Field(None, description="State (open/closed)") due_date: datetime | None = Field(None, description="Due date") labels: list[int] | None = Field(None, description="List of label IDs") milestone: int | None = Field(None, description="Milestone ID") ref: str | None = Field(None, description="Issue ref") unset_due_date: bool | None = Field(None, description="Unset due date") @classmethod @field_validator("state") def validate_state(cls, v): if v and v not in ["open", "closed"]: raise ValueError("State must be 'open' or 'closed'") return v class CreateCommentRequest(BaseModel): """Request to create a comment""" model_config = ConfigDict(extra="forbid") body: str = Field(..., min_length=1, description="Comment body") class CreateLabelRequest(BaseModel): """Request to create a label""" model_config = ConfigDict(extra="forbid") name: str = Field(..., min_length=1, max_length=50, description="Label name") color: str = Field(..., pattern=r"^[0-9A-Fa-f]{6}$", description="Label color (hex without #)") description: str | None = Field(None, max_length=200, description="Label description") class CreateMilestoneRequest(BaseModel): """Request to create a milestone""" model_config = ConfigDict(extra="forbid") title: str = Field(..., min_length=1, max_length=255, description="Milestone title") description: str | None = Field(None, description="Milestone description") due_on: datetime | None = Field(None, description="Due date") state: str | None = Field("open", description="State (open/closed)") @classmethod @field_validator("state") def validate_state(cls, v): if v not in ["open", "closed"]: raise ValueError("State must be 'open' or 'closed'") return v class IssueListFilters(BaseModel): """Filters for listing issues""" model_config = ConfigDict(extra="forbid") state: str | None = Field("open", description="Filter by state (open/closed/all)") labels: str | None = Field(None, description="Comma-separated label IDs") q: str | None = Field(None, description="Search query") type: str | None = Field(None, description="Filter by type (issues/pulls)") milestones: str | None = Field(None, description="Comma-separated milestone names") since: datetime | None = Field(None, description="Only show items updated after this time") before: datetime | None = Field(None, description="Only show items updated before this time") created_by: str | None = Field(None, description="Filter by creator username") assigned_by: str | None = Field(None, description="Filter by assignee username") mentioned_by: str | None = Field(None, description="Filter by mentioned username") @classmethod @field_validator("state") def validate_state(cls, v): if v and v not in ["open", "closed", "all"]: raise ValueError("State must be 'open', 'closed', or 'all'") return v @classmethod @field_validator("type") def validate_type(cls, v): if v and v not in ["issues", "pulls"]: raise ValueError("Type must be 'issues' or 'pulls'") return v