@pipedream/todoist 0.0.1

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.
Files changed (57) hide show
  1. package/LICENSE +7 -0
  2. package/actions/create-filter/create-filter.mjs +66 -0
  3. package/actions/create-label/create-label.mjs +56 -0
  4. package/actions/create-project/create-project.mjs +57 -0
  5. package/actions/create-project-comment/create-project-comment.mjs +42 -0
  6. package/actions/create-section/create-section.mjs +49 -0
  7. package/actions/create-task/create-task.mjs +150 -0
  8. package/actions/create-task-comment/create-task-comment.mjs +51 -0
  9. package/actions/delete-comment/delete-comment.mjs +56 -0
  10. package/actions/delete-filter/delete-filter.mjs +30 -0
  11. package/actions/delete-label/delete-label.mjs +34 -0
  12. package/actions/delete-project/delete-project.mjs +34 -0
  13. package/actions/delete-section/delete-section.mjs +43 -0
  14. package/actions/delete-task/delete-task.mjs +44 -0
  15. package/actions/export-tasks/export-tasks.mjs +40 -0
  16. package/actions/find-project/find-project.mjs +51 -0
  17. package/actions/find-task/find-task.mjs +62 -0
  18. package/actions/find-user/find-user.mjs +28 -0
  19. package/actions/get-label/get-label.mjs +26 -0
  20. package/actions/get-project/get-project.mjs +26 -0
  21. package/actions/get-project-comment/get-project-comment.mjs +38 -0
  22. package/actions/get-section/get-section.mjs +38 -0
  23. package/actions/get-task/get-task.mjs +37 -0
  24. package/actions/get-task-comment/get-task-comment.mjs +47 -0
  25. package/actions/import-tasks/import-tasks.mjs +104 -0
  26. package/actions/invite-user-to-project/invite-user-to-project.mjs +40 -0
  27. package/actions/list-filters/list-filters.mjs +23 -0
  28. package/actions/list-labels/list-labels.mjs +21 -0
  29. package/actions/list-project-comments/list-project-comments.mjs +32 -0
  30. package/actions/list-projects/list-projects.mjs +22 -0
  31. package/actions/list-sections/list-sections.mjs +31 -0
  32. package/actions/list-task-comments/list-task-comments.mjs +40 -0
  33. package/actions/list-uncompleted-tasks/list-uncompleted-tasks.mjs +53 -0
  34. package/actions/mark-task-completed/mark-task-completed.mjs +43 -0
  35. package/actions/move-task-to-section/move-task-to-section.mjs +58 -0
  36. package/actions/uncomplete-task/uncomplete-task.mjs +42 -0
  37. package/actions/update-comment/update-comment.mjs +67 -0
  38. package/actions/update-filter/update-filter.mjs +76 -0
  39. package/actions/update-label/update-label.mjs +70 -0
  40. package/actions/update-project/update-project.mjs +62 -0
  41. package/actions/update-section/update-section.mjs +54 -0
  42. package/actions/update-task/update-task.mjs +126 -0
  43. package/colors.mjs +82 -0
  44. package/package.json +23 -0
  45. package/resource-types.mjs +15 -0
  46. package/sources/common-project.mjs +18 -0
  47. package/sources/common-task.mjs +38 -0
  48. package/sources/common.mjs +56 -0
  49. package/sources/completed-task/completed-task.mjs +17 -0
  50. package/sources/incomplete-task/incomplete-task.mjs +17 -0
  51. package/sources/new-or-modified-project/new-or-modified-project.mjs +10 -0
  52. package/sources/new-or-modified-task/new-or-modified-task.mjs +10 -0
  53. package/sources/new-project/new-project.mjs +11 -0
  54. package/sources/new-section/new-section.mjs +24 -0
  55. package/sources/new-task/new-task.mjs +11 -0
  56. package/sources/sync-resources/sync-resources.mjs +64 -0
  57. package/todoist.app.mjs +1105 -0
