diff options
Diffstat (limited to 'internal/api/todoist.go')
| -rw-r--r-- | internal/api/todoist.go | 155 |
1 files changed, 46 insertions, 109 deletions
diff --git a/internal/api/todoist.go b/internal/api/todoist.go index be699ce..d6058d3 100644 --- a/internal/api/todoist.go +++ b/internal/api/todoist.go @@ -10,22 +10,19 @@ import ( ) const ( - todoistBaseURL = "https://api.todoist.com/api/v1" - todoistSyncBaseURL = "https://api.todoist.com/sync/v9" + todoistBaseURL = "https://api.todoist.com/api/v1" ) // TodoistClient handles interactions with the Todoist API type TodoistClient struct { BaseClient - syncClient BaseClient - apiKey string + apiKey string } // NewTodoistClient creates a new Todoist API client func NewTodoistClient(apiKey string) *TodoistClient { return &TodoistClient{ BaseClient: NewBaseClient(todoistBaseURL), - syncClient: NewBaseClient(todoistSyncBaseURL), apiKey: apiKey, } } @@ -53,43 +50,33 @@ type todoistProjectResponse struct { Name string `json:"name"` } -// Sync API v9 response types -// TodoistSyncResponse represents the Sync API response -type TodoistSyncResponse struct { - SyncToken string `json:"sync_token"` - FullSync bool `json:"full_sync"` - Items []SyncItemResponse `json:"items"` - Projects []SyncProjectResponse `json:"projects"` -} - -// SyncItemResponse represents a task item from Sync API -type SyncItemResponse struct { - ID string `json:"id"` - Content string `json:"content"` - Description string `json:"description"` - ProjectID string `json:"project_id"` - Priority int `json:"priority"` - Labels []string `json:"labels"` - Due *dueInfo `json:"due"` - IsCompleted bool `json:"is_completed"` - IsDeleted bool `json:"is_deleted"` - AddedAt string `json:"added_at"` -} - -// SyncProjectResponse represents a project from Sync API -type SyncProjectResponse struct { - ID string `json:"id"` - Name string `json:"name"` - IsDeleted bool `json:"is_deleted"` +// todoistTasksPage represents the paginated response from the Todoist REST API v1 +type todoistTasksPage struct { + Results []todoistTaskResponse `json:"results"` + NextCursor *string `json:"next_cursor"` } // GetTasks fetches all active tasks from Todoist func (c *TodoistClient) GetTasks(ctx context.Context) ([]models.Task, error) { - var apiTasks []todoistTaskResponse - if err := c.Get(ctx, "/tasks", c.authHeaders(), &apiTasks); err != nil { - return nil, fmt.Errorf("failed to fetch tasks: %w", err) + var allTasks []todoistTaskResponse + cursor := "" + for { + path := "/tasks" + if cursor != "" { + path = "/tasks?cursor=" + cursor + } + var page todoistTasksPage + if err := c.Get(ctx, path, c.authHeaders(), &page); err != nil { + return nil, fmt.Errorf("failed to fetch tasks: %w", err) + } + allTasks = append(allTasks, page.Results...) + if page.NextCursor == nil || *page.NextCursor == "" { + break + } + cursor = *page.NextCursor } + apiTasks := allTasks // Fetch projects to get project names projects, err := c.GetProjects(ctx) @@ -129,12 +116,32 @@ func (c *TodoistClient) GetTasks(ctx context.Context) ([]models.Task, error) { return tasks, nil } +// todoistProjectsPage represents the paginated response for projects +type todoistProjectsPage struct { + Results []todoistProjectResponse `json:"results"` + NextCursor *string `json:"next_cursor"` +} + // GetProjects fetches all projects func (c *TodoistClient) GetProjects(ctx context.Context) ([]models.Project, error) { - var apiProjects []todoistProjectResponse - if err := c.Get(ctx, "/projects", c.authHeaders(), &apiProjects); err != nil { - return nil, fmt.Errorf("failed to fetch projects: %w", err) + var allProjects []todoistProjectResponse + cursor := "" + for { + path := "/projects" + if cursor != "" { + path = "/projects?cursor=" + cursor + } + var page todoistProjectsPage + if err := c.Get(ctx, path, c.authHeaders(), &page); err != nil { + return nil, fmt.Errorf("failed to fetch projects: %w", err) + } + allProjects = append(allProjects, page.Results...) + if page.NextCursor == nil || *page.NextCursor == "" { + break + } + cursor = *page.NextCursor } + apiProjects := allProjects projects := make([]models.Project, 0, len(apiProjects)) for _, apiProj := range apiProjects { @@ -147,76 +154,6 @@ func (c *TodoistClient) GetProjects(ctx context.Context) ([]models.Project, erro return projects, nil } -// Sync performs an incremental sync using the Sync API v9 -func (c *TodoistClient) Sync(ctx context.Context, syncToken string) (*TodoistSyncResponse, error) { - if syncToken == "" { - syncToken = "*" // Full sync - } - - payload := map[string]interface{}{ - "sync_token": syncToken, - "resource_types": []string{"items", "projects"}, - } - - var syncResp TodoistSyncResponse - if err := c.syncClient.Post(ctx, "/sync", c.authHeaders(), payload, &syncResp); err != nil { - return nil, fmt.Errorf("failed to perform sync: %w", err) - } - - 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 task, ok := ConvertSyncItemToTask(item, projectMap); ok { - tasks = append(tasks, task) - } - } - return tasks -} - -// BuildProjectMapFromSync builds a project ID to name map from sync response -func BuildProjectMapFromSync(projects []SyncProjectResponse) map[string]string { - projectMap := make(map[string]string) - for _, proj := range projects { - if !proj.IsDeleted { - projectMap[proj.ID] = proj.Name - } - } - return projectMap -} - // CreateTask creates a new task in Todoist func (c *TodoistClient) CreateTask(ctx context.Context, content, projectID string, dueDate *time.Time, priority int) (*models.Task, error) { payload := map[string]interface{}{"content": content} |
