1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
package api
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"task-dashboard/internal/models"
)
const (
todoistBaseURL = "https://api.todoist.com/rest/v2"
)
// TodoistClient handles interactions with the Todoist API
type TodoistClient struct {
apiKey string
httpClient *http.Client
}
// NewTodoistClient creates a new Todoist API client
func NewTodoistClient(apiKey string) *TodoistClient {
return &TodoistClient{
apiKey: apiKey,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// todoistTaskResponse represents the API response structure
type todoistTaskResponse 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 *struct {
Date string `json:"date"`
Datetime string `json:"datetime"`
} `json:"due"`
URL string `json:"url"`
CreatedAt string `json:"created_at"`
}
// todoistProjectResponse represents the project API response
type todoistProjectResponse struct {
ID string `json:"id"`
Name string `json:"name"`
}
// GetTasks fetches all active tasks from Todoist
func (c *TodoistClient) GetTasks(ctx context.Context) ([]models.Task, error) {
req, err := http.NewRequestWithContext(ctx, "GET", todoistBaseURL+"/tasks", nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch tasks: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("todoist API error (status %d): %s", resp.StatusCode, string(body))
}
var apiTasks []todoistTaskResponse
if err := json.NewDecoder(resp.Body).Decode(&apiTasks); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
// Fetch projects to get project names
projects, err := c.GetProjects(ctx)
if err != nil {
// If we can't get projects, continue with empty project names
projects = make(map[string]string)
}
// Convert to our model
tasks := make([]models.Task, 0, len(apiTasks))
for _, apiTask := range apiTasks {
task := models.Task{
ID: apiTask.ID,
Content: apiTask.Content,
Description: apiTask.Description,
ProjectID: apiTask.ProjectID,
ProjectName: projects[apiTask.ProjectID],
Priority: apiTask.Priority,
Completed: false,
Labels: apiTask.Labels,
URL: apiTask.URL,
}
// Parse created_at
if createdAt, err := time.Parse(time.RFC3339, apiTask.CreatedAt); err == nil {
task.CreatedAt = createdAt
}
// Parse due date
if apiTask.Due != nil {
var dueDate time.Time
if apiTask.Due.Datetime != "" {
dueDate, err = time.Parse(time.RFC3339, apiTask.Due.Datetime)
} else if apiTask.Due.Date != "" {
dueDate, err = time.Parse("2006-01-02", apiTask.Due.Date)
}
if err == nil {
task.DueDate = &dueDate
}
}
tasks = append(tasks, task)
}
return tasks, nil
}
// GetProjects fetches all projects and returns a map of project ID to name
func (c *TodoistClient) GetProjects(ctx context.Context) (map[string]string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", todoistBaseURL+"/projects", nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch projects: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("todoist API error (status %d): %s", resp.StatusCode, string(body))
}
var apiProjects []todoistProjectResponse
if err := json.NewDecoder(resp.Body).Decode(&apiProjects); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
// Convert to map
projects := make(map[string]string, len(apiProjects))
for _, project := range apiProjects {
projects[project.ID] = project.Name
}
return projects, nil
}
// 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) {
// This will be implemented in Phase 2
return nil, fmt.Errorf("not implemented yet")
}
// CompleteTask marks a task as complete in Todoist
func (c *TodoistClient) CompleteTask(ctx context.Context, taskID string) error {
// This will be implemented in Phase 2
return fmt.Errorf("not implemented yet")
}
|