// Package project 承载 Project / Requirement / Issue 三个 PM 聚合根的领域类型与 // Service 接口契约。本文件只做类型与签名声明,业务实现在 *_service.go。 // // 三个聚合根共享同一个 Go 包是有意为之:它们在 schema 上紧耦合(Issue 引用 // Requirement,Requirement 属于 Project),且对外只暴露一组 chi route,拆包 // 反而要互相 import。 package project import ( "context" "time" "github.com/google/uuid" ) // Phase 是 Requirement 的五阶段枚举。phase 不强制单向流动,可任意跳跃/回退。 // 字符串常量保持与 DB CHECK 约束一致。 type Phase string const ( PhasePlanning Phase = "planning" PhaseAuditing Phase = "auditing" PhaseImplementing Phase = "implementing" PhaseReviewing Phase = "reviewing" PhaseDone Phase = "done" ) // AllPhases 用于校验 phase 字符串合法性,顺序也对应前端看板/流转条的列序。 var AllPhases = []Phase{PhasePlanning, PhaseAuditing, PhaseImplementing, PhaseReviewing, PhaseDone} // IsValid 报告 p 是否在枚举集合内。 func (p Phase) IsValid() bool { for _, x := range AllPhases { if p == x { return true } } return false } // Status 是 Requirement / Issue 通用的开/关状态。 type Status string const ( StatusOpen Status = "open" StatusClosed Status = "closed" ) // Visibility 控制 Project 的可见域:private 仅 owner+admin 可读, // internal 所有登录用户可读;写权限两者都仅限 owner+admin。 type Visibility string const ( VisibilityPrivate Visibility = "private" VisibilityInternal Visibility = "internal" ) // IsValid 报告 v 是否在枚举集合内。 func (v Visibility) IsValid() bool { return v == VisibilityPrivate || v == VisibilityInternal } // Project 是顶层聚合根。ArchivedAt 为 nil 表示未归档。 type Project struct { ID uuid.UUID Slug string Name string Description string Visibility Visibility OwnerID uuid.UUID ArchivedAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // Requirement 表示一个项目内的需求。Number 在 (project_id) 内自增。 type Requirement struct { ID uuid.UUID ProjectID uuid.UUID Number int Title string Description string Phase Phase Status Status OwnerID uuid.UUID ClosedAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // Issue 可挂载到某个 Requirement,也可游离(RequirementID == nil)。 // AssigneeID 为 nil 表示未指派。CreatedBy 永远存在(DELETE RESTRICT)。 type Issue struct { ID uuid.UUID ProjectID uuid.UUID RequirementID *uuid.UUID Number int Title string Description string Status Status AssigneeID *uuid.UUID CreatedBy uuid.UUID ClosedAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // Caller 表示发起请求的用户上下文。Service 层据此判定鉴权。 type Caller struct { UserID uuid.UUID IsAdmin bool } // ProjectService 暴露 Project 聚合根的应用服务方法。 type ProjectService interface { Create(ctx context.Context, c Caller, in CreateProjectInput) (*Project, error) Get(ctx context.Context, c Caller, slug string) (*Project, error) List(ctx context.Context, c Caller, includeArchived bool) ([]*Project, error) Update(ctx context.Context, c Caller, slug string, in UpdateProjectInput) (*Project, error) Archive(ctx context.Context, c Caller, slug string) error Unarchive(ctx context.Context, c Caller, slug string) error } // RequirementService 暴露 Requirement 聚合根的应用服务方法。 type RequirementService interface { Create(ctx context.Context, c Caller, projectSlug string, in CreateRequirementInput) (*Requirement, error) Get(ctx context.Context, c Caller, projectSlug string, number int) (*Requirement, error) List(ctx context.Context, c Caller, projectSlug string, filter RequirementFilter) ([]*Requirement, error) Update(ctx context.Context, c Caller, projectSlug string, number int, in UpdateRequirementInput) (*Requirement, error) ChangePhase(ctx context.Context, c Caller, projectSlug string, number int, to Phase) (*Requirement, error) Close(ctx context.Context, c Caller, projectSlug string, number int) error Reopen(ctx context.Context, c Caller, projectSlug string, number int) error } // IssueService 暴露 Issue 聚合根的应用服务方法。 type IssueService interface { Create(ctx context.Context, c Caller, projectSlug string, in CreateIssueInput) (*Issue, error) Get(ctx context.Context, c Caller, projectSlug string, number int) (*Issue, error) List(ctx context.Context, c Caller, projectSlug string, filter IssueFilter) ([]*Issue, error) Update(ctx context.Context, c Caller, projectSlug string, number int, in UpdateIssueInput) (*Issue, error) Assign(ctx context.Context, c Caller, projectSlug string, number int, assignee *uuid.UUID) (*Issue, error) Close(ctx context.Context, c Caller, projectSlug string, number int) error Reopen(ctx context.Context, c Caller, projectSlug string, number int) error } // ===== 输入 DTO ===== type CreateProjectInput struct { Slug string Name string Description string Visibility Visibility } type UpdateProjectInput struct { Name *string Description *string Visibility *Visibility } type CreateRequirementInput struct { Title string Description string OwnerID *uuid.UUID } type UpdateRequirementInput struct { Title *string Description *string OwnerID *uuid.UUID } type RequirementFilter struct { Phase Phase Status Status } type CreateIssueInput struct { Title string Description string RequirementNumber *int AssigneeID *uuid.UUID } type UpdateIssueInput struct { Title *string Description *string RequirementNumber **int } type IssueFilter struct { Status Status Requirement *int AssigneeID *uuid.UUID }