Skip to content

Commit 7c66b35

Browse files
committed
plugin api: migrate to new Todoist v1 API
The key difference is that the endpoints are paginated, so we must make N requests until there is nothing left
1 parent f30b2dd commit 7c66b35

File tree

1 file changed

+35
-14
lines changed

1 file changed

+35
-14
lines changed

plugin/src/api/index.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import type { CreateTaskParams, Task, TaskId } from "@/api/domain/task";
88
import type { RequestParams, WebFetcher, WebResponse } from "@/api/fetcher";
99
import debug from "@/log";
1010

11+
type PaginatedResponse<T> = {
12+
results: T[];
13+
nextCursor: string | null;
14+
};
15+
1116
export class TodoistApiClient {
1217
private token: string;
1318
private fetcher: WebFetcher;
@@ -18,15 +23,11 @@ export class TodoistApiClient {
1823
}
1924

2025
public async getTasks(filter?: string): Promise<Task[]> {
21-
let path = "/tasks";
22-
2326
if (filter !== undefined) {
24-
path += `?filter=${encodeURIComponent(filter)}`;
27+
return await this.doPaginated<Task>("/tasks/filter", { query: filter });
28+
} else {
29+
return await this.doPaginated<Task>("/tasks");
2530
}
26-
27-
const response = await this.do(path, "GET");
28-
29-
return camelize(JSON.parse(response.body)) as Task[];
3031
}
3132

3233
public async createTask(content: string, options?: CreateTaskParams): Promise<void> {
@@ -42,23 +43,43 @@ export class TodoistApiClient {
4243
}
4344

4445
public async getProjects(): Promise<Project[]> {
45-
const response = await this.do("/projects", "GET");
46-
return camelize(JSON.parse(response.body)) as Project[];
46+
return await this.doPaginated<Project>("/projects");
4747
}
4848

4949
public async getSections(): Promise<Section[]> {
50-
const response = await this.do("/sections", "GET");
51-
return camelize(JSON.parse(response.body)) as Section[];
50+
return await this.doPaginated<Section>("/sections");
5251
}
5352

5453
public async getLabels(): Promise<Label[]> {
55-
const response = await this.do("/labels", "GET");
56-
return camelize(JSON.parse(response.body)) as Label[];
54+
return await this.doPaginated<Label>("/labels");
55+
}
56+
57+
private async doPaginated<T>(path: string, params?: Record<string, string>): Promise<T[]> {
58+
const allResults: T[] = [];
59+
let cursor: string | null = null;
60+
61+
do {
62+
const queryParams = new URLSearchParams(params);
63+
if (cursor) {
64+
queryParams.set("cursor", cursor);
65+
}
66+
67+
const queryString = queryParams.toString();
68+
const fullPath = queryString ? `${path}?${queryString}` : path;
69+
70+
const response = await this.do(fullPath, "GET");
71+
const paginatedResponse = camelize(JSON.parse(response.body)) as PaginatedResponse<T>;
72+
73+
allResults.push(...paginatedResponse.results);
74+
cursor = paginatedResponse.nextCursor;
75+
} while (cursor);
76+
77+
return allResults;
5778
}
5879

5980
private async do(path: string, method: string, json?: object): Promise<WebResponse> {
6081
const params: RequestParams = {
61-
url: `https://api.todoist.com/rest/v2${path}`,
82+
url: `https://api.todoist.com/api/v1${path}`,
6283
method: method,
6384
headers: {
6485
Authorization: `Bearer ${this.token}`,

0 commit comments

Comments
 (0)