214 lines
6.2 KiB
Go
214 lines
6.2 KiB
Go
package model
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// TaskStatus represents the status of a task
|
|
type TaskStatus string
|
|
|
|
const (
|
|
TaskStatusTodo TaskStatus = "todo"
|
|
TaskStatusInProgress TaskStatus = "in_progress"
|
|
TaskStatusDone TaskStatus = "done"
|
|
TaskStatusCanceled TaskStatus = "canceled"
|
|
)
|
|
|
|
// TaskPriority represents the priority of a task
|
|
type TaskPriority string
|
|
|
|
const (
|
|
TaskPriorityLow TaskPriority = "low"
|
|
TaskPriorityMedium TaskPriority = "medium"
|
|
TaskPriorityHigh TaskPriority = "high"
|
|
)
|
|
|
|
// Task represents a task in the system
|
|
type Task struct {
|
|
BaseModel
|
|
Title string `json:"title" gorm:"not null;type:varchar(255)"`
|
|
Description string `json:"description" gorm:"type:text"`
|
|
Status TaskStatus `json:"status" gorm:"not null;default:'todo';type:varchar(20)"`
|
|
Priority TaskPriority `json:"priority" gorm:"not null;default:'medium';type:varchar(20)"`
|
|
ProjectID string `json:"project_id" gorm:"not null;type:uuid;index;references:projects(id);onDelete:CASCADE"`
|
|
DueDate *string `json:"due_date" gorm:"type:date"`
|
|
Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"`
|
|
Assignees []User `json:"assignees,omitempty" gorm:"many2many:task_assignees;"`
|
|
}
|
|
|
|
// TaskCreateRequest represents the request to create a new task
|
|
type TaskCreateRequest struct {
|
|
Title string `json:"title" validate:"required,min=1,max=255"`
|
|
Description string `json:"description" validate:"max=1000"`
|
|
Status TaskStatus `json:"status" validate:"omitempty,oneof=todo in_progress done canceled"`
|
|
Priority TaskPriority `json:"priority" validate:"omitempty,oneof=low medium high"`
|
|
ProjectID string `json:"project_id" validate:"required,uuid"`
|
|
DueDate *string `json:"due_date"`
|
|
AssigneeIDs []string `json:"assignee_ids"`
|
|
}
|
|
|
|
// TaskUpdateRequest represents the request to update a task
|
|
type TaskUpdateRequest struct {
|
|
Title *string `json:"title,omitempty" validate:"omitempty,min=1,max=255"`
|
|
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
|
|
Status *TaskStatus `json:"status,omitempty" validate:"omitempty,oneof=todo in_progress done canceled"`
|
|
Priority *TaskPriority `json:"priority,omitempty" validate:"omitempty,oneof=low medium high"`
|
|
DueDate *string `json:"due_date,omitempty"`
|
|
AssigneeIDs []string `json:"assignee_ids,omitempty"`
|
|
}
|
|
|
|
// TaskInfo represents public task information
|
|
type TaskInfo struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Status TaskStatus `json:"status"`
|
|
Priority TaskPriority `json:"priority"`
|
|
ProjectID string `json:"project_id"`
|
|
DueDate *string `json:"due_date"`
|
|
Project ProjectInfo `json:"project,omitempty"`
|
|
Assignees []UserInfo `json:"assignees,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// TaskAssignee represents the many-to-many relationship between tasks and users
|
|
type TaskAssignee struct {
|
|
TaskID string `json:"task_id" gorm:"type:uuid;primaryKey"`
|
|
UserID string `json:"user_id" gorm:"type:uuid;primaryKey"`
|
|
Task Task `json:"task,omitempty" gorm:"foreignKey:TaskID"`
|
|
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
|
}
|
|
|
|
// BeforeCreate is called before creating a task
|
|
func (t *Task) BeforeCreate(tx *gorm.DB) error {
|
|
t.BaseModel.BeforeCreate()
|
|
|
|
// Normalize title and description
|
|
t.Title = strings.TrimSpace(t.Title)
|
|
t.Description = strings.TrimSpace(t.Description)
|
|
|
|
// Set default values
|
|
if t.Status == "" {
|
|
t.Status = TaskStatusTodo
|
|
}
|
|
if t.Priority == "" {
|
|
t.Priority = TaskPriorityMedium
|
|
}
|
|
|
|
return t.Validate()
|
|
}
|
|
|
|
// BeforeUpdate is called before updating a task
|
|
func (t *Task) BeforeUpdate(tx *gorm.DB) error {
|
|
t.BaseModel.BeforeUpdate()
|
|
|
|
// Normalize fields if they're being updated
|
|
if t.Title != "" {
|
|
t.Title = strings.TrimSpace(t.Title)
|
|
}
|
|
if t.Description != "" {
|
|
t.Description = strings.TrimSpace(t.Description)
|
|
}
|
|
|
|
return t.Validate()
|
|
}
|
|
|
|
// Validate validates task data
|
|
func (t *Task) Validate() error {
|
|
if t.Title == "" {
|
|
return errors.New("title is required")
|
|
}
|
|
|
|
if len(t.Title) > 255 {
|
|
return errors.New("title must not exceed 255 characters")
|
|
}
|
|
|
|
if t.ProjectID == "" {
|
|
return errors.New("project_id is required")
|
|
}
|
|
|
|
// Validate status
|
|
if t.Status != "" {
|
|
switch t.Status {
|
|
case TaskStatusTodo, TaskStatusInProgress, TaskStatusDone, TaskStatusCanceled:
|
|
// Valid status
|
|
default:
|
|
return errors.New("invalid status")
|
|
}
|
|
}
|
|
|
|
// Validate priority
|
|
if t.Priority != "" {
|
|
switch t.Priority {
|
|
case TaskPriorityLow, TaskPriorityMedium, TaskPriorityHigh:
|
|
// Valid priority
|
|
default:
|
|
return errors.New("invalid priority")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ToTaskInfo converts Task to TaskInfo (public information)
|
|
func (t *Task) ToTaskInfo() TaskInfo {
|
|
taskInfo := TaskInfo{
|
|
ID: t.ID,
|
|
Title: t.Title,
|
|
Description: t.Description,
|
|
Status: t.Status,
|
|
Priority: t.Priority,
|
|
ProjectID: t.ProjectID,
|
|
DueDate: t.DueDate,
|
|
CreatedAt: t.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
UpdatedAt: t.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
|
Assignees: make([]UserInfo, len(t.Assignees)),
|
|
}
|
|
|
|
// Add project info if loaded
|
|
if t.Project.ID != "" {
|
|
taskInfo.Project = t.Project.ToProjectInfo()
|
|
}
|
|
|
|
// Add assignee info if loaded
|
|
for i, assignee := range t.Assignees {
|
|
taskInfo.Assignees[i] = assignee.ToUserInfo()
|
|
}
|
|
|
|
return taskInfo
|
|
}
|
|
|
|
// IsAssignedTo checks if a user is assigned to this task
|
|
func (t *Task) IsAssignedTo(userID string) bool {
|
|
for _, assignee := range t.Assignees {
|
|
if assignee.ID == userID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsCompleted checks if the task is completed
|
|
func (t *Task) IsCompleted() bool {
|
|
return t.Status == TaskStatusDone
|
|
}
|
|
|
|
// IsCanceled checks if the task is canceled
|
|
func (t *Task) IsCanceled() bool {
|
|
return t.Status == TaskStatusCanceled
|
|
}
|
|
|
|
// IsInProgress checks if the task is in progress
|
|
func (t *Task) IsInProgress() bool {
|
|
return t.Status == TaskStatusInProgress
|
|
}
|
|
|
|
// IsTodo checks if the task is todo
|
|
func (t *Task) IsTodo() bool {
|
|
return t.Status == TaskStatusTodo
|
|
}
|