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 }