Skip to content

Commit 7e1cc17

Browse files
kenliuclaude
andcommitted
Update deprecated endpoints for v1 API
Phase 2 changes: - Quick add: quick/add → tasks/quick (REST API with JSON) - Completed tasks: completed/get_all → tasks/completed/by_completion_date - Add doRestApi() method for JSON REST endpoints - Update completed endpoint with required since/until date parameters - Improve error handling to show full API error responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f7b1af7 commit 7e1cc17

File tree

4 files changed

+99
-14
lines changed

4 files changed

+99
-14
lines changed

MIGRATION_PLAN.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,48 @@ const Server = "https://api.todoist.com/api/v1/"
4343
```
4444

4545
#### 2. Quick Add Endpoint
46-
**File**: `lib/todoist.go:106`
46+
**File**: `lib/todoist.go`
4747
```go
4848
// Current
49+
var r ExecResult
50+
values := url.Values{"text": {text}}
4951
return c.doApi(ctx, http.MethodPost, "quick/add", values, &r)
5052

5153
// Target
52-
return c.doApi(ctx, http.MethodPost, "tasks/quick_add", values, &r)
54+
var item Item
55+
body := map[string]interface{}{"text": text}
56+
return c.doRestApi(ctx, http.MethodPost, "tasks/quick", body, &item)
5357
```
5458

59+
**Changes**:
60+
- Endpoint: `quick/add``tasks/quick`
61+
- Content-Type: `application/x-www-form-urlencoded``application/json`
62+
- Request body: URL-encoded values → JSON body
63+
- Response: `ExecResult``Item` (task object)
64+
- Added new `doRestApi()` method for JSON REST endpoints
65+
5566
#### 3. Completed Tasks Endpoint
5667
**File**: `lib/completed.go:15`
5768
```go
5869
// Current
5970
return c.doApi(ctx, http.MethodPost, "completed/get_all", url.Values{}, &r)
6071

61-
// Target (note: may need to verify exact endpoint and response format)
62-
return c.doApi(ctx, http.MethodGet, "tasks/completed_by_completion_date", url.Values{}, &r)
72+
// Target
73+
now := time.Now()
74+
since := now.AddDate(0, 0, -30).Format(time.RFC3339)
75+
until := now.Format(time.RFC3339)
76+
params := url.Values{
77+
"since": {since},
78+
"until": {until},
79+
}
80+
return c.doApi(ctx, http.MethodGet, "tasks/completed/by_completion_date", params, &r)
6381
```
6482

65-
**Note**: The completed tasks endpoint may require additional investigation:
66-
- v1 uses `GET` method (vs `POST` in v9)
67-
- Response format may differ
68-
- May need pagination handling
83+
**Changes**:
84+
- HTTP method: `POST``GET`
85+
- Endpoint: `completed/get_all``tasks/completed/by_completion_date`
86+
- Required parameters: `since` and `until` (ISO 8601 UTC format with Z suffix, max 3 month range)
87+
- Default behavior: Last 30 days of completed tasks
6988

7089
---
7190

lib/completed.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"net/http"
66
"net/url"
7+
"time"
78
)
89

910
type Completed struct {
@@ -12,5 +13,16 @@ type Completed struct {
1213
}
1314

1415
func (c *Client) CompletedAll(ctx context.Context, r *Completed) error {
15-
return c.doApi(ctx, http.MethodPost, "completed/get_all", url.Values{}, &r)
16+
// v1 API requires since/until parameters in UTC with "Z" suffix (max 3 month range)
17+
// Default to last 30 days
18+
now := time.Now().UTC()
19+
since := now.AddDate(0, 0, -30).Format("2006-01-02T15:04:05Z")
20+
until := now.Format("2006-01-02T15:04:05Z")
21+
22+
params := url.Values{
23+
"since": {since},
24+
"until": {until},
25+
}
26+
27+
return c.doApi(ctx, http.MethodGet, "tasks/completed/by_completion_date", params, &r)
1628
}

lib/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7+
"io"
78
"net/http"
89
)
910

@@ -21,9 +22,14 @@ func ParseAPIError(prefix string, resp *http.Response) error {
2122
Error string `json:"error"`
2223
}
2324

24-
json.NewDecoder(resp.Body).Decode(&e)
25+
bodyBytes, _ := io.ReadAll(resp.Body)
26+
resp.Body.Close()
27+
28+
json.Unmarshal(bodyBytes, &e)
2529
if e.Error != "" {
2630
errMsg = fmt.Sprintf("%s: %s", errMsg, e.Error)
31+
} else if len(bodyBytes) > 0 {
32+
errMsg = fmt.Sprintf("%s: %s", errMsg, string(bodyBytes))
2733
}
2834

2935
return errors.New(errMsg)

lib/todoist.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,54 @@ func (c *Client) doApi(ctx context.Context, method string, uri string, params ur
8585
return json.NewDecoder(resp.Body).Decode(&res)
8686
}
8787

88+
func (c *Client) doRestApi(ctx context.Context, method string, uri string, body interface{}, res interface{}) error {
89+
c.Log("doRestApi: called")
90+
u, err := url.Parse(Server)
91+
if err != nil {
92+
return err
93+
}
94+
u.Path = path.Join(u.Path, uri)
95+
96+
c.Log("config: %#v", c.config)
97+
98+
var bodyReader io.Reader
99+
if body != nil {
100+
jsonData, err := json.Marshal(body)
101+
if err != nil {
102+
return err
103+
}
104+
bodyReader = strings.NewReader(string(jsonData))
105+
}
106+
107+
req, err := http.NewRequest(method, u.String(), bodyReader)
108+
if err != nil {
109+
return err
110+
}
111+
112+
req.Header.Set("Content-Type", "application/json")
113+
req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
114+
req = req.WithContext(ctx)
115+
116+
c.Log("request: %#v", req)
117+
c.Log("request.URL: %#v", req.URL)
118+
119+
resp, err := c.Do(req)
120+
if err != nil {
121+
return err
122+
}
123+
defer resp.Body.Close()
124+
125+
c.Log("response: %#v", resp)
126+
127+
if resp.StatusCode != http.StatusOK {
128+
c.Log(ParseAPIError("bad request", resp).Error())
129+
return ParseAPIError("bad request", resp)
130+
} else if res == nil {
131+
return nil
132+
}
133+
return json.NewDecoder(resp.Body).Decode(&res)
134+
}
135+
88136
type ExecResult struct {
89137
SyncToken string `json:"sync_token"`
90138
SyncStatus interface{} `json:"sync_status"`
@@ -97,13 +145,13 @@ func (c *Client) ExecCommands(ctx context.Context, commands Commands) error {
97145
}
98146

99147
func (c *Client) QuickCommand(ctx context.Context, text string) error {
100-
var r ExecResult
148+
var item Item
101149

102-
values := url.Values{
103-
"text": {text},
150+
body := map[string]interface{}{
151+
"text": text,
104152
}
105153

106-
return c.doApi(ctx, http.MethodPost, "quick/add", values, &r)
154+
return c.doRestApi(ctx, http.MethodPost, "tasks/quick", body, &item)
107155
}
108156

109157
func (c *Client) Sync(ctx context.Context) error {

0 commit comments

Comments
 (0)