package service import ( "database/sql" "time" "github.com/daniil/homelab-api/internal/model" "github.com/daniil/homelab-api/internal/repository" ) type TaskService struct { taskRepo *repository.TaskRepository } func NewTaskService(taskRepo *repository.TaskRepository) *TaskService { return &TaskService{taskRepo: taskRepo} } func (s *TaskService) Create(userID int64, req *model.CreateTaskRequest) (*model.Task, error) { task := &model.Task{ UserID: userID, Title: req.Title, Description: req.Description, Icon: defaultString(req.Icon, "📋"), Color: defaultString(req.Color, "#6B7280"), Priority: req.Priority, IsRecurring: req.IsRecurring, RecurrenceInterval: defaultInt(req.RecurrenceInterval, 1), } if req.DueDate != nil && *req.DueDate != "" { parsed, err := time.Parse("2006-01-02", *req.DueDate) if err == nil { task.DueDate = sql.NullTime{Time: parsed, Valid: true} } } if req.ReminderTime != nil && *req.ReminderTime != "" { task.ReminderTime = sql.NullString{String: *req.ReminderTime, Valid: true} } if req.RecurrenceType != nil && *req.RecurrenceType != "" { task.RecurrenceType = sql.NullString{String: *req.RecurrenceType, Valid: true} } if req.RecurrenceEndDate != nil && *req.RecurrenceEndDate != "" { parsed, err := time.Parse("2006-01-02", *req.RecurrenceEndDate) if err == nil { task.RecurrenceEndDate = sql.NullTime{Time: parsed, Valid: true} } } if err := s.taskRepo.Create(task); err != nil { return nil, err } task.ProcessForJSON() return task, nil } func (s *TaskService) Get(id, userID int64) (*model.Task, error) { return s.taskRepo.GetByID(id, userID) } func (s *TaskService) List(userID int64, completed *bool) ([]model.Task, error) { tasks, err := s.taskRepo.ListByUser(userID, completed) if err != nil { return nil, err } if tasks == nil { tasks = []model.Task{} } return tasks, nil } func (s *TaskService) GetTodayTasks(userID int64) ([]model.Task, error) { tasks, err := s.taskRepo.GetTodayTasks(userID) if err != nil { return nil, err } if tasks == nil { tasks = []model.Task{} } return tasks, nil } func (s *TaskService) Update(id, userID int64, req *model.UpdateTaskRequest) (*model.Task, error) { task, err := s.taskRepo.GetByID(id, userID) if err != nil { return nil, err } if req.Title != nil { task.Title = *req.Title } if req.Description != nil { task.Description = *req.Description } if req.Icon != nil { task.Icon = *req.Icon } if req.Color != nil { task.Color = *req.Color } if req.Priority != nil { task.Priority = *req.Priority } if req.DueDate != nil { if *req.DueDate == "" { task.DueDate = sql.NullTime{Valid: false} } else { parsed, err := time.Parse("2006-01-02", *req.DueDate) if err == nil { task.DueDate = sql.NullTime{Time: parsed, Valid: true} } } } if req.ReminderTime != nil { if *req.ReminderTime == "" { task.ReminderTime = sql.NullString{Valid: false} } else { task.ReminderTime = sql.NullString{String: *req.ReminderTime, Valid: true} } } // Handle recurring fields if req.IsRecurring != nil { task.IsRecurring = *req.IsRecurring } if req.RecurrenceType != nil { if *req.RecurrenceType == "" { task.RecurrenceType = sql.NullString{Valid: false} } else { task.RecurrenceType = sql.NullString{String: *req.RecurrenceType, Valid: true} } } if req.RecurrenceInterval != nil { task.RecurrenceInterval = *req.RecurrenceInterval } if req.RecurrenceEndDate != nil { if *req.RecurrenceEndDate == "" { task.RecurrenceEndDate = sql.NullTime{Valid: false} } else { parsed, err := time.Parse("2006-01-02", *req.RecurrenceEndDate) if err == nil { task.RecurrenceEndDate = sql.NullTime{Time: parsed, Valid: true} } } } if err := s.taskRepo.Update(task); err != nil { return nil, err } task.ProcessForJSON() return task, nil } func (s *TaskService) Delete(id, userID int64) error { return s.taskRepo.Delete(id, userID) } func (s *TaskService) Complete(id, userID int64) (*model.Task, error) { // First, get the task to check if it's recurring task, err := s.taskRepo.GetByID(id, userID) if err != nil { return nil, err } // Complete the current task if err := s.taskRepo.Complete(id, userID); err != nil { return nil, err } // If task is recurring, create the next occurrence if task.IsRecurring && task.RecurrenceType.Valid && task.DueDate.Valid { s.createNextRecurrence(task) } return s.taskRepo.GetByID(id, userID) } func (s *TaskService) createNextRecurrence(task *model.Task) { // Calculate next due date based on recurrence type var nextDueDate time.Time interval := task.RecurrenceInterval if interval < 1 { interval = 1 } currentDue := task.DueDate.Time switch task.RecurrenceType.String { case "daily": nextDueDate = currentDue.AddDate(0, 0, interval) case "weekly": nextDueDate = currentDue.AddDate(0, 0, 7*interval) case "monthly": nextDueDate = currentDue.AddDate(0, interval, 0) case "custom": nextDueDate = currentDue.AddDate(0, 0, interval) default: return // Unknown recurrence type, don't create } // Check if next date is past the end date if task.RecurrenceEndDate.Valid && nextDueDate.After(task.RecurrenceEndDate.Time) { return // Don't create task past end date } // Create the next task nextTask := &model.Task{ UserID: task.UserID, Title: task.Title, Description: task.Description, Icon: task.Icon, Color: task.Color, Priority: task.Priority, DueDate: sql.NullTime{Time: nextDueDate, Valid: true}, ReminderTime: task.ReminderTime, IsRecurring: true, RecurrenceType: task.RecurrenceType, RecurrenceInterval: task.RecurrenceInterval, RecurrenceEndDate: task.RecurrenceEndDate, ParentTaskID: sql.NullInt64{Int64: task.ID, Valid: true}, } // Silently create, ignore errors s.taskRepo.Create(nextTask) } func (s *TaskService) Uncomplete(id, userID int64) (*model.Task, error) { if err := s.taskRepo.Uncomplete(id, userID); err != nil { return nil, err } return s.taskRepo.GetByID(id, userID) }