@@ -0,0 +1,1105 @@
1
+ import { axios } from "@pipedream/platform";
2
+ import querystring from "querystring";
3
+ import resourceTypes from "./resource-types.mjs";
4
+ import colors from "./colors.mjs";
5
+ import { v4 as uuid } from "uuid";
6
+
7
+ export default {
8
+ type: "app",
9
+ app: "todoist",
10
+ propDefinitions: {
11
+ includeResourceTypes: {
12
+ type: "string[]",
13
+ label: "Resource Types",
14
+ description: "Select one or more resources to include",
15
+ async options() {
16
+ resourceTypes.unshift("all");
17
+ return resourceTypes;
18
+ },
19
+ },
20
+ selectProjects: {
21
+ type: "integer[]",
22
+ label: "Select Projects",
23
+ description:
24
+ "Filter for events that match one or more projects. Leave this blank to emit results for any project.",
25
+ optional: true,
26
+ async options() {
27
+ return (await this.getProjects({})).map((project) => ({
28
+ label: project.name,
29
+ value: project.id,
30
+ }));
31
+ },
32
+ },
33
+ project: {
34
+ type: "integer",
35
+ label: "Project",
36
+ description: "Select a project to filter results by",
37
+ optional: true,
38
+ async options() {
39
+ return (await this.getProjects({})).map((project) => ({
40
+ label: project.name,
41
+ value: project.id,
42
+ }));
43
+ },
44
+ },
45
+ section: {
46
+ type: "integer",
47
+ label: "Section",
48
+ description: "Select a section to filter results by",
49
+ optional: true,
50
+ async options({ project }) {
51
+ return (await this.getSections({
52
+ params: {
53
+ project_id: project,
54
+ },
55
+ })).map((section) => ({
56
+ label: section.name,
57
+ value: section.id,
58
+ }));
59
+ },
60
+ },
61
+ label: {
62
+ type: "integer",
63
+ label: "Label",
64
+ description: "Select a label to filter results by",
65
+ optional: true,
66
+ async options() {
67
+ return (await this.getLabels({})).map((label) => ({
68
+ label: label.name,
69
+ value: label.id,
70
+ }));
71
+ },
72
+ },
73
+ task: {
74
+ type: "integer",
75
+ label: "Task",
76
+ description: "Select a task to filter results by",
77
+ async options({
78
+ project, section,
79
+ }) {
80
+ return (await this.getActiveTasks({
81
+ params: {
82
+ project_id: project,
83
+ section_id: section,
84
+ },
85
+ })).map((task) => ({
86
+ label: task.content,
87
+ value: task.id,
88
+ }));
89
+ },
90
+ },
91
+ completedTask: {
92
+ type: "integer",
93
+ label: "Completed Task",
94
+ description: "Select the task to reopen",
95
+ async options({
96
+ project, prevContext,
97
+ }) {
98
+ const { offset = 0 } = prevContext;
99
+ const limit = 30;
100
+ const params = {
101
+ offset,
102
+ limit,
103
+ };
104
+ if (project) {
105
+ params.project_id = project;
106
+ }
107
+ const tasks = (await this.getCompletedTasks({
108
+ params,
109
+ })).map((task) => ({
110
+ label: task.content,
111
+ value: task.task_id,
112
+ }));
113
+ return {
114
+ options: tasks,
115
+ context: {
116
+ offset: offset + limit,
117
+ },
118
+ };
119
+ },
120
+ },
121
+ assignee: {
122
+ type: "integer",
123
+ label: "Assignee",
124
+ description: "The responsible user (if set, and only for shared tasks)",
125
+ async options({ project }) {
126
+ return (await this.getProjectCollaborators(project)).map((assignee) => ({
127
+ label: assignee.name,
128
+ value: assignee.id,
129
+ }));
130
+ },
131
+ optional: true,
132
+ },
133
+ filter: {
134
+ type: "integer",
135
+ label: "Filter",
136
+ description: "Select the filter to update",
137
+ async options() {
138
+ return (await this.getFilters({})).map((filter) => ({
139
+ label: filter.name,
140
+ value: filter.id,
141
+ }));
142
+ },
143
+ },
144
+ name: {
145
+ type: "string",
146
+ label: "Name",
147
+ description: "Enter the new name",
148
+ },
149
+ commentId: {
150
+ type: "integer",
151
+ label: "Comment ID",
152
+ description: "Select a comment",
153
+ async options({
154
+ project, task,
155
+ }) {
156
+ if (!project && !task) {
157
+ return [];
158
+ }
159
+ return (await this.getComments({
160
+ params: {
161
+ project_id: project,
162
+ task_id: task,
163
+ },
164
+ })).map((comment) => ({
165
+ label: comment.content,
166
+ value: comment.id,
167
+ }));
168
+ },
169
+ },
170
+ order: {
171
+ type: "integer",
172
+ label: "Order",
173
+ description: "Order in a list",
174
+ optional: true,
175
+ },
176
+ color: {
177
+ type: "integer",
178
+ label: "Color",
179
+ description: "A numeric ID representing a color. Refer to the id column in the [Colors](https://developer.todoist.com/guides/#colors) guide for more info.",
180
+ options: colors,
181
+ optional: true,
182
+ },
183
+ favorite: {
184
+ type: "boolean",
185
+ label: "Favorite",
186
+ description: "Whether this is a favorite",
187
+ optional: true,
188
+ },
189
+ query: {
190
+ type: "string",
191
+ label: "Query",
192
+ description: "The query to search for. [Examples of searches](https://todoist.com/help/articles/introduction-to-filters) can be found in the Todoist help page.",
193
+ },
194
+ content: {
195
+ type: "string",
196
+ label: "Content",
197
+ description: "Comment content",
198
+ optional: true,
199
+ },
200
+ description: {
201
+ type: "string",
202
+ label: "Description",
203
+ description: "Task description",
204
+ optional: true,
205
+ },
206
+ priority: {
207
+ type: "integer",
208
+ label: "Priority",
209
+ description: "Task priority from 1 (normal) to 4 (urgent)",
210
+ optional: true,
211
+ },
212
+ dueString: {
213
+ type: "string",
214
+ label: "Due String",
215
+ description: "[Human defined](https://todoist.com/help/articles/205325931) task due date (ex.: \"next Monday\", \"Tomorrow\"). Value is set using local (not UTC) time.",
216
+ optional: true,
217
+ },
218
+ dueDate: {
219
+ type: "string",
220
+ label: "Due Date",
221
+ description: "Specific date in `YYYY-MM-DD` format relative to user’s timezone",
222
+ optional: true,
223
+ },
224
+ dueDatetime: {
225
+ type: "string",
226
+ label: "Due Datetime",
227
+ description: "Specific date and time in [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) format in UTC.",
228
+ optional: true,
229
+ },
230
+ dueLang: {
231
+ type: "string",
232
+ label: "Due Language",
233
+ description: "2-letter code specifying language in case `due_string` is not written in English",
234
+ optional: true,
235
+ },
236
+ email: {
237
+ type: "string",
238
+ label: "Email",
239
+ description: "Email Address",
240
+ },
241
+ createIfNotFound: {
242
+ type: "boolean",
243
+ label: "Create If Not Found",
244
+ description: "Create this item if it is not found",
245
+ default: false,
246
+ },
247
+ path: {
248
+ type: "string",
249
+ label: "File Path",
250
+ description: "Path to .csv file containing task data. Enter a static value (e.g., `/tmp/myFile.csv`) or reference prior step exports via the `steps` object (e.g., `{{steps.export_tasks.$return_value}}`).",
251
+ },
252
+ },
253
+ methods: {
254
+ /**
255
+ * Make a request to Todoist's sync API.
256
+ * @params {Object} opts - An object representing the configuration options for this method
257
+ * @params {String} [opts.path=/sync/v8/sync] - The path for the sync request
258
+ * @params {String} opts.payload - The data to send in the API request at the POST body.
259
+ * This data will converted to `application/x-www-form-urlencoded`
260
+ * @returns {Object} When the request succeeds, an HTTP 200 response with
261
+ * a JSON object containing the requested resources and also a new `sync_token`.
262
+ */
263
+ async _makeSyncRequest(opts) {
264
+ const {
265
+ $,
266
+ path = "/sync/v8/sync",
267
+ } = opts;
268
+ delete opts.path;
269
+ delete opts.$;
270
+ opts.url = `https://api.todoist.com${path[0] === "/"
271
+ ? ""
272
+ : "/"}${path}`;
273
+ opts.payload.token = this.$auth.oauth_access_token;
274
+ opts.data = querystring.stringify(opts.payload);
275
+ delete opts.payload;
276
+ opts.headers = {
277
+ "Content-Type": "application/x-www-form-urlencoded",
278
+ "Authorization": `Bearer ${this.$auth.oauth_access_token}`,
279
+ };
280
+ return axios($ ?? this, opts);
281
+ },
282
+ /**
283
+ * Make a request to Todoist's REST API.
284
+ * @params {Object} opts - An object representing the Axios configuration options
285
+ * for this method
286
+ * @params {String} opts.path - The path for the REST API request
287
+ * @returns {*} The response may vary depending the specific API request.
288
+ */
289
+ async _makeRestRequest(opts) {
290
+ const {
291
+ $,
292
+ path,
293
+ } = opts;
294
+ delete opts.path;
295
+ delete opts.$;
296
+ opts.url = `https://api.todoist.com/rest/v1${path[0] === "/"
297
+ ? ""
298
+ : "/"}${path}`;
299
+ opts.headers = {
300
+ Authorization: `Bearer ${this.$auth.oauth_access_token}`,
301
+ };
302
+ return axios($ ?? this, opts);
303
+ },
304
+ /**
305
+ * Get syncToken from a db
306
+ * @params {Object} db - a database instance
307
+ * @returns {*} "syncToken" in the db specified
308
+ */
309
+ _getSyncToken(db) {
310
+ return db.get("syncToken");
311
+ },
312
+ /**
313
+ * Set the syncToken in a db
314
+ * @params {Object} db - a database instance
315
+ * @syncToken {*} - The data to be stored as "syncToken" in the db specified
316
+ */
317
+ _setSyncToken(db, syncToken) {
318
+ db.set("syncToken", syncToken);
319
+ },
320
+ /**
321
+ * Check whether an array of project IDs contains the given proejct ID. This method is
322
+ * used in multiple sources to validate if an event matches the selection in the project filter.
323
+ * @params {Integer} project_id - The ID for a Todoist project
324
+ * @params {Array} selectedProjectIds - An array of Todoist project IDs
325
+ * @returns {Boolean} `true` if the `project_id` matches a value in the arrar or
326
+ * if the array is empty. Otherwise `false`.
327
+ */
328
+ isProjectInList(projectId, selectedProjectIds) {
329
+ return (
330
+ selectedProjectIds.length === 0 ||
331
+ selectedProjectIds.includes(projectId)
332
+ );
333
+ },
334
+ /**
335
+ * Public method to make a sync request.
336
+ * @params {Object} opts - The configuration for an axios request with a `path` key.
337
+ * @returns {Object} When the request succeeds, an HTTP 200 response
338
+ * with a JSON object containing the requested resources and also a new `sync_token`.
339
+ */
340
+ async sync({
341
+ $, opts,
342
+ }) {
343
+ return this._makeSyncRequest({
344
+ $,
345
+ path: "/sync/v8/sync",
346
+ method: "POST",
347
+ payload: opts,
348
+ });
349
+ },
350
+ /**
351
+ * Breaks sync commands into batches of no more than 100
352
+ * @params {Array} commands - An array of sync commands
353
+ * @returns {Array} An array of batches (arrays) of commands
354
+ */
355
+ _makeBatches(commands) {
356
+ const BATCH_SIZE = 100;
357
+ let batches = [];
358
+ for (let i = 0; i < commands.length; i += BATCH_SIZE) {
359
+ batches.push(commands.slice(i, i + BATCH_SIZE));
360
+ }
361
+ return batches;
362
+ },
363
+ /**
364
+ * Processes sync commands in batches
365
+ * @params {Object} opts - An object representing configuration options for this method
366
+ * @returns {Array} A collection of responses, one for each batch of commands
367
+ */
368
+ async batchSync({
369
+ $, opts,
370
+ }) {
371
+ const { commands } = opts;
372
+ const batches = this._makeBatches(commands);
373
+ return Promise.all(batches.map((batch) => {
374
+ return this.sync({
375
+ $,
376
+ opts: {
377
+ ...opts,
378
+ commands: JSON.stringify(batch),
379
+ },
380
+ });
381
+ }));
382
+ },
383
+ /**
384
+ * Get project by ID or get all projects if no ID specified
385
+ * @params {Object} opts - An object representing configuration options for this method
386
+ * @params {Integer} [opts.id = ""] - A project ID
387
+ * @returns {Obect|Array} A project object related to the given ID or all user projects
388
+ * if no ID specified
389
+ */
390
+ async getProjects(opts) {
391
+ const {
392
+ $,
393
+ id = "",
394
+ } = opts;
395
+ return this._makeRestRequest({
396
+ $,
397
+ path: `/projects/${id}`,
398
+ method: "GET",
399
+ });
400
+ },
401
+ /**
402
+ * Create a new project
403
+ * @params {Object} opts - An object representing configuration options for this method
404
+ * @params {Object} [opts.data = {}] - object containing info about the new project
405
+ * being created
406
+ * @returns {Object} The created project as a JSON object
407
+ */
408
+ async createProject(opts) {
409
+ const {
410
+ $,
411
+ data = {},
412
+ } = opts;
413
+ return this._makeRestRequest({
414
+ $,
415
+ path: "/projects",
416
+ method: "POST",
417
+ data,
418
+ });
419
+ },
420
+ /**
421
+ * Update a project
422
+ * @params {Object} opts - An object representing configuration options for this method
423
+ * @params {Object} [opts.data = {}] - object containing the projectId
424
+ * @returns {Object} A successful response has 204 No Content status and an empty body
425
+ */
426
+ async updateProject(opts) {
427
+ const {
428
+ $,
429
+ data = {},
430
+ } = opts;
431
+ const { projectId } = data;
432
+ delete data.projectId;
433
+ return this._makeRestRequest({
434
+ $,
435
+ path: `/projects/${projectId}`,
436
+ method: "POST",
437
+ data,
438
+ });
439
+ },
440
+ /**
441
+ * Delete a project
442
+ * @params {Object} opts - An object representing configuration options for this method
443
+ * @params {Object} [opts.data = {}] - object containing the projectId
444
+ * @returns {Object} A successful response has 204 No Content status and an empty body
445
+ */
446
+ async deleteProject(opts) {
447
+ const {
448
+ $,
449
+ data = {},
450
+ } = opts;
451
+ const { projectId } = data;
452
+ return this._makeRestRequest({
453
+ $,
454
+ path: `/projects/${projectId}`,
455
+ method: "DELETE",
456
+ });
457
+ },
458
+ /**
459
+ * Invite a user to join a project
460
+ * @params {Object} opts - An object representing configuration options for this method
461
+ * @params {Object} [opts.data = {}] - object containing the project ID and user's email
462
+ * @returns {Object} Object including sync_token
463
+ */
464
+ async shareProject(opts) {
465
+ const {
466
+ $,
467
+ data = {},
468
+ } = opts;
469
+ const commands = [
470
+ {
471
+ type: "share_project",
472
+ temp_id: uuid(),
473
+ uuid: uuid(),
474
+ args: data,
475
+ },
476
+ ];
477
+ return this.sync({
478
+ $,
479
+ opts: {
480
+ commands: JSON.stringify(commands),
481
+ },
482
+ });
483
+ },
484
+ /**
485
+ * Get collaborators of a shared project
486
+ * @params {Integer} [projectId] ID of a project
487
+ * @returns {Object} JSON-encoded array containing all collaborators of a shared project
488
+ */
489
+ async getProjectCollaborators(projectId) {
490
+ if (!projectId) {
491
+ return [];
492
+ }
493
+ return this._makeRestRequest({
494
+ path: `/projects/${projectId}/collaborators`,
495
+ method: "GET",
496
+ });
497
+ },
498
+ /**
499
+ * Get section by ID or get a list of all sections in a project in no ID is specified
500
+ * @params {Object} opts - An object representing configuration options for this method
501
+ * @params {Object} params [opts.params = {}] - object containing section_id and/or project_id
502
+ * @returns {Object|Array} A section object related to the given ID or a list of sections if
503
+ * no ID specified
504
+ */
505
+ async getSections(opts) {
506
+ const {
507
+ $,
508
+ params = {},
509
+ } = opts;
510
+ const { section_id: id = "" } = params;
511
+ delete params.section_id;
512
+ return this._makeRestRequest({
513
+ $,
514
+ path: `/sections/${id}`,
515
+ method: "GET",
516
+ params,
517
+ });
518
+ },
519
+ /**
520
+ * Create a new section
521
+ * @params {Object} opts - An object representing configuration options for this method
522
+ * @params {Object} [opts.data = {}] - object containing info about the new section
523
+ * being created
524
+ * @returns {Object} The created section as a JSON object
525
+ */
526
+ async createSection(opts) {
527
+ const {
528
+ $,
529
+ data = {},
530
+ } = opts;
531
+ return this._makeRestRequest({
532
+ $,
533
+ path: "/sections",
534
+ method: "POST",
535
+ data,
536
+ });
537
+ },
538
+ /**
539
+ * Update a section
540
+ * @params {Object} opts - An object representing configuration options for this method
541
+ * @params {Object} [opts.data = {}] - object containing the sectionId and updated name
542
+ * @returns {Object} A successful response has 204 No Content status and an empty body
543
+ */
544
+ async updateSection(opts) {
545
+ const {
546
+ $,
547
+ data = {},
548
+ } = opts;
549
+ const { sectionId } = data;
550
+ delete data.sectionId;
551
+ return this._makeRestRequest({
552
+ $,
553
+ path: `/sections/${sectionId}`,
554
+ method: "POST",
555
+ data,
556
+ });
557
+ },
558
+ /**
559
+ * Delete a section
560
+ * @params {Object} opts - An object representing configuration options for this method
561
+ * @params {Object} [opts.data = {}] - object containing the sectionId
562
+ * @returns {Object} A successful response has 204 No Content status and an empty body
563
+ */
564
+ async deleteSection(opts) {
565
+ const {
566
+ $,
567
+ data = {},
568
+ } = opts;
569
+ const { sectionId } = data;
570
+ return this._makeRestRequest({
571
+ $,
572
+ path: `/sections/${sectionId}`,
573
+ method: "DELETE",
574
+ });
575
+ },
576
+ /**
577
+ * Get label by ID or get all labels if no ID specified
578
+ * @params {Object} opts - An object representing configuration options for this method
579
+ * @params {Integer} [opts.id = ""] - A label ID
580
+ * @returns {Object|Array} A label object related to the given ID or all user labels if
581
+ * no ID specified
582
+ */
583
+ async getLabels(opts) {
584
+ const {
585
+ $,
586
+ id = "",
587
+ } = opts;
588
+ return this._makeRestRequest({
589
+ $,
590
+ path: `/labels/${id}`,
591
+ method: "GET",
592
+ });
593
+ },
594
+ /**
595
+ * Create a new label
596
+ * @params {Object} opts - An object representing configuration options for this method
597
+ * @params {Object} [opts.data = {}] - object containing info about the new label being created
598
+ * @returns {Object} The created label as a JSON object
599
+ */
600
+ async createLabel(opts) {
601
+ const {
602
+ $,
603
+ data = {},
604
+ } = opts;
605
+ return this._makeRestRequest({
606
+ $,
607
+ path: "/labels",
608
+ method: "POST",
609
+ data,
610
+ });
611
+ },
612
+ /**
613
+ * Update a label
614
+ * @params {Object} opts - An object representing configuration options for this method
615
+ * @params {Object} [opts.data = {}] - object containing id & info about the label being updated
616
+ * @returns {Object} The created label as a JSON object
617
+ */
618
+ async updateLabel(opts) {
619
+ const {
620
+ $,
621
+ data = {},
622
+ } = opts;
623
+ const { labelId } = data;
624
+ delete data.labelId;
625
+ return this._makeRestRequest({
626
+ $,
627
+ path: `/labels/${labelId}`,
628
+ method: "POST",
629
+ data,
630
+ });
631
+ },
632
+ /**
633
+ * Delete a label
634
+ * @params {Object} opts - An object representing configuration options for this method
635
+ * @params {Object} [opts.data = {}] - object containing the labelId
636
+ * @returns {Object} A successful response has 204 No Content status and an empty body
637
+ */
638
+ async deleteLabel(opts) {
639
+ const {
640
+ $,
641
+ data = {},
642
+ } = opts;
643
+ const { labelId } = data;
644
+ return this._makeRestRequest({
645
+ $,
646
+ path: `/labels/${labelId}`,
647
+ method: "DELETE",
648
+ });
649
+ },
650
+ /**
651
+ * Get a comment by ID or get a list of comments in a project or task if no ID is specified
652
+ * @params {Object} opts - An object representing configuration options for this method
653
+ * @params {Object} [opts.params = {}] - object containing one or more of comment_id,
654
+ * project_id or task_id
655
+ * @returns {Array} JSON-encoded array containing comments
656
+ */
657
+ async getComments(opts) {
658
+ const {
659
+ $,
660
+ params,
661
+ } = opts;
662
+ const {
663
+ comment_id: id = "",
664
+ task_id: taskId,
665
+ } = params;
666
+ if (taskId) {
667
+ delete params.project_id;
668
+ }
669
+ delete params.comment_id;
670
+ return this._makeRestRequest({
671
+ $,
672
+ path: `/comments/${id}`,
673
+ method: "GET",
674
+ params,
675
+ });
676
+ },
677
+ /**
678
+ * Create a new comment in a task or project
679
+ * @params {Object} opts - An object representing configuration options for this method
680
+ * @params {Object} [opts.data = {}] - object containing info about the new comment
681
+ * being created
682
+ * @returns {Object} The created comment as a JSON object
683
+ */
684
+ async createComment(opts) {
685
+ const {
686
+ $,
687
+ data = {},
688
+ } = opts;
689
+ return this._makeRestRequest({
690
+ $,
691
+ path: "/comments",
692
+ method: "POST",
693
+ data,
694
+ });
695
+ },
696
+ /**
697
+ * Update a comment in a task or project
698
+ * @params {Object} opts - An object representing configuration options for this method
699
+ * @params {Object} [opts.data = {}] - object containing the commentId and new comment content
700
+ * @returns {Object} A successful response has 204 No Content status and an empty body
701
+ */
702
+ async updateComment(opts) {
703
+ const {
704
+ $,
705
+ data = {},
706
+ } = opts;
707
+ const { commentId } = data;
708
+ delete data.commentId;
709
+ return this._makeRestRequest({
710
+ $,
711
+ path: `/comments/${commentId}`,
712
+ method: "POST",
713
+ data,
714
+ });
715
+ },
716
+ /**
717
+ * Delete a comment in a task or project
718
+ * @params {Object} opts - An object representing configuration options for this method
719
+ * @params {Object} [opts.data = {}] = object containing the commentId
720
+ * @returns {Object} A successful response has 204 No Content status and an empty body
721
+ */
722
+ async deleteComment(opts) {
723
+ const {
724
+ $,
725
+ data = {},
726
+ } = opts;
727
+ const { commentId } = data;
728
+ return this._makeRestRequest({
729
+ $,
730
+ path: `/comments/${commentId}`,
731
+ method: "DELETE",
732
+ });
733
+ },
734
+ /**
735
+ * Get task by ID or get a list of all active tasks in a project if no ID is specified
736
+ * @params {Object} opts - An object representing configuration options for this method
737
+ * @params {Object} [opts.params = {}] - object containing one or more of task_id, project_id,
738
+ * section_id, and/or label_id
739
+ * @returns {Object|Array} A task object related to the given ID or a list of tasks if
740
+ * no ID is specified
741
+ */
742
+ async getActiveTasks(opts) {
743
+ const {
744
+ $,
745
+ params = {},
746
+ } = opts;
747
+ const { task_id: id = "" } = params;
748
+ delete params.task_id;
749
+ return this._makeRestRequest({
750
+ $,
751
+ path: `/tasks/${id}`,
752
+ method: "GET",
753
+ params,
754
+ });
755
+ },
756
+ /**
757
+ * Get a list of all completed tasks in a project
758
+ * @params {Object} opts - An object representing configuration options for this method
759
+ * @params {Object} [opts.params = {}] - object containing project_id,
760
+ * @returns {Object|Array} A list of task objects
761
+ */
762
+ async getCompletedTasks(opts) {
763
+ const {
764
+ $,
765
+ params = {},
766
+ } = opts;
767
+ return (await this._makeSyncRequest({
768
+ $,
769
+ path: "/sync/v8/completed/get_all",
770
+ method: "POST",
771
+ payload: params,
772
+ })).items;
773
+ },
774
+ /**
775
+ * Create a new task
776
+ * @params {Object} opts - An object representing configuration options for this method
777
+ * @params {Object} [opts.data = {}] - object containing info about the new task being created
778
+ * @returns {Object} The created task as a JSON object
779
+ */
780
+ async createTask(opts) {
781
+ const {
782
+ $,
783
+ data = {},
784
+ } = opts;
785
+ return this._makeRestRequest({
786
+ $,
787
+ path: "/tasks",
788
+ method: "POST",
789
+ data,
790
+ });
791
+ },
792
+ /**
793
+ * Create multiple new tasks
794
+ * @params {Object} opts - An object representing configuration options for this method
795
+ * @params {Array} [opts.data = []] - an array of objects, each containing parameters
796
+ * for a new task to be created
797
+ * @returns {Object} An array of responses and tempIds for each new task
798
+ */
799
+ async createTasks(opts) {
800
+ const {
801
+ $,
802
+ data = [],
803
+ } = opts;
804
+ const commands = [];
805
+ for (const taskData of data) {
806
+ commands.push({
807
+ type: "item_add",
808
+ temp_id: uuid(),
809
+ uuid: uuid(),
810
+ args: taskData,
811
+ });
812
+ }
813
+ const syncResponses = await this.batchSync({
814
+ $,
815
+ opts: {
816
+ commands,
817
+ },
818
+ });
819
+ return {
820
+ responses: syncResponses,
821
+ tempIds: commands.map((command) => command.temp_id),
822
+ };
823
+ },
824
+ /**
825
+ * Moves tasks to new parent_id, section_id, or project_id
826
+ * @params {Object} opts - An object representing configuration options for this method
827
+ * @retursn {Object} - An array of responses, one per batch of tasks
828
+ */
829
+ async moveTasks(opts) {
830
+ const {
831
+ $,
832
+ data = [],
833
+ } = opts;
834
+
835
+ const commands = data.map((args) => ({
836
+ type: "item_move",
837
+ uuid: uuid(),
838
+ args,
839
+ }));
840
+
841
+ const syncResponses = await this.batchSync({
842
+ $,
843
+ opts: {
844
+ commands,
845
+ },
846
+ });
847
+ return {
848
+ responses: syncResponses,
849
+ };
850
+ },
851
+ /**
852
+ * Update a task
853
+ * @params {Object} opts - An object representing configuration options for this method
854
+ * @params {Object} [opts.data = {}] - object containing info about the task being updated
855
+ * @returns {Object} The updated task as a JSON object
856
+ */
857
+ async updateTask(opts) {
858
+ const {
859
+ $,
860
+ data = {},
861
+ } = opts;
862
+ const { taskId } = data;
863
+ delete data.taskId;
864
+ return this._makeRestRequest({
865
+ $,
866
+ path: `/tasks/${taskId}`,
867
+ method: "POST",
868
+ data,
869
+ });
870
+ },
871
+ /**
872
+ * Mark a task as closed/completed by the task id
873
+ * @params {Object} opts - An object representing configuration options for this method
874
+ * @params {Object} [opts.params = {}] - object containing a taskId
875
+ * @returns {Object} A successful response has 204 No Content status and an empty body
876
+ */
877
+ async closeTask(opts) {
878
+ const {
879
+ $,
880
+ params = {},
881
+ } = opts;
882
+ const { taskId } = params;
883
+ return this._makeRestRequest({
884
+ $,
885
+ path: `tasks/${taskId}/close`,
886
+ method: "POST",
887
+ });
888
+ },
889
+ /**
890
+ * Reopen/uncomplete a task by the task id
891
+ * @params {Object} opts - An object representing configuration options for this method
892
+ * @params {Object} [opts.params = {}] - object containing a taskId
893
+ * @returns {Object} A successful response has 204 No Content status and an empty body
894
+ */
895
+ async reopenTask(opts) {
896
+ const {
897
+ $,
898
+ params = {},
899
+ } = opts;
900
+ const { taskId } = params;
901
+ return this._makeRestRequest({
902
+ $,
903
+ path: `tasks/${taskId}/reopen`,
904
+ method: "POST",
905
+ });
906
+ },
907
+ /**
908
+ * Delete a task by the task id
909
+ * @params {Object} opts - An object representing configuration options for this method
910
+ * @params {Object} [opts.data = {}] - object containing a taskId
911
+ * @returns {Object} A successful response has 204 No Content status and an empty body
912
+ */
913
+ async deleteTask(opts) {
914
+ const {
915
+ $,
916
+ data = {},
917
+ } = opts;
918
+ const { taskId } = data;
919
+ return this._makeRestRequest({
920
+ $,
921
+ path: `tasks/${taskId}`,
922
+ method: "DELETE",
923
+ });
924
+ },
925
+ /**
926
+ * Move a task to a different section
927
+ * @params {Object} opts - An object representing configuration options for this method
928
+ * @params {Object} [opts.data = {}] - object containing a task id and section_id
929
+ * @returns {Object} Object containing the sync_status
930
+ */
931
+ async moveTask(opts) {
932
+ const {
933
+ $,
934
+ data = {},
935
+ } = opts;
936
+ const commands = [
937
+ {
938
+ type: "item_move",
939
+ uuid: uuid(),
940
+ args: data,
941
+ },
942
+ ];
943
+ return this.sync({
944
+ $,
945
+ opts: {
946
+ commands: JSON.stringify(commands),
947
+ },
948
+ });
949
+ },
950
+ /**
951
+ * Get a list of new tasks/items
952
+ * @params {Object} db - a database instance
953
+ * @returns {Object} Object containing new tasks
954
+ */
955
+ async syncItems(db) {
956
+ return this.syncResources(db, [
957
+ "items",
958
+ ]);
959
+ },
960
+ /**
961
+ * Get a list of new projects
962
+ * @params {Object} db - a database instance
963
+ * @returns {Object} Object containing new projects
964
+ */
965
+ async syncProjects(db) {
966
+ return this.syncResources(db, [
967
+ "projects",
968
+ ]);
969
+ },
970
+ /**
971
+ * Get a list of new sections
972
+ * @params {Object} db - a database instance
973
+ * @returns {Object} Object containing new sections
974
+ */
975
+ async syncSections(db) {
976
+ return this.syncResources(db, [
977
+ "sections",
978
+ ]);
979
+ },
980
+ /**
981
+ * Get a list of collaborators
982
+ * @params {Object} [db = null] - a database instance
983
+ * @returns {Object} Object containing new collaborators
984
+ */
985
+ async syncCollaborators(db = null) {
986
+ return this.syncResources(db, [
987
+ "collaborators",
988
+ ]);
989
+ },
990
+ /**
991
+ * Get a list of filters
992
+ * @params {Object} [db] - a database instance
993
+ * @returns {Object} Object containing filters
994
+ */
995
+ async getFilters({
996
+ $, db,
997
+ }) {
998
+ if (db) {
999
+ this._setSyncToken(db, "*");
1000
+ }
1001
+ return (await this.syncResources(db, [
1002
+ "filters",
1003
+ ], $)).filters;
1004
+ },
1005
+ /**
1006
+ * Create a new filter
1007
+ * @params {Object} opts - An object representing configuration options for this method
1008
+ * @params {Object} [opts.data = {}] - object containing info about the new filter being created
1009
+ * @returns {Object} Object including sync_token
1010
+ */
1011
+ async createFilter(opts) {
1012
+ const {
1013
+ $,
1014
+ data = {},
1015
+ } = opts;
1016
+ const commands = [
1017
+ {
1018
+ type: "filter_add",
1019
+ temp_id: uuid(),
1020
+ uuid: uuid(),
1021
+ args: data,
1022
+ },
1023
+ ];
1024
+ return this.sync({
1025
+ $,
1026
+ opts: {
1027
+ commands: JSON.stringify(commands),
1028
+ },
1029
+ });
1030
+ },
1031
+ /**
1032
+ * Update filter
1033
+ * @params {Object} opts - An object representing configuration options for this method
1034
+ * @params {Object} [opts.data = {}] - object containing info about the filter being updated
1035
+ * @returns {Object} Object including sync_token
1036
+ */
1037
+ async updateFilter(opts) {
1038
+ const {
1039
+ $,
1040
+ data = {},
1041
+ } = opts;
1042
+ const commands = [
1043
+ {
1044
+ type: "filter_update",
1045
+ uuid: uuid(),
1046
+ args: data,
1047
+ },
1048
+ ];
1049
+ return this.sync({
1050
+ $,
1051
+ opts: {
1052
+ commands: JSON.stringify(commands),
1053
+ },
1054
+ });
1055
+ },
1056
+ /**
1057
+ * Delete filter
1058
+ * @params {Object} opts - An object representing configuration options for this method
1059
+ * @params {Object} [opts.data = {}] - object containing a filter ID
1060
+ * @returns {Object} Object including sync_token
1061
+ */
1062
+ async deleteFilter(opts) {
1063
+ const {
1064
+ $,
1065
+ data = {},
1066
+ } = opts;
1067
+ const commands = [
1068
+ {
1069
+ type: "filter_delete",
1070
+ uuid: uuid(),
1071
+ args: data,
1072
+ },
1073
+ ];
1074
+ return this.sync({
1075
+ $,
1076
+ opts: {
1077
+ commands: JSON.stringify(commands),
1078
+ },
1079
+ });
1080
+ },
1081
+ /**
1082
+ * @params {Object} [db] - a database instance
1083
+ * @params {Array} resourceTypes - an array of strings representing
1084
+ * resource_types to retrieve updates for
1085
+ * @returns {Object} Object with one or more arrays containing new
1086
+ * resources created since the last syncToken
1087
+ */
1088
+ async syncResources(db, resourceTypes, $ = null) {
1089
+ const syncToken = db
1090
+ ? this._getSyncToken(db) || "*"
1091
+ : "*";
1092
+ const result = await this.sync({
1093
+ $,
1094
+ opts: {
1095
+ resource_types: JSON.stringify(resourceTypes),
1096
+ sync_token: syncToken,
1097
+ },
1098
+ });
1099
+ if (db) {
1100
+ this._setSyncToken(db, result.sync_token);
1101
+ }
1102
+ return result;
1103
+ },
1104
+ },
1105
+ };