@doist/todoist-sdk 9.0.0 → 9.1.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/clients/activity-client.js +63 -0
- package/dist/cjs/clients/backup-client.js +65 -0
- package/dist/cjs/clients/base-client.js +30 -0
- package/dist/cjs/clients/comment-client.js +92 -0
- package/dist/cjs/clients/email-client.js +43 -0
- package/dist/cjs/clients/folder-client.js +80 -0
- package/dist/cjs/clients/goals-client.js +145 -0
- package/dist/cjs/clients/id-mapping-client.js +39 -0
- package/dist/cjs/clients/insights-client.js +88 -0
- package/dist/cjs/clients/label-client.js +127 -0
- package/dist/cjs/clients/productivity-client.js +27 -0
- package/dist/cjs/clients/project-client.js +232 -0
- package/dist/cjs/clients/reminder-client.js +198 -0
- package/dist/cjs/clients/section-client.js +118 -0
- package/dist/cjs/clients/task-client.js +255 -0
- package/dist/cjs/clients/template-client.js +89 -0
- package/dist/cjs/clients/upload-client.js +102 -0
- package/dist/cjs/clients/workspace-client.js +367 -0
- package/dist/cjs/consts/endpoints.js +6 -1
- package/dist/cjs/test-utils/test-defaults.js +25 -1
- package/dist/cjs/todoist-api.js +186 -1537
- package/dist/cjs/transport/sync-request.js +75 -0
- package/dist/cjs/types/goals/index.js +18 -0
- package/dist/cjs/types/goals/requests.js +2 -0
- package/dist/cjs/types/goals/types.js +56 -0
- package/dist/cjs/types/index.js +1 -0
- package/dist/cjs/types/sync/commands/goals.js +2 -0
- package/dist/cjs/types/sync/commands/index.js +1 -0
- package/dist/cjs/types/sync/resource-types.js +1 -0
- package/dist/cjs/types/sync/response.js +12 -10
- package/dist/cjs/utils/headers.js +16 -0
- package/dist/cjs/utils/request-helpers.js +21 -0
- package/dist/cjs/utils/validators.js +48 -46
- package/dist/esm/clients/activity-client.js +59 -0
- package/dist/esm/clients/backup-client.js +61 -0
- package/dist/esm/clients/base-client.js +26 -0
- package/dist/esm/clients/comment-client.js +88 -0
- package/dist/esm/clients/email-client.js +39 -0
- package/dist/esm/clients/folder-client.js +76 -0
- package/dist/esm/clients/goals-client.js +141 -0
- package/dist/esm/clients/id-mapping-client.js +35 -0
- package/dist/esm/clients/insights-client.js +84 -0
- package/dist/esm/clients/label-client.js +123 -0
- package/dist/esm/clients/productivity-client.js +23 -0
- package/dist/esm/clients/project-client.js +228 -0
- package/dist/esm/clients/reminder-client.js +194 -0
- package/dist/esm/clients/section-client.js +114 -0
- package/dist/esm/clients/task-client.js +251 -0
- package/dist/esm/clients/template-client.js +85 -0
- package/dist/esm/clients/upload-client.js +98 -0
- package/dist/esm/clients/workspace-client.js +363 -0
- package/dist/esm/consts/endpoints.js +5 -0
- package/dist/esm/test-utils/test-defaults.js +24 -0
- package/dist/esm/todoist-api.js +189 -1540
- package/dist/esm/transport/sync-request.js +72 -0
- package/dist/esm/types/goals/index.js +2 -0
- package/dist/esm/types/goals/requests.js +1 -0
- package/dist/esm/types/goals/types.js +53 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/types/sync/commands/goals.js +1 -0
- package/dist/esm/types/sync/commands/index.js +1 -0
- package/dist/esm/types/sync/resource-types.js +1 -0
- package/dist/esm/types/sync/response.js +2 -0
- package/dist/esm/utils/headers.js +13 -0
- package/dist/esm/utils/request-helpers.js +17 -0
- package/dist/esm/utils/validators.js +2 -0
- package/dist/types/clients/activity-client.d.ts +12 -0
- package/dist/types/clients/backup-client.d.ts +14 -0
- package/dist/types/clients/base-client.d.ts +21 -0
- package/dist/types/clients/comment-client.d.ts +15 -0
- package/dist/types/clients/email-client.d.ts +13 -0
- package/dist/types/clients/folder-client.d.ts +16 -0
- package/dist/types/clients/goals-client.d.ts +20 -0
- package/dist/types/clients/id-mapping-client.d.ts +13 -0
- package/dist/types/clients/insights-client.d.ts +18 -0
- package/dist/types/clients/label-client.d.ts +19 -0
- package/dist/types/clients/productivity-client.d.ts +12 -0
- package/dist/types/clients/project-client.d.ts +26 -0
- package/dist/types/clients/reminder-client.d.ts +25 -0
- package/dist/types/clients/section-client.d.ts +18 -0
- package/dist/types/clients/task-client.d.ts +25 -0
- package/dist/types/clients/template-client.d.ts +17 -0
- package/dist/types/clients/upload-client.d.ts +17 -0
- package/dist/types/clients/workspace-client.d.ts +43 -0
- package/dist/types/consts/endpoints.d.ts +5 -0
- package/dist/types/test-utils/test-defaults.d.ts +4 -1
- package/dist/types/todoist-api.d.ts +48 -17
- package/dist/types/transport/sync-request.d.ts +27 -0
- package/dist/types/types/goals/index.d.ts +2 -0
- package/dist/types/types/goals/requests.d.ts +33 -0
- package/dist/types/types/goals/types.d.ts +51 -0
- package/dist/types/types/http.d.ts +7 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/sync/commands/goals.d.ts +33 -0
- package/dist/types/types/sync/commands/index.d.ts +9 -0
- package/dist/types/types/sync/resource-types.d.ts +1 -1
- package/dist/types/types/sync/resources/view-options.d.ts +6 -6
- package/dist/types/types/sync/response.d.ts +29 -4
- package/dist/types/types/tasks/requests.d.ts +1 -0
- package/dist/types/utils/headers.d.ts +7 -0
- package/dist/types/utils/request-helpers.d.ts +13 -0
- package/dist/types/utils/validators.d.ts +55 -8
- package/package.json +1 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { ENDPOINT_REST_TASKS, ENDPOINT_REST_TASKS_COMPLETED, ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE, ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE, ENDPOINT_REST_TASKS_COMPLETED_SEARCH, ENDPOINT_REST_TASKS_FILTER, ENDPOINT_REST_TASK_CLOSE, ENDPOINT_REST_TASK_MOVE, ENDPOINT_REST_TASK_REOPEN, ENDPOINT_SYNC_QUICK_ADD, } from '../consts/endpoints.js';
|
|
15
|
+
import { isSuccess, request } from '../transport/http-client.js';
|
|
16
|
+
import { performSyncRequest } from '../transport/sync-request.js';
|
|
17
|
+
import { TodoistRequestError } from '../types/index.js';
|
|
18
|
+
import { generatePath, spreadIfDefined } from '../utils/request-helpers.js';
|
|
19
|
+
import { processTaskContent } from '../utils/uncompletable-helpers.js';
|
|
20
|
+
import { validateTask, validateTaskArray } from '../utils/validators.js';
|
|
21
|
+
import { BaseClient } from './base-client.js';
|
|
22
|
+
const MAX_COMMAND_COUNT = 100;
|
|
23
|
+
/**
|
|
24
|
+
* Internal sub-client handling all task-domain endpoints.
|
|
25
|
+
*
|
|
26
|
+
* Instantiated by `TodoistApi`; every public task method on `TodoistApi`
|
|
27
|
+
* delegates here. See `todoist-api.ts` for the user-facing JSDoc.
|
|
28
|
+
*/
|
|
29
|
+
export class TaskClient extends BaseClient {
|
|
30
|
+
async getTask(id) {
|
|
31
|
+
z.string().parse(id);
|
|
32
|
+
const response = await request({
|
|
33
|
+
httpMethod: 'GET',
|
|
34
|
+
baseUri: this.syncApiBase,
|
|
35
|
+
relativePath: generatePath(ENDPOINT_REST_TASKS, id),
|
|
36
|
+
apiToken: this.authToken,
|
|
37
|
+
customFetch: this.customFetch,
|
|
38
|
+
});
|
|
39
|
+
return validateTask(response.data);
|
|
40
|
+
}
|
|
41
|
+
async getTasks(args = {}) {
|
|
42
|
+
const { data: { results, nextCursor }, } = await request({
|
|
43
|
+
httpMethod: 'GET',
|
|
44
|
+
baseUri: this.syncApiBase,
|
|
45
|
+
relativePath: ENDPOINT_REST_TASKS,
|
|
46
|
+
apiToken: this.authToken,
|
|
47
|
+
customFetch: this.customFetch,
|
|
48
|
+
payload: args,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
results: validateTaskArray(results),
|
|
52
|
+
nextCursor,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async getTasksByFilter(args) {
|
|
56
|
+
const { data: { results, nextCursor }, } = await request({
|
|
57
|
+
httpMethod: 'GET',
|
|
58
|
+
baseUri: this.syncApiBase,
|
|
59
|
+
relativePath: ENDPOINT_REST_TASKS_FILTER,
|
|
60
|
+
apiToken: this.authToken,
|
|
61
|
+
customFetch: this.customFetch,
|
|
62
|
+
payload: args,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
results: validateTaskArray(results),
|
|
66
|
+
nextCursor,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async getCompletedTasksByCompletionDate(args) {
|
|
70
|
+
const { data: { items, nextCursor }, } = await request({
|
|
71
|
+
httpMethod: 'GET',
|
|
72
|
+
baseUri: this.syncApiBase,
|
|
73
|
+
relativePath: ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE,
|
|
74
|
+
apiToken: this.authToken,
|
|
75
|
+
customFetch: this.customFetch,
|
|
76
|
+
payload: args,
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
items: validateTaskArray(items),
|
|
80
|
+
nextCursor,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async getCompletedTasksByDueDate(args) {
|
|
84
|
+
const { data: { items, nextCursor }, } = await request({
|
|
85
|
+
httpMethod: 'GET',
|
|
86
|
+
baseUri: this.syncApiBase,
|
|
87
|
+
relativePath: ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE,
|
|
88
|
+
apiToken: this.authToken,
|
|
89
|
+
customFetch: this.customFetch,
|
|
90
|
+
payload: args,
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
items: validateTaskArray(items),
|
|
94
|
+
nextCursor,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async searchCompletedTasks(args) {
|
|
98
|
+
const { data: { items, nextCursor }, } = await request({
|
|
99
|
+
httpMethod: 'GET',
|
|
100
|
+
baseUri: this.syncApiBase,
|
|
101
|
+
relativePath: ENDPOINT_REST_TASKS_COMPLETED_SEARCH,
|
|
102
|
+
apiToken: this.authToken,
|
|
103
|
+
customFetch: this.customFetch,
|
|
104
|
+
payload: args,
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
items: validateTaskArray(items),
|
|
108
|
+
nextCursor,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async getAllCompletedTasks(args = {}) {
|
|
112
|
+
const { since, until } = args, rest = __rest(args, ["since", "until"]);
|
|
113
|
+
const { data } = await request({
|
|
114
|
+
httpMethod: 'GET',
|
|
115
|
+
baseUri: this.syncApiBase,
|
|
116
|
+
relativePath: ENDPOINT_REST_TASKS_COMPLETED,
|
|
117
|
+
apiToken: this.authToken,
|
|
118
|
+
customFetch: this.customFetch,
|
|
119
|
+
payload: Object.assign(Object.assign(Object.assign({}, rest), (since ? { since: since.toISOString() } : {})), (until ? { until: until.toISOString() } : {})),
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
projects: data.projects,
|
|
123
|
+
sections: data.sections,
|
|
124
|
+
items: validateTaskArray(data.items),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async addTask(args, requestId) {
|
|
128
|
+
// Process content based on isUncompletable flag
|
|
129
|
+
const processedArgs = Object.assign(Object.assign({}, args), { content: processTaskContent(args.content, args.isUncompletable) });
|
|
130
|
+
const response = await request({
|
|
131
|
+
httpMethod: 'POST',
|
|
132
|
+
baseUri: this.syncApiBase,
|
|
133
|
+
relativePath: ENDPOINT_REST_TASKS,
|
|
134
|
+
apiToken: this.authToken,
|
|
135
|
+
customFetch: this.customFetch,
|
|
136
|
+
payload: processedArgs,
|
|
137
|
+
requestId: requestId,
|
|
138
|
+
});
|
|
139
|
+
return validateTask(response.data);
|
|
140
|
+
}
|
|
141
|
+
async quickAddTask(args) {
|
|
142
|
+
// Process text based on isUncompletable flag
|
|
143
|
+
const processedArgs = Object.assign(Object.assign({}, args), { text: processTaskContent(args.text, args.isUncompletable) });
|
|
144
|
+
const response = await request({
|
|
145
|
+
httpMethod: 'POST',
|
|
146
|
+
baseUri: this.syncApiBase,
|
|
147
|
+
relativePath: ENDPOINT_SYNC_QUICK_ADD,
|
|
148
|
+
apiToken: this.authToken,
|
|
149
|
+
customFetch: this.customFetch,
|
|
150
|
+
payload: processedArgs,
|
|
151
|
+
});
|
|
152
|
+
return validateTask(response.data);
|
|
153
|
+
}
|
|
154
|
+
async updateTask(id, args, requestId) {
|
|
155
|
+
z.string().parse(id);
|
|
156
|
+
// Translate SDK alias for due-date clearing to Todoist's accepted payload value.
|
|
157
|
+
const normalizedArgs = args.dueString === null ? Object.assign(Object.assign({}, args), { dueString: 'no date' }) : args;
|
|
158
|
+
// Process content if both content and isUncompletable are provided
|
|
159
|
+
const processedArgs = normalizedArgs.content && normalizedArgs.isUncompletable !== undefined
|
|
160
|
+
? Object.assign(Object.assign({}, normalizedArgs), { content: processTaskContent(normalizedArgs.content, normalizedArgs.isUncompletable) }) : normalizedArgs;
|
|
161
|
+
// Remap `order` → `childOrder` so snakeCaseKeys() produces `child_order`
|
|
162
|
+
const { order } = processedArgs, argsWithoutOrder = __rest(processedArgs, ["order"]);
|
|
163
|
+
const remappedArgs = order !== undefined ? Object.assign(Object.assign({}, argsWithoutOrder), { childOrder: order }) : argsWithoutOrder;
|
|
164
|
+
const response = await request({
|
|
165
|
+
httpMethod: 'POST',
|
|
166
|
+
baseUri: this.syncApiBase,
|
|
167
|
+
relativePath: generatePath(ENDPOINT_REST_TASKS, id),
|
|
168
|
+
apiToken: this.authToken,
|
|
169
|
+
customFetch: this.customFetch,
|
|
170
|
+
payload: remappedArgs,
|
|
171
|
+
requestId: requestId,
|
|
172
|
+
});
|
|
173
|
+
return validateTask(response.data);
|
|
174
|
+
}
|
|
175
|
+
async moveTasks(ids, args, requestId) {
|
|
176
|
+
var _a;
|
|
177
|
+
if (ids.length > MAX_COMMAND_COUNT) {
|
|
178
|
+
throw new TodoistRequestError(`Maximum number of items is ${MAX_COMMAND_COUNT}`, 400);
|
|
179
|
+
}
|
|
180
|
+
const commands = ids.map((id) => ({
|
|
181
|
+
type: 'item_move',
|
|
182
|
+
uuid: uuidv4(),
|
|
183
|
+
args: Object.assign(Object.assign(Object.assign({ id }, spreadIfDefined(args.projectId, (v) => ({ projectId: v }))), spreadIfDefined(args.sectionId, (v) => ({ sectionId: v }))), spreadIfDefined(args.parentId, (v) => ({ parentId: v }))),
|
|
184
|
+
}));
|
|
185
|
+
const syncRequest = {
|
|
186
|
+
commands,
|
|
187
|
+
resourceTypes: ['items'],
|
|
188
|
+
};
|
|
189
|
+
const syncResponse = await performSyncRequest(this.syncContext, syncRequest, {
|
|
190
|
+
requestId,
|
|
191
|
+
hasSyncCommands: true,
|
|
192
|
+
});
|
|
193
|
+
if (!((_a = syncResponse.items) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
194
|
+
throw new TodoistRequestError('Tasks not found', 404);
|
|
195
|
+
}
|
|
196
|
+
const syncTasks = syncResponse.items.filter((task) => ids.includes(task.id));
|
|
197
|
+
if (!syncTasks.length) {
|
|
198
|
+
throw new TodoistRequestError('Tasks not found', 404);
|
|
199
|
+
}
|
|
200
|
+
return validateTaskArray(syncTasks);
|
|
201
|
+
}
|
|
202
|
+
async moveTask(id, args, requestId) {
|
|
203
|
+
z.string().parse(id);
|
|
204
|
+
const response = await request({
|
|
205
|
+
httpMethod: 'POST',
|
|
206
|
+
baseUri: this.syncApiBase,
|
|
207
|
+
relativePath: generatePath(ENDPOINT_REST_TASKS, id, ENDPOINT_REST_TASK_MOVE),
|
|
208
|
+
apiToken: this.authToken,
|
|
209
|
+
customFetch: this.customFetch,
|
|
210
|
+
payload: Object.assign(Object.assign(Object.assign({}, spreadIfDefined(args.projectId, (v) => ({ project_id: v }))), spreadIfDefined(args.sectionId, (v) => ({ section_id: v }))), spreadIfDefined(args.parentId, (v) => ({ parent_id: v }))),
|
|
211
|
+
requestId: requestId,
|
|
212
|
+
});
|
|
213
|
+
return validateTask(response.data);
|
|
214
|
+
}
|
|
215
|
+
async closeTask(id, requestId) {
|
|
216
|
+
z.string().parse(id);
|
|
217
|
+
const response = await request({
|
|
218
|
+
httpMethod: 'POST',
|
|
219
|
+
baseUri: this.syncApiBase,
|
|
220
|
+
relativePath: generatePath(ENDPOINT_REST_TASKS, id, ENDPOINT_REST_TASK_CLOSE),
|
|
221
|
+
apiToken: this.authToken,
|
|
222
|
+
customFetch: this.customFetch,
|
|
223
|
+
requestId: requestId,
|
|
224
|
+
});
|
|
225
|
+
return isSuccess(response);
|
|
226
|
+
}
|
|
227
|
+
async reopenTask(id, requestId) {
|
|
228
|
+
z.string().parse(id);
|
|
229
|
+
const response = await request({
|
|
230
|
+
httpMethod: 'POST',
|
|
231
|
+
baseUri: this.syncApiBase,
|
|
232
|
+
relativePath: generatePath(ENDPOINT_REST_TASKS, id, ENDPOINT_REST_TASK_REOPEN),
|
|
233
|
+
apiToken: this.authToken,
|
|
234
|
+
customFetch: this.customFetch,
|
|
235
|
+
requestId: requestId,
|
|
236
|
+
});
|
|
237
|
+
return isSuccess(response);
|
|
238
|
+
}
|
|
239
|
+
async deleteTask(id, requestId) {
|
|
240
|
+
z.string().parse(id);
|
|
241
|
+
const response = await request({
|
|
242
|
+
httpMethod: 'DELETE',
|
|
243
|
+
baseUri: this.syncApiBase,
|
|
244
|
+
relativePath: generatePath(ENDPOINT_REST_TASKS, id),
|
|
245
|
+
apiToken: this.authToken,
|
|
246
|
+
customFetch: this.customFetch,
|
|
247
|
+
requestId: requestId,
|
|
248
|
+
});
|
|
249
|
+
return isSuccess(response);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ENDPOINT_REST_TEMPLATES_CREATE_FROM_FILE, ENDPOINT_REST_TEMPLATES_FILE, ENDPOINT_REST_TEMPLATES_IMPORT_FROM_FILE, ENDPOINT_REST_TEMPLATES_IMPORT_FROM_ID, ENDPOINT_REST_TEMPLATES_URL, } from '../consts/endpoints.js';
|
|
2
|
+
import { request } from '../transport/http-client.js';
|
|
3
|
+
import { camelCaseKeys } from '../utils/case-conversion.js';
|
|
4
|
+
import { uploadMultipartFile } from '../utils/multipart-upload.js';
|
|
5
|
+
import { validateCommentArray, validateProjectArray, validateSectionArray, validateTaskArray, } from '../utils/validators.js';
|
|
6
|
+
import { BaseClient } from './base-client.js';
|
|
7
|
+
/**
|
|
8
|
+
* Internal sub-client handling all template-domain endpoints.
|
|
9
|
+
*
|
|
10
|
+
* Instantiated by `TodoistApi`; every public template method on
|
|
11
|
+
* `TodoistApi` delegates here. See `todoist-api.ts` for user-facing
|
|
12
|
+
* JSDoc.
|
|
13
|
+
*/
|
|
14
|
+
export class TemplateClient extends BaseClient {
|
|
15
|
+
async exportTemplateAsFile(args) {
|
|
16
|
+
const response = await request({
|
|
17
|
+
httpMethod: 'GET',
|
|
18
|
+
baseUri: this.syncApiBase,
|
|
19
|
+
relativePath: ENDPOINT_REST_TEMPLATES_FILE,
|
|
20
|
+
apiToken: this.authToken,
|
|
21
|
+
customFetch: this.customFetch,
|
|
22
|
+
payload: args,
|
|
23
|
+
});
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
async exportTemplateAsUrl(args) {
|
|
27
|
+
const { data } = await request({
|
|
28
|
+
httpMethod: 'GET',
|
|
29
|
+
baseUri: this.syncApiBase,
|
|
30
|
+
relativePath: ENDPOINT_REST_TEMPLATES_URL,
|
|
31
|
+
apiToken: this.authToken,
|
|
32
|
+
customFetch: this.customFetch,
|
|
33
|
+
payload: args,
|
|
34
|
+
});
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
async createProjectFromTemplate(args, requestId) {
|
|
38
|
+
const { file, fileName, name, workspaceId } = args;
|
|
39
|
+
const additionalFields = { name };
|
|
40
|
+
if (workspaceId !== undefined && workspaceId !== null) {
|
|
41
|
+
additionalFields.workspace_id = workspaceId;
|
|
42
|
+
}
|
|
43
|
+
const data = await uploadMultipartFile({
|
|
44
|
+
baseUrl: this.syncApiBase,
|
|
45
|
+
authToken: this.authToken,
|
|
46
|
+
endpoint: ENDPOINT_REST_TEMPLATES_CREATE_FROM_FILE,
|
|
47
|
+
file,
|
|
48
|
+
fileName,
|
|
49
|
+
additionalFields,
|
|
50
|
+
customFetch: this.customFetch,
|
|
51
|
+
requestId,
|
|
52
|
+
});
|
|
53
|
+
return this.validateTemplateResponse(camelCaseKeys(data));
|
|
54
|
+
}
|
|
55
|
+
async importTemplateIntoProject(args, requestId) {
|
|
56
|
+
const { file, fileName, projectId } = args;
|
|
57
|
+
const data = await uploadMultipartFile({
|
|
58
|
+
baseUrl: this.syncApiBase,
|
|
59
|
+
authToken: this.authToken,
|
|
60
|
+
endpoint: ENDPOINT_REST_TEMPLATES_IMPORT_FROM_FILE,
|
|
61
|
+
file,
|
|
62
|
+
fileName,
|
|
63
|
+
additionalFields: { project_id: projectId },
|
|
64
|
+
customFetch: this.customFetch,
|
|
65
|
+
requestId,
|
|
66
|
+
});
|
|
67
|
+
return this.validateTemplateResponse(camelCaseKeys(data));
|
|
68
|
+
}
|
|
69
|
+
async importTemplateFromId(args, requestId) {
|
|
70
|
+
const { data } = await request({
|
|
71
|
+
httpMethod: 'POST',
|
|
72
|
+
baseUri: this.syncApiBase,
|
|
73
|
+
relativePath: ENDPOINT_REST_TEMPLATES_IMPORT_FROM_ID,
|
|
74
|
+
apiToken: this.authToken,
|
|
75
|
+
customFetch: this.customFetch,
|
|
76
|
+
payload: args,
|
|
77
|
+
requestId: requestId,
|
|
78
|
+
});
|
|
79
|
+
return this.validateTemplateResponse(data);
|
|
80
|
+
}
|
|
81
|
+
validateTemplateResponse(data) {
|
|
82
|
+
var _a, _b, _c, _d;
|
|
83
|
+
return Object.assign(Object.assign({}, data), { projects: validateProjectArray((_a = data.projects) !== null && _a !== void 0 ? _a : []), sections: validateSectionArray((_b = data.sections) !== null && _b !== void 0 ? _b : []), tasks: validateTaskArray((_c = data.tasks) !== null && _c !== void 0 ? _c : []), comments: validateCommentArray((_d = data.comments) !== null && _d !== void 0 ? _d : []) });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ENDPOINT_REST_UPLOADS } from '../consts/endpoints.js';
|
|
2
|
+
import { isSuccess, request } from '../transport/http-client.js';
|
|
3
|
+
import { headersToRecord } from '../utils/headers.js';
|
|
4
|
+
import { uploadMultipartFile } from '../utils/multipart-upload.js';
|
|
5
|
+
import { validateAttachment } from '../utils/validators.js';
|
|
6
|
+
import { BaseClient } from './base-client.js';
|
|
7
|
+
/**
|
|
8
|
+
* Internal sub-client handling upload + attachment endpoints
|
|
9
|
+
* (file upload, upload delete, attachment view).
|
|
10
|
+
*
|
|
11
|
+
* Instantiated by `TodoistApi`; every public upload method on
|
|
12
|
+
* `TodoistApi` delegates here. See `todoist-api.ts` for user-facing
|
|
13
|
+
* JSDoc.
|
|
14
|
+
*/
|
|
15
|
+
export class UploadClient extends BaseClient {
|
|
16
|
+
async uploadFile(args, requestId) {
|
|
17
|
+
const additionalFields = {};
|
|
18
|
+
if (args.projectId) {
|
|
19
|
+
additionalFields.project_id = args.projectId;
|
|
20
|
+
}
|
|
21
|
+
const data = await uploadMultipartFile({
|
|
22
|
+
baseUrl: this.syncApiBase,
|
|
23
|
+
authToken: this.authToken,
|
|
24
|
+
endpoint: ENDPOINT_REST_UPLOADS,
|
|
25
|
+
file: args.file,
|
|
26
|
+
fileName: args.fileName,
|
|
27
|
+
additionalFields: additionalFields,
|
|
28
|
+
requestId: requestId,
|
|
29
|
+
customFetch: this.customFetch,
|
|
30
|
+
});
|
|
31
|
+
return validateAttachment(data);
|
|
32
|
+
}
|
|
33
|
+
async deleteUpload(args, requestId) {
|
|
34
|
+
const response = await request({
|
|
35
|
+
httpMethod: 'DELETE',
|
|
36
|
+
baseUri: this.syncApiBase,
|
|
37
|
+
relativePath: ENDPOINT_REST_UPLOADS,
|
|
38
|
+
apiToken: this.authToken,
|
|
39
|
+
customFetch: this.customFetch,
|
|
40
|
+
payload: args,
|
|
41
|
+
requestId: requestId,
|
|
42
|
+
});
|
|
43
|
+
return isSuccess(response);
|
|
44
|
+
}
|
|
45
|
+
async viewAttachment(commentOrUrl) {
|
|
46
|
+
var _a;
|
|
47
|
+
let fileUrl;
|
|
48
|
+
if (typeof commentOrUrl === 'string') {
|
|
49
|
+
fileUrl = commentOrUrl;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
if (!((_a = commentOrUrl.fileAttachment) === null || _a === void 0 ? void 0 : _a.fileUrl)) {
|
|
53
|
+
throw new Error('Comment does not have a file attachment');
|
|
54
|
+
}
|
|
55
|
+
fileUrl = commentOrUrl.fileAttachment.fileUrl;
|
|
56
|
+
}
|
|
57
|
+
// Validate the URL belongs to Todoist to prevent leaking the auth token
|
|
58
|
+
const urlHostname = new URL(fileUrl).hostname;
|
|
59
|
+
if (!urlHostname.endsWith('.todoist.com')) {
|
|
60
|
+
throw new Error('Attachment URLs must be on a todoist.com domain');
|
|
61
|
+
}
|
|
62
|
+
const fetchOptions = {
|
|
63
|
+
method: 'GET',
|
|
64
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
65
|
+
};
|
|
66
|
+
if (this.customFetch) {
|
|
67
|
+
const response = await this.customFetch(fileUrl, fetchOptions);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Failed to fetch attachment: ${response.status} ${response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
// Convert text to ArrayBuffer for custom fetch implementations that lack arrayBuffer()
|
|
72
|
+
const text = await response.text();
|
|
73
|
+
const buffer = new TextEncoder().encode(text).buffer;
|
|
74
|
+
return {
|
|
75
|
+
ok: response.ok,
|
|
76
|
+
status: response.status,
|
|
77
|
+
statusText: response.statusText,
|
|
78
|
+
headers: response.headers,
|
|
79
|
+
text: () => Promise.resolve(text),
|
|
80
|
+
json: () => response.json(),
|
|
81
|
+
arrayBuffer: () => Promise.resolve(buffer),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const response = await fetch(fileUrl, fetchOptions);
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Failed to fetch attachment: ${response.status} ${response.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
ok: response.ok,
|
|
90
|
+
status: response.status,
|
|
91
|
+
statusText: response.statusText,
|
|
92
|
+
headers: headersToRecord(response.headers),
|
|
93
|
+
text: () => response.text(),
|
|
94
|
+
json: () => response.json(),
|
|
95
|
+
arrayBuffer: () => response.arrayBuffer(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|