package repositories import ( "time" "gorm.io/gorm" "github.com/treytartt/mycrib-api/internal/models" ) // TaskRepository handles database operations for tasks type TaskRepository struct { db *gorm.DB } // NewTaskRepository creates a new task repository func NewTaskRepository(db *gorm.DB) *TaskRepository { return &TaskRepository{db: db} } // === Task CRUD === // FindByID finds a task by ID with preloaded relations func (r *TaskRepository) FindByID(id uint) (*models.Task, error) { var task models.Task err := r.db.Preload("Residence"). Preload("CreatedBy"). Preload("AssignedTo"). Preload("Category"). Preload("Priority"). Preload("Status"). Preload("Frequency"). Preload("Completions"). Preload("Completions.CompletedBy"). First(&task, id).Error if err != nil { return nil, err } return &task, nil } // FindByResidence finds all tasks for a residence func (r *TaskRepository) FindByResidence(residenceID uint) ([]models.Task, error) { var tasks []models.Task err := r.db.Preload("CreatedBy"). Preload("AssignedTo"). Preload("Category"). Preload("Priority"). Preload("Status"). Preload("Frequency"). Preload("Completions"). Where("residence_id = ?", residenceID). Order("due_date ASC NULLS LAST, created_at DESC"). Find(&tasks).Error return tasks, err } // FindByUser finds all tasks accessible to a user (across all their residences) func (r *TaskRepository) FindByUser(userID uint, residenceIDs []uint) ([]models.Task, error) { var tasks []models.Task err := r.db.Preload("Residence"). Preload("CreatedBy"). Preload("AssignedTo"). Preload("Category"). Preload("Priority"). Preload("Status"). Preload("Frequency"). Preload("Completions"). Where("residence_id IN ?", residenceIDs). Order("due_date ASC NULLS LAST, created_at DESC"). Find(&tasks).Error return tasks, err } // Create creates a new task func (r *TaskRepository) Create(task *models.Task) error { return r.db.Create(task).Error } // Update updates a task func (r *TaskRepository) Update(task *models.Task) error { return r.db.Save(task).Error } // Delete hard-deletes a task func (r *TaskRepository) Delete(id uint) error { return r.db.Delete(&models.Task{}, id).Error } // === Task State Operations === // MarkInProgress marks a task as in progress func (r *TaskRepository) MarkInProgress(id uint, statusID uint) error { return r.db.Model(&models.Task{}). Where("id = ?", id). Update("status_id", statusID).Error } // Cancel cancels a task func (r *TaskRepository) Cancel(id uint) error { return r.db.Model(&models.Task{}). Where("id = ?", id). Update("is_cancelled", true).Error } // Uncancel uncancels a task func (r *TaskRepository) Uncancel(id uint) error { return r.db.Model(&models.Task{}). Where("id = ?", id). Update("is_cancelled", false).Error } // Archive archives a task func (r *TaskRepository) Archive(id uint) error { return r.db.Model(&models.Task{}). Where("id = ?", id). Update("is_archived", true).Error } // Unarchive unarchives a task func (r *TaskRepository) Unarchive(id uint) error { return r.db.Model(&models.Task{}). Where("id = ?", id). Update("is_archived", false).Error } // === Kanban Board === // GetKanbanData retrieves tasks organized for kanban display func (r *TaskRepository) GetKanbanData(residenceID uint, daysThreshold int) (*models.KanbanBoard, error) { var tasks []models.Task err := r.db.Preload("CreatedBy"). Preload("AssignedTo"). Preload("Category"). Preload("Priority"). Preload("Status"). Preload("Frequency"). Preload("Completions"). Preload("Completions.CompletedBy"). Where("residence_id = ? AND is_archived = ?", residenceID, false). Order("due_date ASC NULLS LAST, priority_id DESC, created_at DESC"). Find(&tasks).Error if err != nil { return nil, err } // Organize into columns now := time.Now().UTC() threshold := now.AddDate(0, 0, daysThreshold) overdue := make([]models.Task, 0) dueSoon := make([]models.Task, 0) upcoming := make([]models.Task, 0) inProgress := make([]models.Task, 0) completed := make([]models.Task, 0) cancelled := make([]models.Task, 0) for _, task := range tasks { if task.IsCancelled { cancelled = append(cancelled, task) continue } // Check if completed (has completions) if len(task.Completions) > 0 { completed = append(completed, task) continue } // Check status for in-progress (status_id = 2 typically) if task.Status != nil && task.Status.Name == "In Progress" { inProgress = append(inProgress, task) continue } // Check due date if task.DueDate != nil { if task.DueDate.Before(now) { overdue = append(overdue, task) } else if task.DueDate.Before(threshold) { dueSoon = append(dueSoon, task) } else { upcoming = append(upcoming, task) } } else { upcoming = append(upcoming, task) } } columns := []models.KanbanColumn{ { Name: "overdue_tasks", DisplayName: "Overdue", ButtonTypes: []string{"edit", "cancel", "mark_in_progress"}, Icons: map[string]string{"ios": "exclamationmark.triangle", "android": "Warning"}, Color: "#FF3B30", Tasks: overdue, Count: len(overdue), }, { Name: "due_soon_tasks", DisplayName: "Due Soon", ButtonTypes: []string{"edit", "complete", "mark_in_progress"}, Icons: map[string]string{"ios": "clock", "android": "Schedule"}, Color: "#FF9500", Tasks: dueSoon, Count: len(dueSoon), }, { Name: "upcoming_tasks", DisplayName: "Upcoming", ButtonTypes: []string{"edit", "cancel"}, Icons: map[string]string{"ios": "calendar", "android": "Event"}, Color: "#007AFF", Tasks: upcoming, Count: len(upcoming), }, { Name: "in_progress_tasks", DisplayName: "In Progress", ButtonTypes: []string{"edit", "complete"}, Icons: map[string]string{"ios": "hammer", "android": "Build"}, Color: "#5856D6", Tasks: inProgress, Count: len(inProgress), }, { Name: "completed_tasks", DisplayName: "Completed", ButtonTypes: []string{"view"}, Icons: map[string]string{"ios": "checkmark.circle", "android": "CheckCircle"}, Color: "#34C759", Tasks: completed, Count: len(completed), }, { Name: "cancelled_tasks", DisplayName: "Cancelled", ButtonTypes: []string{"uncancel", "delete"}, Icons: map[string]string{"ios": "xmark.circle", "android": "Cancel"}, Color: "#8E8E93", Tasks: cancelled, Count: len(cancelled), }, } return &models.KanbanBoard{ Columns: columns, DaysThreshold: daysThreshold, ResidenceID: string(rune(residenceID)), }, nil } // === Lookup Operations === // GetAllCategories returns all task categories func (r *TaskRepository) GetAllCategories() ([]models.TaskCategory, error) { var categories []models.TaskCategory err := r.db.Order("display_order, name").Find(&categories).Error return categories, err } // GetAllPriorities returns all task priorities func (r *TaskRepository) GetAllPriorities() ([]models.TaskPriority, error) { var priorities []models.TaskPriority err := r.db.Order("level").Find(&priorities).Error return priorities, err } // GetAllStatuses returns all task statuses func (r *TaskRepository) GetAllStatuses() ([]models.TaskStatus, error) { var statuses []models.TaskStatus err := r.db.Order("display_order").Find(&statuses).Error return statuses, err } // GetAllFrequencies returns all task frequencies func (r *TaskRepository) GetAllFrequencies() ([]models.TaskFrequency, error) { var frequencies []models.TaskFrequency err := r.db.Order("display_order").Find(&frequencies).Error return frequencies, err } // FindStatusByName finds a status by name func (r *TaskRepository) FindStatusByName(name string) (*models.TaskStatus, error) { var status models.TaskStatus err := r.db.Where("name = ?", name).First(&status).Error if err != nil { return nil, err } return &status, nil } // CountByResidence counts tasks in a residence func (r *TaskRepository) CountByResidence(residenceID uint) (int64, error) { var count int64 err := r.db.Model(&models.Task{}). Where("residence_id = ? AND is_cancelled = ? AND is_archived = ?", residenceID, false, false). Count(&count).Error return count, err } // === Task Completion Operations === // CreateCompletion creates a new task completion func (r *TaskRepository) CreateCompletion(completion *models.TaskCompletion) error { return r.db.Create(completion).Error } // FindCompletionByID finds a completion by ID func (r *TaskRepository) FindCompletionByID(id uint) (*models.TaskCompletion, error) { var completion models.TaskCompletion err := r.db.Preload("Task"). Preload("CompletedBy"). First(&completion, id).Error if err != nil { return nil, err } return &completion, nil } // FindCompletionsByTask finds all completions for a task func (r *TaskRepository) FindCompletionsByTask(taskID uint) ([]models.TaskCompletion, error) { var completions []models.TaskCompletion err := r.db.Preload("CompletedBy"). Where("task_id = ?", taskID). Order("completed_at DESC"). Find(&completions).Error return completions, err } // FindCompletionsByUser finds all completions by a user func (r *TaskRepository) FindCompletionsByUser(userID uint, residenceIDs []uint) ([]models.TaskCompletion, error) { var completions []models.TaskCompletion err := r.db.Preload("Task"). Preload("CompletedBy"). Joins("JOIN task_task ON task_task.id = task_taskcompletion.task_id"). Where("task_task.residence_id IN ?", residenceIDs). Order("completed_at DESC"). Find(&completions).Error return completions, err } // DeleteCompletion deletes a task completion func (r *TaskRepository) DeleteCompletion(id uint) error { return r.db.Delete(&models.TaskCompletion{}, id).Error }