From e85b42d373de55781af9d699b246c0d6a492aec1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Mar 2026 10:04:57 +0000 Subject: refactor: RF-03/06 extract groupMeals helper, eliminate convertSyncItemToTask wrapper RF-03: Extract shared groupMeals helper into internal/handlers/meals.go. Both HandleTabMeals and BuildTimeline now call groupMeals instead of duplicating the date+mealType grouping algorithm inline. CombinedMeal gains ID and Meals fields to carry the first-meal ID and original records needed by BuildTimeline when constructing TimelineItems. RF-06: Add api.ConvertSyncItemToTask for single-item conversion. ConvertSyncItemsToTasks now delegates to it, eliminating duplication. The Handler.convertSyncItemToTask wrapper (which allocated a one-element slice just to unwrap it) is deleted; its caller uses api.ConvertSyncItemToTask directly. Covered by TestConvertSyncItemToTask in internal/api/todoist_test.go. Co-Authored-By: Claude Sonnet 4.6 --- internal/api/todoist.go | 54 ++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 23 deletions(-) (limited to 'internal/api/todoist.go') diff --git a/internal/api/todoist.go b/internal/api/todoist.go index 2745e3e..be699ce 100644 --- a/internal/api/todoist.go +++ b/internal/api/todoist.go @@ -166,34 +166,42 @@ func (c *TodoistClient) Sync(ctx context.Context, syncToken string) (*TodoistSyn return &syncResp, nil } +// ConvertSyncItemToTask converts a single sync item to a Task model. +// Returns the task and true if the item is active, or a zero Task and false if it should be skipped. +func ConvertSyncItemToTask(item SyncItemResponse, projectMap map[string]string) (models.Task, bool) { + if item.IsCompleted || item.IsDeleted { + return models.Task{}, false + } + + task := models.Task{ + ID: item.ID, + Content: item.Content, + Description: item.Description, + ProjectID: item.ProjectID, + ProjectName: projectMap[item.ProjectID], + Priority: item.Priority, + Completed: false, + Labels: item.Labels, + URL: fmt.Sprintf("https://todoist.com/app/task/%s", item.ID), + } + + if item.AddedAt != "" { + if createdAt, err := time.Parse(time.RFC3339, item.AddedAt); err == nil { + task.CreatedAt = createdAt + } + } + + task.DueDate = parseDueDate(item.Due) + return task, true +} + // ConvertSyncItemsToTasks converts sync API items to Task models func ConvertSyncItemsToTasks(items []SyncItemResponse, projectMap map[string]string) []models.Task { tasks := make([]models.Task, 0, len(items)) for _, item := range items { - if item.IsCompleted || item.IsDeleted { - continue - } - - task := models.Task{ - ID: item.ID, - Content: item.Content, - Description: item.Description, - ProjectID: item.ProjectID, - ProjectName: projectMap[item.ProjectID], - Priority: item.Priority, - Completed: false, - Labels: item.Labels, - URL: fmt.Sprintf("https://todoist.com/app/task/%s", item.ID), + if task, ok := ConvertSyncItemToTask(item, projectMap); ok { + tasks = append(tasks, task) } - - if item.AddedAt != "" { - if createdAt, err := time.Parse(time.RFC3339, item.AddedAt); err == nil { - task.CreatedAt = createdAt - } - } - - task.DueDate = parseDueDate(item.Due) - tasks = append(tasks, task) } return tasks } -- cgit v1.2.3