summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/handlers.go95
-rw-r--r--internal/handlers/handlers_test.go27
2 files changed, 115 insertions, 7 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index 0af2bba..f53eced 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -350,9 +350,10 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models
return data, nil
}
-// fetchTasks fetches tasks from cache or API
+// fetchTasks fetches tasks from cache or API using incremental sync
func (h *Handler) fetchTasks(ctx context.Context, forceRefresh bool) ([]models.Task, error) {
cacheKey := store.CacheKeyTodoistTasks
+ syncService := "todoist"
// Check cache validity
if !forceRefresh {
@@ -362,8 +363,20 @@ func (h *Handler) fetchTasks(ctx context.Context, forceRefresh bool) ([]models.T
}
}
- // Fetch from API
- tasks, err := h.todoistClient.GetTasks(ctx)
+ // Get stored sync token (empty string means full sync)
+ syncToken, err := h.store.GetSyncToken(syncService)
+ if err != nil {
+ log.Printf("Failed to get sync token, will do full sync: %v", err)
+ syncToken = ""
+ }
+
+ // Force full sync if requested
+ if forceRefresh {
+ syncToken = ""
+ }
+
+ // Fetch using Sync API
+ syncResp, err := h.todoistClient.Sync(ctx, syncToken)
if err != nil {
// Try to return cached data even if stale
cachedTasks, cacheErr := h.store.GetTasks()
@@ -373,9 +386,41 @@ func (h *Handler) fetchTasks(ctx context.Context, forceRefresh bool) ([]models.T
return nil, err
}
- // Save to cache
- if err := h.store.SaveTasks(tasks); err != nil {
- log.Printf("Failed to save tasks to cache: %v", err)
+ // Build project map from sync response
+ projectMap := api.BuildProjectMapFromSync(syncResp.Projects)
+
+ // Process sync response
+ if syncResp.FullSync {
+ // Full sync: replace all tasks
+ tasks := api.ConvertSyncItemsToTasks(syncResp.Items, projectMap)
+ if err := h.store.SaveTasks(tasks); err != nil {
+ log.Printf("Failed to save tasks to cache: %v", err)
+ }
+ } else {
+ // Incremental sync: merge changes
+ var deletedIDs []string
+ for _, item := range syncResp.Items {
+ if item.IsDeleted || item.IsCompleted {
+ deletedIDs = append(deletedIDs, item.ID)
+ } else {
+ // Upsert active task
+ task := h.convertSyncItemToTask(item, projectMap)
+ if err := h.store.UpsertTask(task); err != nil {
+ log.Printf("Failed to upsert task %s: %v", item.ID, err)
+ }
+ }
+ }
+ // Delete removed tasks
+ if len(deletedIDs) > 0 {
+ if err := h.store.DeleteTasksByIDs(deletedIDs); err != nil {
+ log.Printf("Failed to delete tasks: %v", err)
+ }
+ }
+ }
+
+ // Store the new sync token
+ if err := h.store.SetSyncToken(syncService, syncResp.SyncToken); err != nil {
+ log.Printf("Failed to save sync token: %v", err)
}
// Update cache metadata
@@ -383,7 +428,43 @@ func (h *Handler) fetchTasks(ctx context.Context, forceRefresh bool) ([]models.T
log.Printf("Failed to update cache metadata: %v", err)
}
- return tasks, nil
+ return h.store.GetTasks()
+}
+
+// convertSyncItemToTask converts a sync item to a Task model
+func (h *Handler) convertSyncItemToTask(item api.SyncItemResponse, projectMap map[string]string) models.Task {
+ 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/showTask?id=%s", item.ID),
+ }
+
+ if item.AddedAt != "" {
+ if createdAt, err := time.Parse(time.RFC3339, item.AddedAt); err == nil {
+ task.CreatedAt = createdAt
+ }
+ }
+
+ if item.Due != nil {
+ var dueDate time.Time
+ var err error
+ if item.Due.Datetime != "" {
+ dueDate, err = time.Parse(time.RFC3339, item.Due.Datetime)
+ } else if item.Due.Date != "" {
+ dueDate, err = time.Parse("2006-01-02", item.Due.Date)
+ }
+ if err == nil {
+ task.DueDate = &dueDate
+ }
+ }
+
+ return task
}
// fetchNotes fetches notes from cache or filesystem
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go
index 3ea2a3e..1aa72cc 100644
--- a/internal/handlers/handlers_test.go
+++ b/internal/handlers/handlers_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "task-dashboard/internal/api"
"task-dashboard/internal/config"
"task-dashboard/internal/models"
"task-dashboard/internal/store"
@@ -82,6 +83,32 @@ func (m *mockTodoistClient) CompleteTask(ctx context.Context, taskID string) err
return nil
}
+func (m *mockTodoistClient) Sync(ctx context.Context, syncToken string) (*api.TodoistSyncResponse, error) {
+ if m.err != nil {
+ return nil, m.err
+ }
+ // Return a mock sync response with tasks converted to sync items
+ items := make([]api.SyncItemResponse, 0, len(m.tasks))
+ for _, task := range m.tasks {
+ items = append(items, api.SyncItemResponse{
+ ID: task.ID,
+ Content: task.Content,
+ Description: task.Description,
+ ProjectID: task.ProjectID,
+ Priority: task.Priority,
+ Labels: task.Labels,
+ IsCompleted: task.Completed,
+ IsDeleted: false,
+ })
+ }
+ return &api.TodoistSyncResponse{
+ SyncToken: "test-sync-token",
+ FullSync: true,
+ Items: items,
+ Projects: []api.SyncProjectResponse{},
+ }, nil
+}
+
// mockTrelloClient creates a mock Trello client for testing
type mockTrelloClient struct {
boards []models.Board