-
-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
71bc89d
commit a2e7d0a
Showing
31 changed files
with
878 additions
and
460 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type LabelId = string; | ||
|
||
export type Label = { | ||
id: LabelId, | ||
name: string, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export type ProjectId = string; | ||
|
||
export type Project = { | ||
id: ProjectId, | ||
parentId: ProjectId | null, | ||
name: string, | ||
order: number, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { ProjectId } from "./project"; | ||
|
||
export type SectionId = string; | ||
|
||
export type Section = { | ||
id: SectionId, | ||
projectId: ProjectId, | ||
name: string, | ||
order: number, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import moment from "moment"; | ||
import type { ProjectId } from "./project"; | ||
import type { SectionId } from "./section"; | ||
|
||
export type TaskId = string; | ||
|
||
export type Task = { | ||
id: TaskId, | ||
|
||
content: string, | ||
description: string, | ||
|
||
projectId: ProjectId, | ||
sectionId: SectionId | null, | ||
parentId: TaskId | null, | ||
|
||
labels: string[], | ||
priority: Priority, | ||
|
||
due: DueDate | null, | ||
|
||
order: number, | ||
}; | ||
|
||
export type DueDate = { | ||
recurring: boolean, | ||
date: string, | ||
datetime?: string, | ||
}; | ||
|
||
export type DueDateInfo = { | ||
hasDate: boolean, | ||
hasTime: boolean, | ||
|
||
isOverdue: boolean, | ||
isToday: boolean, | ||
|
||
m?: moment.Moment, | ||
} | ||
|
||
export function getDueDateInfo(dueDate: DueDate | undefined): DueDateInfo { | ||
if (dueDate === undefined) { | ||
return { | ||
hasDate: false, | ||
hasTime: false, | ||
|
||
isOverdue: false, | ||
isToday: false, | ||
}; | ||
} | ||
|
||
const hasTime = dueDate.datetime !== undefined; | ||
const date = moment(dueDate.datetime ?? dueDate.date); | ||
|
||
const isToday = date.isSame(new Date(), "day"); | ||
const isOverdue = hasTime ? date.isBefore() : date.clone().add(1, "day").isBefore() | ||
|
||
|
||
return { | ||
hasDate: true, | ||
hasTime: hasTime, | ||
isToday: isToday, | ||
isOverdue: isOverdue, | ||
m: date, | ||
}; | ||
} | ||
|
||
export type Priority = 1 | 2 | 3 | 4; | ||
|
||
export type CreateTaskParams = { | ||
description?: string; | ||
priority?: number; | ||
projectId?: ProjectId, | ||
sectionId?: SectionId, | ||
dueDate?: string, | ||
labels?: string[], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export interface WebFetcher { | ||
fetch(params: RequestParams): Promise<WebResponse>, | ||
} | ||
|
||
export type RequestParams = { | ||
url: string, | ||
method: string, | ||
headers?: Record<string, string>, | ||
body?: string, | ||
}; | ||
|
||
export type WebResponse = { | ||
statusCode: number, | ||
body: string, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import type { Label } from "./domain/label"; | ||
import type { Project } from "./domain/project"; | ||
import type { Section } from "./domain/section"; | ||
import type { CreateTaskParams, Task, TaskId } from "./domain/task"; | ||
import type { RequestParams, WebFetcher, WebResponse } from "./fetcher"; | ||
import camelize from "camelize"; | ||
import snakeize from "snakeize"; | ||
|
||
// TODO: Consider validating API response shape w/ zod or similar. | ||
export class TodoistApiClient { | ||
private token: string; | ||
private fetcher: WebFetcher; | ||
|
||
constructor(token: string, fetcher: WebFetcher) { | ||
this.token = token; | ||
this.fetcher = fetcher; | ||
} | ||
|
||
public async getTasks(filter?: string): Promise<Task[]> { | ||
let path = "/tasks"; | ||
|
||
if (filter !== undefined) { | ||
path += `?filter=${encodeURIComponent(filter)}`; | ||
} | ||
|
||
const response = await this.do(path, "GET"); | ||
|
||
return camelize(JSON.parse(response.body)) as Task[]; | ||
} | ||
|
||
public async createTask(content: string, options?: CreateTaskParams): Promise<void> { | ||
const body = snakeize({ content: content, ...(options ?? {}) }); | ||
await this.do("/tasks", "POST", body); | ||
} | ||
|
||
public async closeTask(id: TaskId): Promise<void> { | ||
await this.do(`/tasks/${id}/close`, "POST"); | ||
} | ||
|
||
public async getProjects(): Promise<Project[]> { | ||
const response = await this.do("/projects", "GET"); | ||
return camelize(JSON.parse(response.body)) as Project[]; | ||
} | ||
|
||
public async getSections(): Promise<Section[]> { | ||
const response = await this.do("/sections", "GET"); | ||
return camelize(JSON.parse(response.body)) as Section[]; | ||
} | ||
|
||
public async getLabels(): Promise<Label[]> { | ||
const response = await this.do("/labels", "GET"); | ||
return camelize(JSON.parse(response.body)) as Label[]; | ||
} | ||
|
||
private async do(path: string, method: string, json?: object): Promise<WebResponse> { | ||
const params: RequestParams = { | ||
url: `https://api.todoist.com/rest/v2${path}`, | ||
method: method, | ||
headers: { | ||
"Authorization": `Bearer ${this.token}`, | ||
}, | ||
}; | ||
|
||
if (json !== undefined) { | ||
params.body = JSON.stringify(json); | ||
params.headers["Content-Type"] = "application/json"; | ||
} | ||
|
||
const response = await this.fetcher.fetch(params); | ||
|
||
if (response.statusCode >= 400) { | ||
throw new TodoistApiError(params, response); | ||
} | ||
|
||
return response; | ||
} | ||
} | ||
|
||
class TodoistApiError extends Error { | ||
constructor(request: RequestParams, response: WebResponse) { | ||
const message = `[${request.method}] ${request.url} returned '${response.statusCode}: ${response.body}`; | ||
super(message) | ||
} | ||
} | ||
|
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.