From 601509379048496170a4c2d5c728b5b898d41e08 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Tue, 15 Apr 2025 09:24:20 +0200 Subject: [PATCH 1/5] refactor: [BREAKING] update Task and Project schemas to match API objects --- src/TodoistApi.projects.test.ts | 31 +++---- src/TodoistApi.tasks.test.ts | 55 ++++--------- src/TodoistApi.ts | 88 ++++++++++---------- src/testUtils/testDefaults.ts | 133 +++++++++---------------------- src/types/entities.ts | 103 ++++++++++++------------ src/types/requests.ts | 27 ------- src/types/sync.ts | 4 +- src/utils/projectConverter.ts | 23 ------ src/utils/taskConverters.test.ts | 41 ---------- src/utils/taskConverters.ts | 56 ------------- src/utils/validators.ts | 34 ++++---- 11 files changed, 180 insertions(+), 415 deletions(-) delete mode 100644 src/utils/projectConverter.ts delete mode 100644 src/utils/taskConverters.test.ts delete mode 100644 src/utils/taskConverters.ts diff --git a/src/TodoistApi.projects.test.ts b/src/TodoistApi.projects.test.ts index 503591d..112aff5 100644 --- a/src/TodoistApi.projects.test.ts +++ b/src/TodoistApi.projects.test.ts @@ -2,9 +2,9 @@ import { TodoistApi } from '.' import { DEFAULT_AUTH_TOKEN, DEFAULT_PROJECT, - RAW_DEFAULT_PROJECT, DEFAULT_REQUEST_ID, DEFAULT_USER, + PROJECT_WITH_OPTIONALS_AS_NULL, } from './testUtils/testDefaults' import { getSyncBaseUri, @@ -21,7 +21,7 @@ describe('TodoistApi project endpoints', () => { describe('getProject', () => { test('calls get request with expected url', async () => { const projectId = '12' - const requestMock = setupRestClientMock(RAW_DEFAULT_PROJECT) + const requestMock = setupRestClientMock(DEFAULT_PROJECT) const api = getTarget() await api.getProject(projectId) @@ -36,7 +36,7 @@ describe('TodoistApi project endpoints', () => { }) test('returns result from rest client', async () => { - setupRestClientMock(RAW_DEFAULT_PROJECT) + setupRestClientMock(DEFAULT_PROJECT) const api = getTarget() const project = await api.getProject('123') @@ -48,7 +48,7 @@ describe('TodoistApi project endpoints', () => { describe('getProjects', () => { test('calls get on projects endpoint', async () => { const requestMock = setupRestClientMock({ - results: [RAW_DEFAULT_PROJECT], + results: [DEFAULT_PROJECT], nextCursor: '123', }) const api = getTarget() @@ -67,13 +67,13 @@ describe('TodoistApi project endpoints', () => { }) test('returns result from rest client', async () => { - const projects = [RAW_DEFAULT_PROJECT] + const projects = [DEFAULT_PROJECT, PROJECT_WITH_OPTIONALS_AS_NULL] setupRestClientMock({ results: projects, nextCursor: '123' }) const api = getTarget() const { results, nextCursor } = await api.getProjects() - expect(results).toEqual([DEFAULT_PROJECT]) + expect(results).toEqual(projects) expect(nextCursor).toBe('123') }) }) @@ -84,7 +84,7 @@ describe('TodoistApi project endpoints', () => { } test('calls post on restClient with expected parameters', async () => { - const requestMock = setupRestClientMock(RAW_DEFAULT_PROJECT) + const requestMock = setupRestClientMock(DEFAULT_PROJECT) const api = getTarget() await api.addProject(DEFAULT_ADD_PROJECT_ARGS, DEFAULT_REQUEST_ID) @@ -101,7 +101,7 @@ describe('TodoistApi project endpoints', () => { }) test('returns result from rest client', async () => { - setupRestClientMock(RAW_DEFAULT_PROJECT) + setupRestClientMock(DEFAULT_PROJECT) const api = getTarget() const project = await api.addProject(DEFAULT_ADD_PROJECT_ARGS) @@ -115,7 +115,7 @@ describe('TodoistApi project endpoints', () => { test('calls post on restClient with expected parameters', async () => { const projectId = '123' const updateArgs = { name: 'a new name' } - const requestMock = setupRestClientMock(RAW_DEFAULT_PROJECT, 204) + const requestMock = setupRestClientMock(DEFAULT_PROJECT, 204) const api = getTarget() await api.updateProject(projectId, updateArgs, DEFAULT_REQUEST_ID) @@ -132,20 +132,13 @@ describe('TodoistApi project endpoints', () => { }) test('returns success result from rest client', async () => { - const RAW_DEFAULT_PROJECT_WITH_UPDATES = { - ...RAW_DEFAULT_PROJECT, - name: DEFAULT_UPDATE_PROJECT_ARGS.name, - } - setupRestClientMock(RAW_DEFAULT_PROJECT_WITH_UPDATES, 204) + const returnedProject = { ...DEFAULT_PROJECT, ...DEFAULT_UPDATE_PROJECT_ARGS } + setupRestClientMock(returnedProject, 204) const api = getTarget() const result = await api.updateProject('123', DEFAULT_UPDATE_PROJECT_ARGS) - const DEFAULT_PROJECT_WITH_UPDATES = { - ...DEFAULT_PROJECT, - name: DEFAULT_UPDATE_PROJECT_ARGS.name, - } - expect(result).toEqual(DEFAULT_PROJECT_WITH_UPDATES) + expect(result).toEqual(returnedProject) }) }) diff --git a/src/TodoistApi.tasks.test.ts b/src/TodoistApi.tasks.test.ts index dfed950..df208a2 100644 --- a/src/TodoistApi.tasks.test.ts +++ b/src/TodoistApi.tasks.test.ts @@ -1,12 +1,8 @@ -import * as taskConverters from './utils/taskConverters' import { TodoistApi } from '.' -import { Task } from './types' import { DEFAULT_AUTH_TOKEN, - DEFAULT_QUICK_ADD_RESPONSE, DEFAULT_REQUEST_ID, DEFAULT_TASK, - RAW_DEFAULT_TASK, TASK_WITH_OPTIONALS_AS_NULL, } from './testUtils/testDefaults' import { @@ -19,10 +15,6 @@ import { } from './consts/endpoints' import { setupRestClientMock } from './testUtils/mocks' -function setupSyncTaskConverter(returnedTask: Task) { - return jest.spyOn(taskConverters, 'getTaskFromQuickAddResponse').mockReturnValue(returnedTask) -} - function getTarget(baseUrl = 'https://api.todoist.com') { return new TodoistApi(DEFAULT_AUTH_TOKEN, baseUrl) } @@ -34,7 +26,7 @@ describe('TodoistApi task endpoints', () => { } test('calls post on restClient with expected parameters', async () => { - const requestMock = setupRestClientMock(RAW_DEFAULT_TASK) + const requestMock = setupRestClientMock(DEFAULT_TASK) const api = getTarget() await api.addTask(DEFAULT_ADD_TASK_ARGS, DEFAULT_REQUEST_ID) @@ -51,7 +43,7 @@ describe('TodoistApi task endpoints', () => { }) test('calls post on restClient with expected parameters against staging', async () => { - const requestMock = setupRestClientMock(RAW_DEFAULT_TASK) + const requestMock = setupRestClientMock(DEFAULT_TASK) const api = getTarget('https://staging.todoist.com') await api.addTask(DEFAULT_ADD_TASK_ARGS, DEFAULT_REQUEST_ID) @@ -68,7 +60,7 @@ describe('TodoistApi task endpoints', () => { }) test('returns result from rest client', async () => { - setupRestClientMock(RAW_DEFAULT_TASK) + setupRestClientMock(DEFAULT_TASK) const api = getTarget() const task = await api.addTask(DEFAULT_ADD_TASK_ARGS) @@ -82,7 +74,7 @@ describe('TodoistApi task endpoints', () => { test('calls post on restClient with expected parameters', async () => { const taskId = '123' - const requestMock = setupRestClientMock(RAW_DEFAULT_TASK, 204) + const requestMock = setupRestClientMock(DEFAULT_TASK, 204) const api = getTarget() await api.updateTask(taskId, DEFAULT_UPDATE_TASK_ARGS, DEFAULT_REQUEST_ID) @@ -99,20 +91,13 @@ describe('TodoistApi task endpoints', () => { }) test('returns success result from rest client', async () => { - const RAW_DEFAULT_TASK_WITH_UPDATES = { - ...RAW_DEFAULT_TASK, - content: DEFAULT_UPDATE_TASK_ARGS.content, - } - setupRestClientMock(RAW_DEFAULT_TASK_WITH_UPDATES, 204) + const returnedTask = { ...DEFAULT_TASK, ...DEFAULT_UPDATE_TASK_ARGS } + setupRestClientMock(returnedTask, 204) const api = getTarget() const response = await api.updateTask('123', DEFAULT_UPDATE_TASK_ARGS) - const DEFAULT_TASK_WITH_UPDATES = { - ...DEFAULT_TASK, - content: DEFAULT_UPDATE_TASK_ARGS.content, - } - expect(response).toEqual(DEFAULT_TASK_WITH_UPDATES) + expect(response).toEqual(returnedTask) }) }) @@ -212,7 +197,7 @@ describe('TodoistApi task endpoints', () => { } test('calls sync endpoint with expected parameters', async () => { - const requestMock = setupRestClientMock(DEFAULT_QUICK_ADD_RESPONSE) + const requestMock = setupRestClientMock(DEFAULT_TASK) const api = getTarget() await api.quickAddTask(DEFAULT_QUICK_ADD_ARGS) @@ -228,14 +213,9 @@ describe('TodoistApi task endpoints', () => { }) test('calls task converter with response data and returns result', async () => { - setupRestClientMock(DEFAULT_QUICK_ADD_RESPONSE) - const taskConverter = setupSyncTaskConverter(DEFAULT_TASK) + setupRestClientMock(DEFAULT_TASK) const api = getTarget() - const task = await api.quickAddTask(DEFAULT_QUICK_ADD_ARGS) - - expect(taskConverter).toBeCalledTimes(1) - expect(taskConverter).toBeCalledWith(DEFAULT_QUICK_ADD_RESPONSE) expect(task).toEqual(DEFAULT_TASK) }) }) @@ -243,7 +223,7 @@ describe('TodoistApi task endpoints', () => { describe('getTask', () => { test('calls get request with expected url', async () => { const taskId = '12' - const requestMock = setupRestClientMock(RAW_DEFAULT_TASK) + const requestMock = setupRestClientMock(DEFAULT_TASK) const api = getTarget() await api.getTask(taskId) @@ -267,7 +247,7 @@ describe('TodoistApi task endpoints', () => { test('calls get on expected endpoint with args', async () => { const requestMock = setupRestClientMock({ - results: [RAW_DEFAULT_TASK, TASK_WITH_OPTIONALS_AS_NULL], + results: [DEFAULT_TASK, TASK_WITH_OPTIONALS_AS_NULL], nextCursor: '123', }) const api = getTarget() @@ -285,13 +265,13 @@ describe('TodoistApi task endpoints', () => { }) test('returns result from rest client', async () => { - const tasks = [RAW_DEFAULT_TASK] + const tasks = [DEFAULT_TASK] setupRestClientMock({ results: tasks, nextCursor: '123' }) const api = getTarget() const { results, nextCursor } = await api.getTasks(DEFAULT_GET_TASKS_ARGS) - expect(results).toEqual([DEFAULT_TASK]) + expect(results).toEqual(tasks) expect(nextCursor).toBe('123') }) }) @@ -305,10 +285,7 @@ describe('TodoistApi task endpoints', () => { } test('calls get request with expected url', async () => { - const requestMock = setupRestClientMock({ - results: [RAW_DEFAULT_TASK], - nextCursor: null, - }) + const requestMock = setupRestClientMock({ results: [DEFAULT_TASK], nextCursor: null }) const api = getTarget() await api.getTasksByFilter(DEFAULT_GET_TASKS_BY_FILTER_ARGS) @@ -324,7 +301,7 @@ describe('TodoistApi task endpoints', () => { }) test('returns result from rest client', async () => { - setupRestClientMock({ results: [RAW_DEFAULT_TASK], nextCursor: null }) + setupRestClientMock({ results: [DEFAULT_TASK], nextCursor: null }) const api = getTarget() const response = await api.getTasksByFilter(DEFAULT_GET_TASKS_BY_FILTER_ARGS) @@ -336,7 +313,7 @@ describe('TodoistApi task endpoints', () => { }) test('validates task array in response', async () => { - const invalidTask = { ...RAW_DEFAULT_TASK, due: '2020-01-31' } + const invalidTask = { ...DEFAULT_TASK, due: '2020-01-31' } setupRestClientMock({ results: [invalidTask], nextCursor: null }) const api = getTarget() diff --git a/src/TodoistApi.ts b/src/TodoistApi.ts index 0d4eb06..d5e9a9d 100644 --- a/src/TodoistApi.ts +++ b/src/TodoistApi.ts @@ -1,5 +1,4 @@ -import { Project, Label, Section, Comment } from './types/entities' -import type { RawProject, RawTask, Task } from './types/entities' +import { Project, Label, Section, Comment, Task } from './types/entities' import { AddCommentArgs, AddLabelArgs, @@ -30,11 +29,9 @@ import { GetSectionsResponse, GetSharedLabelsResponse, GetCommentsResponse, - QuickAddTaskResponse, type MoveTaskArgs, } from './types/requests' import { request, isSuccess } from './restClient' -import { getTaskFromQuickAddResponse, getTaskFromRawTaskResponse } from './utils/taskConverters' import { getSyncBaseUri, ENDPOINT_REST_TASKS, @@ -70,7 +67,6 @@ import { z } from 'zod' import { v4 as uuidv4 } from 'uuid' import { SyncResponse, type Command, type SyncRequest } from './types/sync' import { TodoistRequestError } from './types' -import { getProjectFromRawProjectResponse } from './utils/projectConverter' const MAX_COMMAND_COUNT = 100 @@ -130,15 +126,14 @@ export class TodoistApi { */ async getTask(id: string): Promise { z.string().parse(id) - const response = await request( + const response = await request( 'GET', this.syncApiBase, generatePath(ENDPOINT_REST_TASKS, id), this.authToken, ) - const task = getTaskFromRawTaskResponse(response.data) - return validateTask(task) + return validateTask(response.data) } /** @@ -149,13 +144,15 @@ export class TodoistApi { */ async getTasks(args: GetTasksArgs = {}): Promise { const { - data: { results: rawResults, nextCursor }, - } = await request<{ - results: RawTask[] - nextCursor: string | null - }>('GET', this.syncApiBase, ENDPOINT_REST_TASKS, this.authToken, args) + data: { results, nextCursor }, + } = await request( + 'GET', + this.syncApiBase, + ENDPOINT_REST_TASKS, + this.authToken, + args, + ) - const results = rawResults.map(getTaskFromRawTaskResponse) return { results: validateTaskArray(results), nextCursor, @@ -170,13 +167,15 @@ export class TodoistApi { */ async getTasksByFilter(args: GetTasksByFilterArgs): Promise { const { - data: { results: rawResults, nextCursor }, - } = await request<{ - results: RawTask[] - nextCursor: string | null - }>('GET', this.syncApiBase, ENDPOINT_REST_TASKS_FILTER, this.authToken, args) + data: { results, nextCursor }, + } = await request( + 'GET', + this.syncApiBase, + ENDPOINT_REST_TASKS_FILTER, + this.authToken, + args, + ) - const results = rawResults.map(getTaskFromRawTaskResponse) return { results: validateTaskArray(results), nextCursor, @@ -191,7 +190,7 @@ export class TodoistApi { * @returns A promise that resolves to the created task. */ async addTask(args: AddTaskArgs, requestId?: string): Promise { - const response = await request( + const response = await request( 'POST', this.syncApiBase, ENDPOINT_REST_TASKS, @@ -200,8 +199,7 @@ export class TodoistApi { requestId, ) - const task = getTaskFromRawTaskResponse(response.data) - return validateTask(task) + return validateTask(response.data) } /** @@ -211,7 +209,7 @@ export class TodoistApi { * @returns A promise that resolves to the created task. */ async quickAddTask(args: QuickAddTaskArgs): Promise { - const response = await request( + const response = await request( 'POST', this.syncApiBase, ENDPOINT_SYNC_QUICK_ADD, @@ -219,9 +217,7 @@ export class TodoistApi { args, ) - const task = getTaskFromQuickAddResponse(response.data) - - return validateTask(task) + return validateTask(response.data) } /** @@ -234,7 +230,7 @@ export class TodoistApi { */ async updateTask(id: string, args: UpdateTaskArgs, requestId?: string): Promise { z.string().parse(id) - const response = await request( + const response = await request( 'POST', this.syncApiBase, generatePath(ENDPOINT_REST_TASKS, id), @@ -243,8 +239,7 @@ export class TodoistApi { requestId, ) - const task = getTaskFromRawTaskResponse(response.data) - return validateTask(task) + return validateTask(response.data) } /** @@ -303,9 +298,7 @@ export class TodoistApi { throw new TodoistRequestError('Tasks not found', 404) } - const tasks = syncTasks.map(getTaskFromQuickAddResponse) - - return validateTaskArray(tasks) + return validateTaskArray(syncTasks) } /** @@ -376,15 +369,14 @@ export class TodoistApi { */ async getProject(id: string): Promise { z.string().parse(id) - const response = await request( + const response = await request( 'GET', this.syncApiBase, generatePath(ENDPOINT_REST_PROJECTS, id), this.authToken, ) - const project = getProjectFromRawProjectResponse(response.data) - return validateProject(project) + return validateProject(response.data) } /** @@ -395,13 +387,15 @@ export class TodoistApi { */ async getProjects(args: GetProjectsArgs = {}): Promise { const { - data: { results: rawResults, nextCursor }, - } = await request<{ - results: RawProject[] - nextCursor: string | null - }>('GET', this.syncApiBase, ENDPOINT_REST_PROJECTS, this.authToken, args) + data: { results, nextCursor }, + } = await request( + 'GET', + this.syncApiBase, + ENDPOINT_REST_PROJECTS, + this.authToken, + args, + ) - const results = rawResults.map(getProjectFromRawProjectResponse) return { results: validateProjectArray(results), nextCursor, @@ -416,7 +410,7 @@ export class TodoistApi { * @returns A promise that resolves to the created project. */ async addProject(args: AddProjectArgs, requestId?: string): Promise { - const response = await request( + const response = await request( 'POST', this.syncApiBase, ENDPOINT_REST_PROJECTS, @@ -425,8 +419,7 @@ export class TodoistApi { requestId, ) - const project = getProjectFromRawProjectResponse(response.data) - return validateProject(project) + return validateProject(response.data) } /** @@ -439,7 +432,7 @@ export class TodoistApi { */ async updateProject(id: string, args: UpdateProjectArgs, requestId?: string): Promise { z.string().parse(id) - const response = await request( + const response = await request( 'POST', this.syncApiBase, generatePath(ENDPOINT_REST_PROJECTS, id), @@ -448,8 +441,7 @@ export class TodoistApi { requestId, ) - const project = getProjectFromRawProjectResponse(response.data) - return validateProject(project) + return validateProject(response.data) } /** diff --git a/src/testUtils/testDefaults.ts b/src/testUtils/testDefaults.ts index dac3c46..80bcf96 100644 --- a/src/testUtils/testDefaults.ts +++ b/src/testUtils/testDefaults.ts @@ -1,16 +1,13 @@ import { Label, Project, - QuickAddTaskResponse, Section, Task, User, - RawComment, Attachment, Duration, Deadline, - RawTask, - RawProject, + RawComment, } from '../types' const DEFAULT_TASK_ID = '1234' @@ -37,6 +34,12 @@ const DEFAULT_USER_EMAIL = 'atestuser@doist.com' const DEFAULT_COMMENT_ID = '4' const DEFAULT_COMMENT_CONTENT = 'A comment' const DEFAULT_COMMENT_REACTIONS = { '👍': ['1234', '5678'] } +const DEFAULT_NOTE_COUNT = 0 +const DEFAULT_CAN_ASSIGN_TASKS = true +const DEFAULT_IS_ARCHIVED = false +const DEFAULT_IS_DELETED = false +const DEFAULT_IS_FROZEN = false +const DEFAULT_IS_COLLAPSED = false export const DEFAULT_AUTH_TOKEN = 'AToken' export const DEFAULT_REQUEST_ID = 'ARequestID' @@ -61,51 +64,31 @@ export const DEFAULT_DEADLINE: Deadline = { lang: 'en', } -export const DEFAULT_QUICK_ADD_RESPONSE: QuickAddTaskResponse = { +export const DEFAULT_TASK: Task = { id: DEFAULT_TASK_ID, + userId: DEFAULT_CREATOR, projectId: DEFAULT_PROJECT_ID, - content: DEFAULT_TASK_CONTENT, - description: DEFAULT_TASK_DESCRIPTION, - priority: DEFAULT_TASK_PRIORITY, sectionId: DEFAULT_SECTION_ID, parentId: DEFAULT_PARENT_ID, - childOrder: DEFAULT_ORDER, - labels: DEFAULT_LABELS, + addedByUid: DEFAULT_CREATOR, + assignedByUid: DEFAULT_CREATOR, responsibleUid: DEFAULT_ASSIGNEE, + labels: DEFAULT_LABELS, + deadline: DEFAULT_DEADLINE, + duration: DEFAULT_DURATION, checked: false, + isDeleted: DEFAULT_IS_DELETED, addedAt: DEFAULT_DATE, - addedByUid: DEFAULT_CREATOR, - duration: DEFAULT_DURATION, - due: { - date: DEFAULT_DATE, - timezone: null, - string: 'a date string', - lang: 'en', - isRecurring: false, - }, - deadline: DEFAULT_DEADLINE, - assignedByUid: DEFAULT_CREATOR, -} - -export const DEFAULT_TASK: Task = { - id: DEFAULT_TASK_ID, - order: DEFAULT_ORDER, - parentId: DEFAULT_PARENT_ID, + completedAt: null, + updatedAt: DEFAULT_DATE, + due: DEFAULT_DUE_DATE, + priority: DEFAULT_TASK_PRIORITY, + childOrder: DEFAULT_ORDER, content: DEFAULT_TASK_CONTENT, description: DEFAULT_TASK_DESCRIPTION, - projectId: DEFAULT_PROJECT_ID, - sectionId: DEFAULT_SECTION_ID, - isCompleted: false, - labels: DEFAULT_LABELS, - priority: DEFAULT_TASK_PRIORITY, - createdAt: DEFAULT_DATE, - url: 'https://todoist.com/showTask?id=1234', - due: DEFAULT_DUE_DATE, - assignerId: DEFAULT_CREATOR, - assigneeId: DEFAULT_ASSIGNEE, - creatorId: DEFAULT_CREATOR, - duration: DEFAULT_DURATION, - deadline: DEFAULT_DEADLINE, + noteCount: DEFAULT_NOTE_COUNT, + dayOrder: DEFAULT_ORDER, + isCollapsed: DEFAULT_IS_COLLAPSED, } export const INVALID_TASK = { @@ -113,7 +96,7 @@ export const INVALID_TASK = { due: '2020-01-31', } -export const TASK_WITH_OPTIONALS_AS_NULL: RawTask = { +export const TASK_WITH_OPTIONALS_AS_NULL: Task = { userId: DEFAULT_CREATOR, id: DEFAULT_TASK_ID, projectId: DEFAULT_PROJECT_ID, @@ -126,7 +109,7 @@ export const TASK_WITH_OPTIONALS_AS_NULL: RawTask = { deadline: null, duration: null, checked: false, - isDeleted: false, + isDeleted: DEFAULT_IS_DELETED, addedAt: DEFAULT_DATE, completedAt: null, updatedAt: DEFAULT_DATE, @@ -136,21 +119,29 @@ export const TASK_WITH_OPTIONALS_AS_NULL: RawTask = { content: DEFAULT_TASK_CONTENT, description: DEFAULT_TASK_DESCRIPTION, dayOrder: DEFAULT_ORDER, - isCollapsed: false, + isCollapsed: DEFAULT_IS_COLLAPSED, + noteCount: DEFAULT_NOTE_COUNT, } export const DEFAULT_PROJECT: Project = { id: DEFAULT_PROJECT_ID, name: DEFAULT_PROJECT_NAME, color: DEFAULT_ENTITY_COLOR, - order: DEFAULT_ORDER, + childOrder: DEFAULT_ORDER, parentId: DEFAULT_PROJECT_ID, isFavorite: false, isShared: false, - isInboxProject: false, - isTeamInbox: false, + inboxProject: false, viewStyle: DEFAULT_PROJECT_VIEW_STYLE, - url: 'https://todoist.com/showProject?id=123', + canAssignTasks: DEFAULT_CAN_ASSIGN_TASKS, + isArchived: DEFAULT_IS_ARCHIVED, + isDeleted: DEFAULT_IS_DELETED, + isFrozen: DEFAULT_IS_FROZEN, + createdAt: DEFAULT_DATE, + updatedAt: DEFAULT_DATE, + defaultOrder: DEFAULT_ORDER, + description: '', + isCollapsed: DEFAULT_IS_COLLAPSED, } export const INVALID_PROJECT = { @@ -287,51 +278,3 @@ export const COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT = { ...RAW_COMMENT_WITH_OPTIONALS_AS_NULL_PROJECT, taskId: undefined, } - -export const RAW_DEFAULT_TASK: RawTask = { - userId: DEFAULT_CREATOR, - id: DEFAULT_TASK_ID, - projectId: DEFAULT_PROJECT_ID, - sectionId: DEFAULT_SECTION_ID, - parentId: DEFAULT_PARENT_ID, - addedByUid: DEFAULT_CREATOR, - assignedByUid: DEFAULT_CREATOR, - responsibleUid: DEFAULT_ASSIGNEE, - labels: DEFAULT_LABELS, - deadline: DEFAULT_DEADLINE, - duration: DEFAULT_DURATION, - checked: false, - isDeleted: false, - addedAt: DEFAULT_DATE, - completedAt: null, - updatedAt: DEFAULT_DATE, - due: DEFAULT_DUE_DATE, - priority: DEFAULT_TASK_PRIORITY, - childOrder: DEFAULT_ORDER, - content: DEFAULT_TASK_CONTENT, - description: DEFAULT_TASK_DESCRIPTION, - dayOrder: DEFAULT_ORDER, - isCollapsed: false, -} - -export const RAW_DEFAULT_PROJECT: RawProject = { - id: DEFAULT_PROJECT_ID, - canAssignTasks: true, - childOrder: DEFAULT_ORDER, - color: DEFAULT_ENTITY_COLOR, - createdAt: DEFAULT_DATE, - isArchived: false, - isDeleted: false, - isFavorite: false, - isFrozen: false, - name: DEFAULT_PROJECT_NAME, - updatedAt: DEFAULT_DATE, - viewStyle: DEFAULT_PROJECT_VIEW_STYLE, - defaultOrder: DEFAULT_ORDER, - description: '', - publicAccess: false, - parentId: DEFAULT_PROJECT_ID, - inboxProject: false, - isCollapsed: false, - isShared: false, -} diff --git a/src/types/entities.ts b/src/types/entities.ts index 8e8ae65..fd37d38 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -36,13 +36,13 @@ export const DeadlineSchema = z.object({ */ export interface Deadline extends z.infer {} -export const RawTaskSchema = z.object({ - userId: z.string(), +export const TaskSchema = z.object({ id: z.string(), + userId: z.string(), projectId: z.string(), sectionId: z.string().nullable(), parentId: z.string().nullable(), - addedByUid: z.string(), + addedByUid: z.string().nullable(), assignedByUid: z.string().nullable(), responsibleUid: z.string().nullable(), labels: z.array(z.string()), @@ -50,87 +50,89 @@ export const RawTaskSchema = z.object({ duration: DurationSchema.nullable(), checked: z.boolean(), isDeleted: z.boolean(), - addedAt: z.string(), + addedAt: z.string().nullable(), completedAt: z.string().nullable(), - updatedAt: z.string(), + updatedAt: z.string().nullable(), due: DueDateSchema.nullable(), priority: z.number().int(), childOrder: z.number().int(), content: z.string(), description: z.string(), + noteCount: z.number().int(), dayOrder: z.number().int(), isCollapsed: z.boolean(), }) - -export interface RawTask extends z.infer {} - -export const TaskSchema = z.object({ - id: z.string(), - assignerId: z.string().nullable(), - assigneeId: z.string().nullable(), - projectId: z.string(), - sectionId: z.string().nullable(), - parentId: z.string().nullable(), - order: z.number().int(), - content: z.string(), - description: z.string(), - isCompleted: z.boolean(), - labels: z.array(z.string()), - priority: z.number().int(), - creatorId: z.string(), - createdAt: z.string(), - due: DueDateSchema.nullable(), - url: z.string(), - duration: DurationSchema.nullable(), - deadline: DeadlineSchema.nullable(), -}) /** * Represents a task in Todoist. * @see https://todoist.com/api/v1/docs#tag/Tasks */ export interface Task extends z.infer {} -export const RawProjectSchema = z.object({ +/** + * Base schema for all project types in Todoist. + * Contains common fields shared between personal and workspace projects. + */ +export const BaseProjectSchema = z.object({ id: z.string(), canAssignTasks: z.boolean(), - childOrder: z.number().int().nullable(), + childOrder: z.number().int(), color: z.string(), - createdAt: z.string(), + createdAt: z.string().nullable(), isArchived: z.boolean(), isDeleted: z.boolean(), isFavorite: z.boolean(), isFrozen: z.boolean(), name: z.string(), - updatedAt: z.string(), + updatedAt: z.string().nullable(), viewStyle: z.string(), - defaultOrder: z.number().int().nullable(), + defaultOrder: z.number().int(), description: z.string(), - publicAccess: z.boolean(), - parentId: z.string().nullable().optional(), - inboxProject: z.boolean().optional(), isCollapsed: z.boolean(), isShared: z.boolean(), }) -export interface RawProject extends z.infer {} -export const ProjectSchema = z.object({ - id: z.string(), +/** + * Schema for personal projects in Todoist. + */ +export const PersonalProjectSchema = BaseProjectSchema.extend({ parentId: z.string().nullable(), - order: z.number().int().nullable(), - color: z.string(), - name: z.string(), - isShared: z.boolean(), - isFavorite: z.boolean(), - isInboxProject: z.boolean(), - isTeamInbox: z.boolean(), - url: z.string(), - viewStyle: z.string(), + inboxProject: z.boolean(), +}) + +/** + * Schema for workspace projects in Todoist. + */ +export const WorkspaceProjectSchema = BaseProjectSchema.extend({ + collaboratorRoleDefault: z.string(), + folderId: z.boolean().nullable(), + isInviteOnly: z.boolean().nullable(), + isLinkSharingEnabled: z.boolean(), + role: z.string().nullable(), + status: z.string(), + workspaceId: z.string(), }) + +/** + * Represents a personal project in Todoist. + * @see https://todoist.com/api/v1/docs#tag/Projects + */ +export interface PersonalProject extends z.infer {} + /** - * Represents a project in Todoist. + * Represents a workspace project in Todoist. * @see https://todoist.com/api/v1/docs#tag/Projects */ -export interface Project extends z.infer {} +export interface WorkspaceProject extends z.infer {} + +/** + * Schema for validating either a personal project or a workspace project. + */ +export const ProjectSchema = z.union([PersonalProjectSchema, WorkspaceProjectSchema]) + +/** + * Represents either a personal project or a workspace project in Todoist. + */ +export type Project = z.infer // This allows us to accept any string during validation, but provide intellisense for the two possible values in request args /** @@ -233,7 +235,6 @@ export const CommentSchema = RawCommentSchema.transform((data) => { taskId: itemId, } }) - /** * Represents a comment in Todoist. * @see https://todoist.com/api/v1/docs#tag/Comments diff --git a/src/types/requests.ts b/src/types/requests.ts index 4f629f7..63d2cc1 100644 --- a/src/types/requests.ts +++ b/src/types/requests.ts @@ -1,8 +1,6 @@ import type { RequireAllOrNone, RequireOneOrNone, RequireExactlyOne } from 'type-fest' import type { Comment, - Deadline, - DueDate, Duration, Label, Project, @@ -111,31 +109,6 @@ export type QuickAddTaskArgs = { * Response from quick adding a task. * @see https://todoist.com/api/v1/docs#tag/Tasks/operation/quick_add_api_v1_tasks_quick_post */ -export type SyncTask = { - id: string - projectId: string - content: string - description: string - priority: number - sectionId: string | null - parentId: string | null - childOrder: number // order - labels: string[] - assignedByUid: string | null - responsibleUid: string | null - checked: boolean // completed - addedAt: string // created - addedByUid: string | null - duration: Duration | null - due: DueDate | null - deadline: Deadline | null -} - -/** - * Response from quick adding a task. - * @see https://todoist.com/api/v1/docs#tag/Tasks/operation/quick_add_api_v1_tasks_quick_post - */ -export type QuickAddTaskResponse = SyncTask /** * Arguments for moving a task. diff --git a/src/types/sync.ts b/src/types/sync.ts index 1f74ef2..91748a6 100644 --- a/src/types/sync.ts +++ b/src/types/sync.ts @@ -1,4 +1,4 @@ -import type { SyncTask } from './requests' +import { Task } from './entities' export type Command = { type: string @@ -20,6 +20,6 @@ export type SyncRequest = { } export type SyncResponse = { - items?: SyncTask[] + items?: Task[] sync_status?: Record } diff --git a/src/utils/projectConverter.ts b/src/utils/projectConverter.ts deleted file mode 100644 index 2b279e1..0000000 --- a/src/utils/projectConverter.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Project, RawProject } from '../types' - -const showProjectEndpoint = 'https://todoist.com/showProject' - -function getProjectUrlFromProjectId(projectId: string) { - return `${showProjectEndpoint}?id=${projectId}` -} -export function getProjectFromRawProjectResponse(responseData: RawProject): Project { - const project = { - id: responseData.id, - parentId: responseData.parentId ?? null, // workspace projects do not have a parent - order: responseData.childOrder, - color: responseData.color, - name: responseData.name, - isShared: responseData.isShared, - isFavorite: responseData.isFavorite, - isInboxProject: responseData.inboxProject ?? false, // workspace projects do not set this flag - isTeamInbox: false, // this flag is no longer used - url: getProjectUrlFromProjectId(responseData.id), - viewStyle: responseData.viewStyle, - } - return project -} diff --git a/src/utils/taskConverters.test.ts b/src/utils/taskConverters.test.ts deleted file mode 100644 index dd2b45c..0000000 --- a/src/utils/taskConverters.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { getTaskFromQuickAddResponse } from './taskConverters' -import { DEFAULT_QUICK_ADD_RESPONSE, DEFAULT_TASK } from '../testUtils/testDefaults' - -describe('getTaskFromQuickAddResponse', () => { - test('maps sync data to expected task properties', () => { - const task = getTaskFromQuickAddResponse(DEFAULT_QUICK_ADD_RESPONSE) - expect(task).toEqual({ ...DEFAULT_TASK, labels: ['personal', 'work', 'hobby'] }) - }) - - const completedTheories = [ - [false, false], - [true, true], - ] as const - - test.each(completedTheories)( - 'checked number value: %p converted to completed boolean value: %p', - (checked, completedBoolean) => { - const quickAddResponse = { - ...DEFAULT_QUICK_ADD_RESPONSE, - checked, - } - - const task = getTaskFromQuickAddResponse(quickAddResponse) - expect(task.isCompleted).toEqual(completedBoolean) - }, - ) - - const taskUrlTheories = [ - ['1234', 'https://todoist.com/showTask?id=1234'], - ['1234', 'https://todoist.com/showTask?id=1234'], - ] as const - - test.each(taskUrlTheories)('with id %p and syncId %p returns url %p', (id, url) => { - const quickAddResponse = { - ...DEFAULT_QUICK_ADD_RESPONSE, - id, - } - const task = getTaskFromQuickAddResponse(quickAddResponse) - expect(task.url).toEqual(url) - }) -}) diff --git a/src/utils/taskConverters.ts b/src/utils/taskConverters.ts deleted file mode 100644 index 8f7061d..0000000 --- a/src/utils/taskConverters.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { QuickAddTaskResponse, RawTask, Task } from '../types' - -const showTaskEndpoint = 'https://todoist.com/showTask' - -function getTaskUrlFromTaskId(taskId: string) { - return `${showTaskEndpoint}?id=${taskId}` -} - -export function getTaskFromQuickAddResponse(responseData: QuickAddTaskResponse): Task { - const task = { - id: responseData.id, - order: responseData.childOrder, - content: responseData.content, - description: responseData.description, - projectId: responseData.projectId, - sectionId: responseData.sectionId, - isCompleted: responseData.checked, - labels: responseData.labels, - priority: responseData.priority, - createdAt: responseData.addedAt, - url: getTaskUrlFromTaskId(responseData.id), - creatorId: responseData.addedByUid ?? '', - parentId: responseData.parentId, - duration: responseData.duration, - assignerId: responseData.assignedByUid, - assigneeId: responseData.responsibleUid, - deadline: responseData.deadline, - due: responseData.due, - } - - return task -} - -export function getTaskFromRawTaskResponse(responseData: RawTask): Task { - const task = { - id: responseData.id, - assignerId: responseData.assignedByUid, - assigneeId: responseData.responsibleUid, - projectId: responseData.projectId, - sectionId: responseData.sectionId, - parentId: responseData.parentId, - order: responseData.childOrder, - content: responseData.content, - description: responseData.description, - isCompleted: responseData.checked, - labels: responseData.labels, - priority: responseData.priority, - creatorId: responseData.addedByUid, - createdAt: responseData.addedAt, - due: responseData.due, - url: getTaskUrlFromTaskId(responseData.id), - duration: responseData.duration, - deadline: responseData.deadline, - } - return task -} diff --git a/src/utils/validators.ts b/src/utils/validators.ts index f02e3e4..b099f6b 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -9,20 +9,13 @@ import { type Label, type Comment, type User, - RawTaskSchema, - RawProjectSchema, ProjectSchema, TaskSchema, + type WorkspaceProject, + PersonalProject, } from '../types/entities' -import { getProjectFromRawProjectResponse } from './projectConverter' -import { getTaskFromRawTaskResponse } from './taskConverters' export function validateTask(input: unknown): Task { - const rawTaskParse = RawTaskSchema.safeParse(input) - if (rawTaskParse.success) { - const task = getTaskFromRawTaskResponse(rawTaskParse.data) - return task - } return TaskSchema.parse(input) } @@ -30,12 +23,25 @@ export function validateTaskArray(input: unknown[]): Task[] { return input.map(validateTask) } +/** + * Type guard to check if a project is a workspace project. + * @param project The project to check + * @returns True if the project is a workspace project + */ +export function isWorkspaceProject(project: Project): project is WorkspaceProject { + return 'workspaceId' in project +} + +/** + * Type guard to check if a project is a personal project. + * @param project The project to check + * @returns True if the project is a personal project + */ +export function isPersonalProject(project: Project): project is PersonalProject { + return !isWorkspaceProject(project) +} + export function validateProject(input: unknown): Project { - const rawProjectParse = RawProjectSchema.safeParse(input) - if (rawProjectParse.success) { - const project = getProjectFromRawProjectResponse(rawProjectParse.data) - return project - } return ProjectSchema.parse(input) } From c0280f0b1a2d5d15dbe40aeaf2ffe446280c8a9c Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Tue, 15 Apr 2025 10:34:54 +0200 Subject: [PATCH 2/5] chore: add 'url' field back to Task and Project schemas --- src/TodoistApi.projects.test.ts | 13 +++++- src/TodoistApi.tasks.test.ts | 12 +++++- src/consts/endpoints.ts | 1 + src/testUtils/testDefaults.ts | 24 +++++++---- src/types/entities.ts | 70 +++++++++++++++++++++------------ src/utils/urlHelpers.ts | 50 +++++++++++++++++++++++ src/utils/validators.test.ts | 2 +- src/utils/validators.ts | 2 +- 8 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 src/utils/urlHelpers.ts diff --git a/src/TodoistApi.projects.test.ts b/src/TodoistApi.projects.test.ts index 112aff5..72c3d8f 100644 --- a/src/TodoistApi.projects.test.ts +++ b/src/TodoistApi.projects.test.ts @@ -5,6 +5,7 @@ import { DEFAULT_REQUEST_ID, DEFAULT_USER, PROJECT_WITH_OPTIONALS_AS_NULL, + DEFAULT_PROJECT_ID, } from './testUtils/testDefaults' import { getSyncBaseUri, @@ -12,6 +13,7 @@ import { ENDPOINT_REST_PROJECT_COLLABORATORS, } from './consts/endpoints' import { setupRestClientMock } from './testUtils/mocks' +import { getProjectUrl } from './utils/urlHelpers' function getTarget() { return new TodoistApi(DEFAULT_AUTH_TOKEN) @@ -112,6 +114,11 @@ describe('TodoistApi project endpoints', () => { describe('updateProject', () => { const DEFAULT_UPDATE_PROJECT_ARGS = { name: 'a name' } + const DEFAULT_UPDATED_PROJECT_URL = getProjectUrl( + DEFAULT_PROJECT_ID, + DEFAULT_UPDATE_PROJECT_ARGS.name, + ) + test('calls post on restClient with expected parameters', async () => { const projectId = '123' const updateArgs = { name: 'a new name' } @@ -132,7 +139,11 @@ describe('TodoistApi project endpoints', () => { }) test('returns success result from rest client', async () => { - const returnedProject = { ...DEFAULT_PROJECT, ...DEFAULT_UPDATE_PROJECT_ARGS } + const returnedProject = { + ...DEFAULT_PROJECT, + ...DEFAULT_UPDATE_PROJECT_ARGS, + url: DEFAULT_UPDATED_PROJECT_URL, + } setupRestClientMock(returnedProject, 204) const api = getTarget() diff --git a/src/TodoistApi.tasks.test.ts b/src/TodoistApi.tasks.test.ts index df208a2..328645f 100644 --- a/src/TodoistApi.tasks.test.ts +++ b/src/TodoistApi.tasks.test.ts @@ -4,6 +4,7 @@ import { DEFAULT_REQUEST_ID, DEFAULT_TASK, TASK_WITH_OPTIONALS_AS_NULL, + DEFAULT_TASK_ID, } from './testUtils/testDefaults' import { getSyncBaseUri, @@ -14,6 +15,7 @@ import { ENDPOINT_SYNC_QUICK_ADD, } from './consts/endpoints' import { setupRestClientMock } from './testUtils/mocks' +import { getTaskUrl } from './utils/urlHelpers' function getTarget(baseUrl = 'https://api.todoist.com') { return new TodoistApi(DEFAULT_AUTH_TOKEN, baseUrl) @@ -71,6 +73,10 @@ describe('TodoistApi task endpoints', () => { describe('updateTask', () => { const DEFAULT_UPDATE_TASK_ARGS = { content: 'some new content' } + const DEFAULT_UPDATED_TASK_URL = getTaskUrl( + DEFAULT_TASK_ID, + DEFAULT_UPDATE_TASK_ARGS.content, + ) test('calls post on restClient with expected parameters', async () => { const taskId = '123' @@ -91,7 +97,11 @@ describe('TodoistApi task endpoints', () => { }) test('returns success result from rest client', async () => { - const returnedTask = { ...DEFAULT_TASK, ...DEFAULT_UPDATE_TASK_ARGS } + const returnedTask = { + ...DEFAULT_TASK, + ...DEFAULT_UPDATE_TASK_ARGS, + url: DEFAULT_UPDATED_TASK_URL, + } setupRestClientMock(returnedTask, 204) const api = getTarget() diff --git a/src/consts/endpoints.ts b/src/consts/endpoints.ts index 522d871..873e471 100644 --- a/src/consts/endpoints.ts +++ b/src/consts/endpoints.ts @@ -1,5 +1,6 @@ const BASE_URI = 'https://api.todoist.com' const TODOIST_URI = 'https://todoist.com' +export const TODOIST_WEB_URI = 'https://app/todoist.com/app' // The API version is not configurable, to ensure // compatibility between the API and the client. diff --git a/src/testUtils/testDefaults.ts b/src/testUtils/testDefaults.ts index 80bcf96..3ef3fe2 100644 --- a/src/testUtils/testDefaults.ts +++ b/src/testUtils/testDefaults.ts @@ -9,15 +9,16 @@ import { Deadline, RawComment, } from '../types' +import { getProjectUrl, getTaskUrl } from '../utils/urlHelpers' -const DEFAULT_TASK_ID = '1234' -const DEFAULT_TASK_CONTENT = 'This is a task' -const DEFAULT_TASK_DESCRIPTION = 'A description' -const DEFAULT_TASK_PRIORITY = 1 -const DEFAULT_ORDER = 3 -const DEFAULT_PROJECT_ID = '123' -const DEFAULT_PROJECT_NAME = 'This is a project' -const DEFAULT_PROJECT_VIEW_STYLE = 'list' +export const DEFAULT_TASK_ID = '1234' +export const DEFAULT_TASK_CONTENT = 'This is a task' +export const DEFAULT_TASK_DESCRIPTION = 'A description' +export const DEFAULT_TASK_PRIORITY = 1 +export const DEFAULT_ORDER = 3 +export const DEFAULT_PROJECT_ID = '123' +export const DEFAULT_PROJECT_NAME = 'This is a project' +export const DEFAULT_PROJECT_VIEW_STYLE = 'list' const DEFAULT_LABEL_ID = '456' const DEFAULT_LABEL_NAME = 'This is a label' const DEFAULT_SECTION_ID = '456' @@ -41,6 +42,10 @@ const DEFAULT_IS_DELETED = false const DEFAULT_IS_FROZEN = false const DEFAULT_IS_COLLAPSED = false +// URL constants using the helper functions +const DEFAULT_TASK_URL = getTaskUrl(DEFAULT_TASK_ID, DEFAULT_TASK_CONTENT) +const DEFAULT_PROJECT_URL = getProjectUrl(DEFAULT_PROJECT_ID, DEFAULT_PROJECT_NAME) + export const DEFAULT_AUTH_TOKEN = 'AToken' export const DEFAULT_REQUEST_ID = 'ARequestID' @@ -89,6 +94,7 @@ export const DEFAULT_TASK: Task = { noteCount: DEFAULT_NOTE_COUNT, dayOrder: DEFAULT_ORDER, isCollapsed: DEFAULT_IS_COLLAPSED, + url: DEFAULT_TASK_URL, } export const INVALID_TASK = { @@ -121,6 +127,7 @@ export const TASK_WITH_OPTIONALS_AS_NULL: Task = { dayOrder: DEFAULT_ORDER, isCollapsed: DEFAULT_IS_COLLAPSED, noteCount: DEFAULT_NOTE_COUNT, + url: DEFAULT_TASK_URL, } export const DEFAULT_PROJECT: Project = { @@ -142,6 +149,7 @@ export const DEFAULT_PROJECT: Project = { defaultOrder: DEFAULT_ORDER, description: '', isCollapsed: DEFAULT_IS_COLLAPSED, + url: DEFAULT_PROJECT_URL, } export const INVALID_PROJECT = { diff --git a/src/types/entities.ts b/src/types/entities.ts index fd37d38..efd3620 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -1,4 +1,5 @@ import { z } from 'zod' +import { getProjectUrl, getTaskUrl } from '../utils/urlHelpers' export const DueDateSchema = z .object({ @@ -36,32 +37,39 @@ export const DeadlineSchema = z.object({ */ export interface Deadline extends z.infer {} -export const TaskSchema = z.object({ - id: z.string(), - userId: z.string(), - projectId: z.string(), - sectionId: z.string().nullable(), - parentId: z.string().nullable(), - addedByUid: z.string().nullable(), - assignedByUid: z.string().nullable(), - responsibleUid: z.string().nullable(), - labels: z.array(z.string()), - deadline: DeadlineSchema.nullable(), - duration: DurationSchema.nullable(), - checked: z.boolean(), - isDeleted: z.boolean(), - addedAt: z.string().nullable(), - completedAt: z.string().nullable(), - updatedAt: z.string().nullable(), - due: DueDateSchema.nullable(), - priority: z.number().int(), - childOrder: z.number().int(), - content: z.string(), - description: z.string(), - noteCount: z.number().int(), - dayOrder: z.number().int(), - isCollapsed: z.boolean(), -}) +export const TaskSchema = z + .object({ + id: z.string(), + userId: z.string(), + projectId: z.string(), + sectionId: z.string().nullable(), + parentId: z.string().nullable(), + addedByUid: z.string().nullable(), + assignedByUid: z.string().nullable(), + responsibleUid: z.string().nullable(), + labels: z.array(z.string()), + deadline: DeadlineSchema.nullable(), + duration: DurationSchema.nullable(), + checked: z.boolean(), + isDeleted: z.boolean(), + addedAt: z.string().nullable(), + completedAt: z.string().nullable(), + updatedAt: z.string().nullable(), + due: DueDateSchema.nullable(), + priority: z.number().int(), + childOrder: z.number().int(), + content: z.string(), + description: z.string(), + noteCount: z.number().int(), + dayOrder: z.number().int(), + isCollapsed: z.boolean(), + }) + .transform((data) => { + return { + ...data, + url: getTaskUrl(data.id, data.content), + } + }) /** * Represents a task in Todoist. * @see https://todoist.com/api/v1/docs#tag/Tasks @@ -97,6 +105,11 @@ export const BaseProjectSchema = z.object({ export const PersonalProjectSchema = BaseProjectSchema.extend({ parentId: z.string().nullable(), inboxProject: z.boolean(), +}).transform((data) => { + return { + ...data, + url: getProjectUrl(data.id, data.name), + } }) /** @@ -110,6 +123,11 @@ export const WorkspaceProjectSchema = BaseProjectSchema.extend({ role: z.string().nullable(), status: z.string(), workspaceId: z.string(), +}).transform((data) => { + return { + ...data, + url: getProjectUrl(data.id, data.name), + } }) /** diff --git a/src/utils/urlHelpers.ts b/src/utils/urlHelpers.ts new file mode 100644 index 0000000..a3f347a --- /dev/null +++ b/src/utils/urlHelpers.ts @@ -0,0 +1,50 @@ +import { TODOIST_WEB_URI } from '../consts/endpoints' + +/** + * Generate the URL for a given task. + * + * @param taskId The ID of the task. + * @param content The content of the task. + * @returns The URL string for the task view. + */ +export function getTaskUrl(taskId: string, content?: string): string { + const slug = content ? slugify(content) : undefined + const path = slug ? `${slug}-${taskId}` : taskId + return `${TODOIST_WEB_URI}/task/${path}` +} + +/** + * Generate the URL for a given project. + * + * @param projectId The ID of the project. + * @param name The name of the project. + * @returns The URL string for the project view. + */ +export function getProjectUrl(projectId: string, name?: string): string { + const slug = name ? slugify(name) : undefined + const path = slug ? `${slug}-${projectId}` : projectId + return `${TODOIST_WEB_URI}/project/${path}` +} + +/** + * Slugify function borrowed from Django. + * + * @param value The string to slugify. + * @returns The slugified string. + */ +export function slugify(value: string): string { + // Convert to ASCII + let result = value.normalize('NFKD').replace(/[\u0300-\u036f]/g, '') + + // Remove non-ASCII characters + result = result.replace(/[^\x20-\x7E]/g, '') + + // Convert to lowercase and replace non-alphanumeric characters with dashes + result = result.toLowerCase().replace(/[^\w\s-]/g, '') + + // Replace spaces and repeated dashes with single dashes + result = result.replace(/[-\s]+/g, '-') + + // Strip dashes from the beginning and end + return result.replace(/^-+|-+$/g, '') +} diff --git a/src/utils/validators.test.ts b/src/utils/validators.test.ts index 6f9c96b..36e9b6e 100644 --- a/src/utils/validators.test.ts +++ b/src/utils/validators.test.ts @@ -204,7 +204,7 @@ describe('validators', () => { expect(result).toEqual([]) }) - test('validation passes for valid comment user', () => { + test('validation passes for valid user array', () => { const result = validateUserArray([DEFAULT_USER]) expect(result).toEqual([DEFAULT_USER]) }) diff --git a/src/utils/validators.ts b/src/utils/validators.ts index b099f6b..33caecd 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -12,7 +12,7 @@ import { ProjectSchema, TaskSchema, type WorkspaceProject, - PersonalProject, + type PersonalProject, } from '../types/entities' export function validateTask(input: unknown): Task { From 05ad9ed9a011e97689fdf54e3fd5d45ac2ad8f40 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Tue, 15 Apr 2025 10:49:03 +0200 Subject: [PATCH 3/5] chore: update docs --- website/docs/api/.md | 15 +- website/docs/api/classes/TodoistApi.md | 294 +++++++++++++++++- .../docs/api/interfaces/PersonalProject.md | 35 +++ website/docs/api/interfaces/Project.md | 27 -- website/docs/api/interfaces/Task.md | 19 +- .../docs/api/interfaces/WorkspaceProject.md | 40 +++ .../api/type-aliases/GetProjectsResponse.md | 2 +- website/docs/api/type-aliases/Project.md | 54 ++++ .../api/type-aliases/QuickAddTaskResponse.md | 11 - website/docs/api/type-aliases/SyncTask.md | 51 --- website/docs/api/typedoc-sidebar.cjs | 2 +- .../docs/api/variables/BaseProjectSchema.md | 59 ++++ .../api/variables/PersonalProjectSchema.md | 104 +++++++ website/docs/api/variables/ProjectSchema.md | 7 + .../api/variables/WorkspaceProjectSchema.md | 129 ++++++++ 15 files changed, 743 insertions(+), 106 deletions(-) create mode 100644 website/docs/api/interfaces/PersonalProject.md delete mode 100644 website/docs/api/interfaces/Project.md create mode 100644 website/docs/api/interfaces/WorkspaceProject.md create mode 100644 website/docs/api/type-aliases/Project.md delete mode 100644 website/docs/api/type-aliases/QuickAddTaskResponse.md delete mode 100644 website/docs/api/type-aliases/SyncTask.md create mode 100644 website/docs/api/variables/BaseProjectSchema.md create mode 100644 website/docs/api/variables/PersonalProjectSchema.md create mode 100644 website/docs/api/variables/ProjectSchema.md create mode 100644 website/docs/api/variables/WorkspaceProjectSchema.md diff --git a/website/docs/api/.md b/website/docs/api/.md index a934052..724c1f8 100644 --- a/website/docs/api/.md +++ b/website/docs/api/.md @@ -18,11 +18,12 @@ | [DueDate](interfaces/DueDate.md) | Represents a due date for a task. | | [Duration](interfaces/Duration.md) | Represents a duration for a task deadline. | | [Label](interfaces/Label.md) | Represents a label in Todoist. | -| [Project](interfaces/Project.md) | Represents a project in Todoist. | +| [PersonalProject](interfaces/PersonalProject.md) | Represents a personal project in Todoist. | | [RawComment](interfaces/RawComment.md) | Represents a raw comment response from the API. | | [Section](interfaces/Section.md) | Represents a section in a Todoist project. | | [Task](interfaces/Task.md) | Represents a task in Todoist. | | [User](interfaces/User.md) | Represents a user in Todoist. | +| [WorkspaceProject](interfaces/WorkspaceProject.md) | Represents a workspace project in Todoist. | ## Type Aliases @@ -54,13 +55,12 @@ | [GetTasksResponse](type-aliases/GetTasksResponse.md) | - | | [MoveTaskArgs](type-aliases/MoveTaskArgs.md) | Arguments for moving a task. | | [Permission](type-aliases/Permission.md) | Permission scopes that can be requested during OAuth2 authorization. | +| [Project](type-aliases/Project.md) | Represents either a personal project or a workspace project in Todoist. | | [ProjectViewStyle](type-aliases/ProjectViewStyle.md) | - | | [QuickAddTaskArgs](type-aliases/QuickAddTaskArgs.md) | Arguments for quick adding a task. | -| [QuickAddTaskResponse](type-aliases/QuickAddTaskResponse.md) | Response from quick adding a task. | | [RemoveSharedLabelArgs](type-aliases/RemoveSharedLabelArgs.md) | Arguments for removing a shared label. | | [RenameSharedLabelArgs](type-aliases/RenameSharedLabelArgs.md) | Arguments for renaming a shared label. | | [RevokeAuthTokenRequestArgs](type-aliases/RevokeAuthTokenRequestArgs.md) | Parameters required to revoke an access token. | -| [SyncTask](type-aliases/SyncTask.md) | Response from quick adding a task. | | [TaskWithSanitizedContent](type-aliases/TaskWithSanitizedContent.md) | - | | [UpdateCommentArgs](type-aliases/UpdateCommentArgs.md) | Arguments for updating a comment. | | [UpdateLabelArgs](type-aliases/UpdateLabelArgs.md) | Arguments for updating a label. | @@ -68,6 +68,15 @@ | [UpdateSectionArgs](type-aliases/UpdateSectionArgs.md) | Arguments for updating a section. | | [UpdateTaskArgs](type-aliases/UpdateTaskArgs.md) | Arguments for updating a task. | +## Variables + +| Variable | Description | +| ------ | ------ | +| [BaseProjectSchema](variables/BaseProjectSchema.md) | Base schema for all project types in Todoist. Contains common fields shared between personal and workspace projects. | +| [PersonalProjectSchema](variables/PersonalProjectSchema.md) | Schema for personal projects in Todoist. | +| [ProjectSchema](variables/ProjectSchema.md) | Schema for validating either a personal project or a workspace project. | +| [WorkspaceProjectSchema](variables/WorkspaceProjectSchema.md) | Schema for workspace projects in Todoist. | + ## Functions | Function | Description | diff --git a/website/docs/api/classes/TodoistApi.md b/website/docs/api/classes/TodoistApi.md index 0b38173..9c23752 100644 --- a/website/docs/api/classes/TodoistApi.md +++ b/website/docs/api/classes/TodoistApi.md @@ -91,7 +91,54 @@ A promise that resolves to the created label. ### addProject() ```ts -addProject(args: AddProjectArgs, requestId?: string): Promise +addProject(args: AddProjectArgs, requestId?: string): Promise< + | { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + url: string; + viewStyle: string; + } + | { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + url: string; + viewStyle: string; + workspaceId: string; +}> ``` Creates a new project with the provided parameters. @@ -105,7 +152,54 @@ Creates a new project with the provided parameters. #### Returns -`Promise`\<[`Project`](../interfaces/Project.md)\> +`Promise`\< + \| \{ + `canAssignTasks`: `boolean`; + `childOrder`: `number`; + `color`: `string`; + `createdAt`: `null` \| `string`; + `defaultOrder`: `number`; + `description`: `string`; + `id`: `string`; + `inboxProject`: `boolean`; + `isArchived`: `boolean`; + `isCollapsed`: `boolean`; + `isDeleted`: `boolean`; + `isFavorite`: `boolean`; + `isFrozen`: `boolean`; + `isShared`: `boolean`; + `name`: `string`; + `parentId`: `null` \| `string`; + `updatedAt`: `null` \| `string`; + `url`: `string`; + `viewStyle`: `string`; + \} + \| \{ + `canAssignTasks`: `boolean`; + `childOrder`: `number`; + `collaboratorRoleDefault`: `string`; + `color`: `string`; + `createdAt`: `null` \| `string`; + `defaultOrder`: `number`; + `description`: `string`; + `folderId`: `null` \| `boolean`; + `id`: `string`; + `isArchived`: `boolean`; + `isCollapsed`: `boolean`; + `isDeleted`: `boolean`; + `isFavorite`: `boolean`; + `isFrozen`: `boolean`; + `isInviteOnly`: `null` \| `boolean`; + `isLinkSharingEnabled`: `boolean`; + `isShared`: `boolean`; + `name`: `string`; + `role`: `null` \| `string`; + `status`: `string`; + `updatedAt`: `null` \| `string`; + `url`: `string`; + `viewStyle`: `string`; + `workspaceId`: `string`; + \}\> A promise that resolves to the created project. @@ -388,7 +482,54 @@ A promise that resolves to an array of labels. ### getProject() ```ts -getProject(id: string): Promise +getProject(id: string): Promise< + | { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + url: string; + viewStyle: string; + } + | { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + url: string; + viewStyle: string; + workspaceId: string; +}> ``` Retrieves a project by its ID. @@ -401,7 +542,54 @@ Retrieves a project by its ID. #### Returns -`Promise`\<[`Project`](../interfaces/Project.md)\> +`Promise`\< + \| \{ + `canAssignTasks`: `boolean`; + `childOrder`: `number`; + `color`: `string`; + `createdAt`: `null` \| `string`; + `defaultOrder`: `number`; + `description`: `string`; + `id`: `string`; + `inboxProject`: `boolean`; + `isArchived`: `boolean`; + `isCollapsed`: `boolean`; + `isDeleted`: `boolean`; + `isFavorite`: `boolean`; + `isFrozen`: `boolean`; + `isShared`: `boolean`; + `name`: `string`; + `parentId`: `null` \| `string`; + `updatedAt`: `null` \| `string`; + `url`: `string`; + `viewStyle`: `string`; + \} + \| \{ + `canAssignTasks`: `boolean`; + `childOrder`: `number`; + `collaboratorRoleDefault`: `string`; + `color`: `string`; + `createdAt`: `null` \| `string`; + `defaultOrder`: `number`; + `description`: `string`; + `folderId`: `null` \| `boolean`; + `id`: `string`; + `isArchived`: `boolean`; + `isCollapsed`: `boolean`; + `isDeleted`: `boolean`; + `isFavorite`: `boolean`; + `isFrozen`: `boolean`; + `isInviteOnly`: `null` \| `boolean`; + `isLinkSharingEnabled`: `boolean`; + `isShared`: `boolean`; + `name`: `string`; + `role`: `null` \| `string`; + `status`: `string`; + `updatedAt`: `null` \| `string`; + `url`: `string`; + `viewStyle`: `string`; + `workspaceId`: `string`; + \}\> A promise that resolves to the requested project. @@ -760,7 +948,54 @@ A promise that resolves to the updated label. updateProject( id: string, args: UpdateProjectArgs, -requestId?: string): Promise + requestId?: string): Promise< + | { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + url: string; + viewStyle: string; + } + | { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + url: string; + viewStyle: string; + workspaceId: string; +}> ``` Updates an existing project by its ID with the provided parameters. @@ -775,7 +1010,54 @@ Updates an existing project by its ID with the provided parameters. #### Returns -`Promise`\<[`Project`](../interfaces/Project.md)\> +`Promise`\< + \| \{ + `canAssignTasks`: `boolean`; + `childOrder`: `number`; + `color`: `string`; + `createdAt`: `null` \| `string`; + `defaultOrder`: `number`; + `description`: `string`; + `id`: `string`; + `inboxProject`: `boolean`; + `isArchived`: `boolean`; + `isCollapsed`: `boolean`; + `isDeleted`: `boolean`; + `isFavorite`: `boolean`; + `isFrozen`: `boolean`; + `isShared`: `boolean`; + `name`: `string`; + `parentId`: `null` \| `string`; + `updatedAt`: `null` \| `string`; + `url`: `string`; + `viewStyle`: `string`; + \} + \| \{ + `canAssignTasks`: `boolean`; + `childOrder`: `number`; + `collaboratorRoleDefault`: `string`; + `color`: `string`; + `createdAt`: `null` \| `string`; + `defaultOrder`: `number`; + `description`: `string`; + `folderId`: `null` \| `boolean`; + `id`: `string`; + `isArchived`: `boolean`; + `isCollapsed`: `boolean`; + `isDeleted`: `boolean`; + `isFavorite`: `boolean`; + `isFrozen`: `boolean`; + `isInviteOnly`: `null` \| `boolean`; + `isLinkSharingEnabled`: `boolean`; + `isShared`: `boolean`; + `name`: `string`; + `role`: `null` \| `string`; + `status`: `string`; + `updatedAt`: `null` \| `string`; + `url`: `string`; + `viewStyle`: `string`; + `workspaceId`: `string`; + \}\> A promise that resolves to the updated project. diff --git a/website/docs/api/interfaces/PersonalProject.md b/website/docs/api/interfaces/PersonalProject.md new file mode 100644 index 0000000..b92f46a --- /dev/null +++ b/website/docs/api/interfaces/PersonalProject.md @@ -0,0 +1,35 @@ +# PersonalProject + +Represents a personal project in Todoist. + +## See + +https://todoist.com/api/v1/docs#tag/Projects + +## Extends + +- `TypeOf`\<*typeof* [`PersonalProjectSchema`](../variables/PersonalProjectSchema.md)\> + +## Properties + +| Property | Type | +| ------ | ------ | +| `canAssignTasks` | `boolean` | +| `childOrder` | `number` | +| `color` | `string` | +| `createdAt` | `null` \| `string` | +| `defaultOrder` | `number` | +| `description` | `string` | +| `id` | `string` | +| `inboxProject` | `boolean` | +| `isArchived` | `boolean` | +| `isCollapsed` | `boolean` | +| `isDeleted` | `boolean` | +| `isFavorite` | `boolean` | +| `isFrozen` | `boolean` | +| `isShared` | `boolean` | +| `name` | `string` | +| `parentId` | `null` \| `string` | +| `updatedAt` | `null` \| `string` | +| `url` | `string` | +| `viewStyle` | `string` | diff --git a/website/docs/api/interfaces/Project.md b/website/docs/api/interfaces/Project.md deleted file mode 100644 index 6495c3d..0000000 --- a/website/docs/api/interfaces/Project.md +++ /dev/null @@ -1,27 +0,0 @@ -# Project - -Represents a project in Todoist. - -## See - -https://todoist.com/api/v1/docs#tag/Projects - -## Extends - -- `TypeOf`\<*typeof* `ProjectSchema`\> - -## Properties - -| Property | Type | -| ------ | ------ | -| `color` | `string` | -| `id` | `string` | -| `isFavorite` | `boolean` | -| `isInboxProject` | `boolean` | -| `isShared` | `boolean` | -| `isTeamInbox` | `boolean` | -| `name` | `string` | -| `order` | `null` \| `number` | -| `parentId` | `null` \| `string` | -| `url` | `string` | -| `viewStyle` | `string` | diff --git a/website/docs/api/interfaces/Task.md b/website/docs/api/interfaces/Task.md index cfa3759..e6d16e2 100644 --- a/website/docs/api/interfaces/Task.md +++ b/website/docs/api/interfaces/Task.md @@ -14,21 +14,28 @@ https://todoist.com/api/v1/docs#tag/Tasks | Property | Type | | ------ | ------ | -| `assigneeId` | `null` \| `string` | -| `assignerId` | `null` \| `string` | +| `addedAt` | `null` \| `string` | +| `addedByUid` | `null` \| `string` | +| `assignedByUid` | `null` \| `string` | +| `checked` | `boolean` | +| `childOrder` | `number` | +| `completedAt` | `null` \| `string` | | `content` | `string` | -| `createdAt` | `string` | -| `creatorId` | `string` | +| `dayOrder` | `number` | | `deadline` | \| `null` \| \{ `date`: `string`; `lang`: `string`; \} | | `description` | `string` | | `due` | \| `null` \| \{ `date`: `string`; `datetime`: `null` \| `string`; `isRecurring`: `boolean`; `lang`: `null` \| `string`; `string`: `string`; `timezone`: `null` \| `string`; \} | | `duration` | \| `null` \| \{ `amount`: `number`; `unit`: `"minute"` \| `"day"`; \} | | `id` | `string` | -| `isCompleted` | `boolean` | +| `isCollapsed` | `boolean` | +| `isDeleted` | `boolean` | | `labels` | `string`[] | -| `order` | `number` | +| `noteCount` | `number` | | `parentId` | `null` \| `string` | | `priority` | `number` | | `projectId` | `string` | +| `responsibleUid` | `null` \| `string` | | `sectionId` | `null` \| `string` | +| `updatedAt` | `null` \| `string` | | `url` | `string` | +| `userId` | `string` | diff --git a/website/docs/api/interfaces/WorkspaceProject.md b/website/docs/api/interfaces/WorkspaceProject.md new file mode 100644 index 0000000..c27702d --- /dev/null +++ b/website/docs/api/interfaces/WorkspaceProject.md @@ -0,0 +1,40 @@ +# WorkspaceProject + +Represents a workspace project in Todoist. + +## See + +https://todoist.com/api/v1/docs#tag/Projects + +## Extends + +- `TypeOf`\<*typeof* [`WorkspaceProjectSchema`](../variables/WorkspaceProjectSchema.md)\> + +## Properties + +| Property | Type | +| ------ | ------ | +| `canAssignTasks` | `boolean` | +| `childOrder` | `number` | +| `collaboratorRoleDefault` | `string` | +| `color` | `string` | +| `createdAt` | `null` \| `string` | +| `defaultOrder` | `number` | +| `description` | `string` | +| `folderId` | `null` \| `boolean` | +| `id` | `string` | +| `isArchived` | `boolean` | +| `isCollapsed` | `boolean` | +| `isDeleted` | `boolean` | +| `isFavorite` | `boolean` | +| `isFrozen` | `boolean` | +| `isInviteOnly` | `null` \| `boolean` | +| `isLinkSharingEnabled` | `boolean` | +| `isShared` | `boolean` | +| `name` | `string` | +| `role` | `null` \| `string` | +| `status` | `string` | +| `updatedAt` | `null` \| `string` | +| `url` | `string` | +| `viewStyle` | `string` | +| `workspaceId` | `string` | diff --git a/website/docs/api/type-aliases/GetProjectsResponse.md b/website/docs/api/type-aliases/GetProjectsResponse.md index d3961cb..1af4a75 100644 --- a/website/docs/api/type-aliases/GetProjectsResponse.md +++ b/website/docs/api/type-aliases/GetProjectsResponse.md @@ -14,7 +14,7 @@ Response from retrieving projects. | Name | Type | | ------ | ------ | | `nextCursor` | `string` \| `null` | -| `results` | [`Project`](../interfaces/Project.md)[] | +| `results` | [`Project`](Project.md)[] | ## See diff --git a/website/docs/api/type-aliases/Project.md b/website/docs/api/type-aliases/Project.md new file mode 100644 index 0000000..a683c54 --- /dev/null +++ b/website/docs/api/type-aliases/Project.md @@ -0,0 +1,54 @@ +# Project + +```ts +type Project = + | { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + url: string; + viewStyle: string; + } + | { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + url: string; + viewStyle: string; + workspaceId: string; +}; +``` + +Represents either a personal project or a workspace project in Todoist. diff --git a/website/docs/api/type-aliases/QuickAddTaskResponse.md b/website/docs/api/type-aliases/QuickAddTaskResponse.md deleted file mode 100644 index 531145a..0000000 --- a/website/docs/api/type-aliases/QuickAddTaskResponse.md +++ /dev/null @@ -1,11 +0,0 @@ -# QuickAddTaskResponse - -```ts -type QuickAddTaskResponse = SyncTask; -``` - -Response from quick adding a task. - -## See - -https://todoist.com/api/v1/docs#tag/Tasks/operation/quick_add_api_v1_tasks_quick_post diff --git a/website/docs/api/type-aliases/SyncTask.md b/website/docs/api/type-aliases/SyncTask.md deleted file mode 100644 index 50b2868..0000000 --- a/website/docs/api/type-aliases/SyncTask.md +++ /dev/null @@ -1,51 +0,0 @@ -# SyncTask - -```ts -type SyncTask = { - addedAt: string; - addedByUid: string | null; - assignedByUid: string | null; - checked: boolean; - childOrder: number; - content: string; - deadline: Deadline | null; - description: string; - due: DueDate | null; - duration: Duration | null; - id: string; - labels: string[]; - parentId: string | null; - priority: number; - projectId: string; - responsibleUid: string | null; - sectionId: string | null; -}; -``` - -Response from quick adding a task. - -## Type declaration - -| Name | Type | -| ------ | ------ | -| `addedAt` | `string` | -| `addedByUid` | `string` \| `null` | -| `assignedByUid` | `string` \| `null` | -| `checked` | `boolean` | -| `childOrder` | `number` | -| `content` | `string` | -| `deadline` | [`Deadline`](../interfaces/Deadline.md) \| `null` | -| `description` | `string` | -| `due` | [`DueDate`](../interfaces/DueDate.md) \| `null` | -| `duration` | [`Duration`](../interfaces/Duration.md) \| `null` | -| `id` | `string` | -| `labels` | `string`[] | -| `parentId` | `string` \| `null` | -| `priority` | `number` | -| `projectId` | `string` | -| `responsibleUid` | `string` \| `null` | -| `sectionId` | `string` \| `null` | - -## See - -https://todoist.com/api/v1/docs#tag/Tasks/operation/quick_add_api_v1_tasks_quick_post diff --git a/website/docs/api/typedoc-sidebar.cjs b/website/docs/api/typedoc-sidebar.cjs index 0a779d7..d9d6774 100644 --- a/website/docs/api/typedoc-sidebar.cjs +++ b/website/docs/api/typedoc-sidebar.cjs @@ -1,4 +1,4 @@ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"api/classes/TodoistApi","label":"TodoistApi"},{"type":"doc","id":"api/classes/TodoistRequestError","label":"TodoistRequestError"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"api/interfaces/Attachment","label":"Attachment"},{"type":"doc","id":"api/interfaces/Color","label":"Color"},{"type":"doc","id":"api/interfaces/Comment","label":"Comment"},{"type":"doc","id":"api/interfaces/Deadline","label":"Deadline"},{"type":"doc","id":"api/interfaces/DueDate","label":"DueDate"},{"type":"doc","id":"api/interfaces/Duration","label":"Duration"},{"type":"doc","id":"api/interfaces/Label","label":"Label"},{"type":"doc","id":"api/interfaces/Project","label":"Project"},{"type":"doc","id":"api/interfaces/RawComment","label":"RawComment"},{"type":"doc","id":"api/interfaces/Section","label":"Section"},{"type":"doc","id":"api/interfaces/Task","label":"Task"},{"type":"doc","id":"api/interfaces/User","label":"User"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"api/type-aliases/AddCommentArgs","label":"AddCommentArgs"},{"type":"doc","id":"api/type-aliases/AddLabelArgs","label":"AddLabelArgs"},{"type":"doc","id":"api/type-aliases/AddProjectArgs","label":"AddProjectArgs"},{"type":"doc","id":"api/type-aliases/AddSectionArgs","label":"AddSectionArgs"},{"type":"doc","id":"api/type-aliases/AddTaskArgs","label":"AddTaskArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenRequestArgs","label":"AuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenResponse","label":"AuthTokenResponse"},{"type":"doc","id":"api/type-aliases/GetCommentsArgs","label":"GetCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetCommentsResponse","label":"GetCommentsResponse"},{"type":"doc","id":"api/type-aliases/GetLabelsArgs","label":"GetLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetLabelsResponse","label":"GetLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsArgs","label":"GetProjectCollaboratorsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsResponse","label":"GetProjectCollaboratorsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCommentsArgs","label":"GetProjectCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsArgs","label":"GetProjectsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsResponse","label":"GetProjectsResponse"},{"type":"doc","id":"api/type-aliases/GetSectionsArgs","label":"GetSectionsArgs"},{"type":"doc","id":"api/type-aliases/GetSectionsResponse","label":"GetSectionsResponse"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsArgs","label":"GetSharedLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsResponse","label":"GetSharedLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetTaskCommentsArgs","label":"GetTaskCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetTasksArgs","label":"GetTasksArgs"},{"type":"doc","id":"api/type-aliases/GetTasksByFilterArgs","label":"GetTasksByFilterArgs"},{"type":"doc","id":"api/type-aliases/GetTasksResponse","label":"GetTasksResponse"},{"type":"doc","id":"api/type-aliases/MoveTaskArgs","label":"MoveTaskArgs"},{"type":"doc","id":"api/type-aliases/Permission","label":"Permission"},{"type":"doc","id":"api/type-aliases/ProjectViewStyle","label":"ProjectViewStyle"},{"type":"doc","id":"api/type-aliases/QuickAddTaskArgs","label":"QuickAddTaskArgs"},{"type":"doc","id":"api/type-aliases/QuickAddTaskResponse","label":"QuickAddTaskResponse"},{"type":"doc","id":"api/type-aliases/RemoveSharedLabelArgs","label":"RemoveSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RenameSharedLabelArgs","label":"RenameSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RevokeAuthTokenRequestArgs","label":"RevokeAuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/SyncTask","label":"SyncTask"},{"type":"doc","id":"api/type-aliases/TaskWithSanitizedContent","label":"TaskWithSanitizedContent"},{"type":"doc","id":"api/type-aliases/UpdateCommentArgs","label":"UpdateCommentArgs"},{"type":"doc","id":"api/type-aliases/UpdateLabelArgs","label":"UpdateLabelArgs"},{"type":"doc","id":"api/type-aliases/UpdateProjectArgs","label":"UpdateProjectArgs"},{"type":"doc","id":"api/type-aliases/UpdateSectionArgs","label":"UpdateSectionArgs"},{"type":"doc","id":"api/type-aliases/UpdateTaskArgs","label":"UpdateTaskArgs"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"api/functions/getAuthorizationUrl","label":"getAuthorizationUrl"},{"type":"doc","id":"api/functions/getAuthStateParameter","label":"getAuthStateParameter"},{"type":"doc","id":"api/functions/getAuthToken","label":"getAuthToken"},{"type":"doc","id":"api/functions/getColorByKey","label":"getColorByKey"},{"type":"doc","id":"api/functions/getSanitizedContent","label":"getSanitizedContent"},{"type":"doc","id":"api/functions/getSanitizedTasks","label":"getSanitizedTasks"},{"type":"doc","id":"api/functions/revokeAuthToken","label":"revokeAuthToken"}]}]}; +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"api/classes/TodoistApi","label":"TodoistApi"},{"type":"doc","id":"api/classes/TodoistRequestError","label":"TodoistRequestError"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"api/interfaces/Attachment","label":"Attachment"},{"type":"doc","id":"api/interfaces/Color","label":"Color"},{"type":"doc","id":"api/interfaces/Comment","label":"Comment"},{"type":"doc","id":"api/interfaces/Deadline","label":"Deadline"},{"type":"doc","id":"api/interfaces/DueDate","label":"DueDate"},{"type":"doc","id":"api/interfaces/Duration","label":"Duration"},{"type":"doc","id":"api/interfaces/Label","label":"Label"},{"type":"doc","id":"api/interfaces/PersonalProject","label":"PersonalProject"},{"type":"doc","id":"api/interfaces/RawComment","label":"RawComment"},{"type":"doc","id":"api/interfaces/Section","label":"Section"},{"type":"doc","id":"api/interfaces/Task","label":"Task"},{"type":"doc","id":"api/interfaces/User","label":"User"},{"type":"doc","id":"api/interfaces/WorkspaceProject","label":"WorkspaceProject"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"api/type-aliases/AddCommentArgs","label":"AddCommentArgs"},{"type":"doc","id":"api/type-aliases/AddLabelArgs","label":"AddLabelArgs"},{"type":"doc","id":"api/type-aliases/AddProjectArgs","label":"AddProjectArgs"},{"type":"doc","id":"api/type-aliases/AddSectionArgs","label":"AddSectionArgs"},{"type":"doc","id":"api/type-aliases/AddTaskArgs","label":"AddTaskArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenRequestArgs","label":"AuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenResponse","label":"AuthTokenResponse"},{"type":"doc","id":"api/type-aliases/GetCommentsArgs","label":"GetCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetCommentsResponse","label":"GetCommentsResponse"},{"type":"doc","id":"api/type-aliases/GetLabelsArgs","label":"GetLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetLabelsResponse","label":"GetLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsArgs","label":"GetProjectCollaboratorsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsResponse","label":"GetProjectCollaboratorsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCommentsArgs","label":"GetProjectCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsArgs","label":"GetProjectsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsResponse","label":"GetProjectsResponse"},{"type":"doc","id":"api/type-aliases/GetSectionsArgs","label":"GetSectionsArgs"},{"type":"doc","id":"api/type-aliases/GetSectionsResponse","label":"GetSectionsResponse"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsArgs","label":"GetSharedLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsResponse","label":"GetSharedLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetTaskCommentsArgs","label":"GetTaskCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetTasksArgs","label":"GetTasksArgs"},{"type":"doc","id":"api/type-aliases/GetTasksByFilterArgs","label":"GetTasksByFilterArgs"},{"type":"doc","id":"api/type-aliases/GetTasksResponse","label":"GetTasksResponse"},{"type":"doc","id":"api/type-aliases/MoveTaskArgs","label":"MoveTaskArgs"},{"type":"doc","id":"api/type-aliases/Permission","label":"Permission"},{"type":"doc","id":"api/type-aliases/Project","label":"Project"},{"type":"doc","id":"api/type-aliases/ProjectViewStyle","label":"ProjectViewStyle"},{"type":"doc","id":"api/type-aliases/QuickAddTaskArgs","label":"QuickAddTaskArgs"},{"type":"doc","id":"api/type-aliases/RemoveSharedLabelArgs","label":"RemoveSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RenameSharedLabelArgs","label":"RenameSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RevokeAuthTokenRequestArgs","label":"RevokeAuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/TaskWithSanitizedContent","label":"TaskWithSanitizedContent"},{"type":"doc","id":"api/type-aliases/UpdateCommentArgs","label":"UpdateCommentArgs"},{"type":"doc","id":"api/type-aliases/UpdateLabelArgs","label":"UpdateLabelArgs"},{"type":"doc","id":"api/type-aliases/UpdateProjectArgs","label":"UpdateProjectArgs"},{"type":"doc","id":"api/type-aliases/UpdateSectionArgs","label":"UpdateSectionArgs"},{"type":"doc","id":"api/type-aliases/UpdateTaskArgs","label":"UpdateTaskArgs"}]},{"type":"category","label":"Variables","items":[{"type":"doc","id":"api/variables/BaseProjectSchema","label":"BaseProjectSchema"},{"type":"doc","id":"api/variables/PersonalProjectSchema","label":"PersonalProjectSchema"},{"type":"doc","id":"api/variables/ProjectSchema","label":"ProjectSchema"},{"type":"doc","id":"api/variables/WorkspaceProjectSchema","label":"WorkspaceProjectSchema"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"api/functions/getAuthorizationUrl","label":"getAuthorizationUrl"},{"type":"doc","id":"api/functions/getAuthStateParameter","label":"getAuthStateParameter"},{"type":"doc","id":"api/functions/getAuthToken","label":"getAuthToken"},{"type":"doc","id":"api/functions/getColorByKey","label":"getColorByKey"},{"type":"doc","id":"api/functions/getSanitizedContent","label":"getSanitizedContent"},{"type":"doc","id":"api/functions/getSanitizedTasks","label":"getSanitizedTasks"},{"type":"doc","id":"api/functions/revokeAuthToken","label":"revokeAuthToken"}]}]}; module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/website/docs/api/variables/BaseProjectSchema.md b/website/docs/api/variables/BaseProjectSchema.md new file mode 100644 index 0000000..4a87fe3 --- /dev/null +++ b/website/docs/api/variables/BaseProjectSchema.md @@ -0,0 +1,59 @@ +# BaseProjectSchema + +```ts +const BaseProjectSchema: ZodObject<{ + canAssignTasks: ZodBoolean; + childOrder: ZodNumber; + color: ZodString; + createdAt: ZodNullable; + defaultOrder: ZodNumber; + description: ZodString; + id: ZodString; + isArchived: ZodBoolean; + isCollapsed: ZodBoolean; + isDeleted: ZodBoolean; + isFavorite: ZodBoolean; + isFrozen: ZodBoolean; + isShared: ZodBoolean; + name: ZodString; + updatedAt: ZodNullable; + viewStyle: ZodString; + }, "strip", { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + updatedAt: null | string; + viewStyle: string; + }, { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + updatedAt: null | string; + viewStyle: string; +}>; +``` + +Base schema for all project types in Todoist. +Contains common fields shared between personal and workspace projects. diff --git a/website/docs/api/variables/PersonalProjectSchema.md b/website/docs/api/variables/PersonalProjectSchema.md new file mode 100644 index 0000000..8a9be3a --- /dev/null +++ b/website/docs/api/variables/PersonalProjectSchema.md @@ -0,0 +1,104 @@ +# PersonalProjectSchema + +```ts +const PersonalProjectSchema: ZodEffects; + defaultOrder: ZodNumber; + description: ZodString; + id: ZodString; + isArchived: ZodBoolean; + isCollapsed: ZodBoolean; + isDeleted: ZodBoolean; + isFavorite: ZodBoolean; + isFrozen: ZodBoolean; + isShared: ZodBoolean; + name: ZodString; + updatedAt: ZodNullable; + viewStyle: ZodString; + }, { + inboxProject: ZodBoolean; + parentId: ZodNullable; + }>, "strip", { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + viewStyle: string; + }, { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + viewStyle: string; + }>, { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + url: string; + viewStyle: string; + }, { + canAssignTasks: boolean; + childOrder: number; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + id: string; + inboxProject: boolean; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isShared: boolean; + name: string; + parentId: null | string; + updatedAt: null | string; + viewStyle: string; +}>; +``` + +Schema for personal projects in Todoist. diff --git a/website/docs/api/variables/ProjectSchema.md b/website/docs/api/variables/ProjectSchema.md new file mode 100644 index 0000000..7bc7a5f --- /dev/null +++ b/website/docs/api/variables/ProjectSchema.md @@ -0,0 +1,7 @@ +# ProjectSchema + +```ts +const ProjectSchema: ZodUnion; +``` + +Schema for validating either a personal project or a workspace project. diff --git a/website/docs/api/variables/WorkspaceProjectSchema.md b/website/docs/api/variables/WorkspaceProjectSchema.md new file mode 100644 index 0000000..af8e2fe --- /dev/null +++ b/website/docs/api/variables/WorkspaceProjectSchema.md @@ -0,0 +1,129 @@ +# WorkspaceProjectSchema + +```ts +const WorkspaceProjectSchema: ZodEffects; + defaultOrder: ZodNumber; + description: ZodString; + id: ZodString; + isArchived: ZodBoolean; + isCollapsed: ZodBoolean; + isDeleted: ZodBoolean; + isFavorite: ZodBoolean; + isFrozen: ZodBoolean; + isShared: ZodBoolean; + name: ZodString; + updatedAt: ZodNullable; + viewStyle: ZodString; + }, { + collaboratorRoleDefault: ZodString; + folderId: ZodNullable; + isInviteOnly: ZodNullable; + isLinkSharingEnabled: ZodBoolean; + role: ZodNullable; + status: ZodString; + workspaceId: ZodString; + }>, "strip", { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + viewStyle: string; + workspaceId: string; + }, { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + viewStyle: string; + workspaceId: string; + }>, { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + url: string; + viewStyle: string; + workspaceId: string; + }, { + canAssignTasks: boolean; + childOrder: number; + collaboratorRoleDefault: string; + color: string; + createdAt: null | string; + defaultOrder: number; + description: string; + folderId: null | boolean; + id: string; + isArchived: boolean; + isCollapsed: boolean; + isDeleted: boolean; + isFavorite: boolean; + isFrozen: boolean; + isInviteOnly: null | boolean; + isLinkSharingEnabled: boolean; + isShared: boolean; + name: string; + role: null | string; + status: string; + updatedAt: null | string; + viewStyle: string; + workspaceId: string; +}>; +``` + +Schema for workspace projects in Todoist. From bced5ee1f90748f578dfae9a98a70c051b46ae7a Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Thu, 17 Apr 2025 10:46:27 +0200 Subject: [PATCH 4/5] chore: do not export slugify function --- src/utils/urlHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/urlHelpers.ts b/src/utils/urlHelpers.ts index a3f347a..039b3cc 100644 --- a/src/utils/urlHelpers.ts +++ b/src/utils/urlHelpers.ts @@ -32,7 +32,7 @@ export function getProjectUrl(projectId: string, name?: string): string { * @param value The string to slugify. * @returns The slugified string. */ -export function slugify(value: string): string { +function slugify(value: string): string { // Convert to ASCII let result = value.normalize('NFKD').replace(/[\u0300-\u036f]/g, '') From a922ba64ecba0e423802aa09d7633948a9876597 Mon Sep 17 00:00:00 2001 From: Pedro Alves Date: Thu, 17 Apr 2025 15:26:22 +0200 Subject: [PATCH 5/5] chore: switch to explicit personal/workspace project types instead of Project union --- src/TodoistApi.ts | 21 +- src/testUtils/testDefaults.ts | 6 +- src/types/entities.ts | 10 - src/types/requests.ts | 5 +- src/utils/validators.ts | 28 +- website/docs/api/.md | 2 - website/docs/api/classes/TodoistApi.md | 294 +----------------- .../api/type-aliases/GetProjectsResponse.md | 6 +- website/docs/api/type-aliases/Project.md | 54 ---- website/docs/api/typedoc-sidebar.cjs | 2 +- website/docs/api/variables/ProjectSchema.md | 7 - 11 files changed, 57 insertions(+), 378 deletions(-) delete mode 100644 website/docs/api/type-aliases/Project.md delete mode 100644 website/docs/api/variables/ProjectSchema.md diff --git a/src/TodoistApi.ts b/src/TodoistApi.ts index d5e9a9d..33e4452 100644 --- a/src/TodoistApi.ts +++ b/src/TodoistApi.ts @@ -1,4 +1,4 @@ -import { Project, Label, Section, Comment, Task } from './types/entities' +import { PersonalProject, WorkspaceProject, Label, Section, Comment, Task } from './types/entities' import { AddCommentArgs, AddLabelArgs, @@ -367,9 +367,9 @@ export class TodoistApi { * @param id - The unique identifier of the project. * @returns A promise that resolves to the requested project. */ - async getProject(id: string): Promise { + async getProject(id: string): Promise { z.string().parse(id) - const response = await request( + const response = await request( 'GET', this.syncApiBase, generatePath(ENDPOINT_REST_PROJECTS, id), @@ -409,8 +409,11 @@ export class TodoistApi { * @param requestId - Optional unique identifier for idempotency. * @returns A promise that resolves to the created project. */ - async addProject(args: AddProjectArgs, requestId?: string): Promise { - const response = await request( + async addProject( + args: AddProjectArgs, + requestId?: string, + ): Promise { + const response = await request( 'POST', this.syncApiBase, ENDPOINT_REST_PROJECTS, @@ -430,9 +433,13 @@ export class TodoistApi { * @param requestId - Optional unique identifier for idempotency. * @returns A promise that resolves to the updated project. */ - async updateProject(id: string, args: UpdateProjectArgs, requestId?: string): Promise { + async updateProject( + id: string, + args: UpdateProjectArgs, + requestId?: string, + ): Promise { z.string().parse(id) - const response = await request( + const response = await request( 'POST', this.syncApiBase, generatePath(ENDPOINT_REST_PROJECTS, id), diff --git a/src/testUtils/testDefaults.ts b/src/testUtils/testDefaults.ts index 3ef3fe2..515b28b 100644 --- a/src/testUtils/testDefaults.ts +++ b/src/testUtils/testDefaults.ts @@ -1,6 +1,5 @@ import { Label, - Project, Section, Task, User, @@ -8,6 +7,7 @@ import { Duration, Deadline, RawComment, + PersonalProject, } from '../types' import { getProjectUrl, getTaskUrl } from '../utils/urlHelpers' @@ -130,7 +130,7 @@ export const TASK_WITH_OPTIONALS_AS_NULL: Task = { url: DEFAULT_TASK_URL, } -export const DEFAULT_PROJECT: Project = { +export const DEFAULT_PROJECT: PersonalProject = { id: DEFAULT_PROJECT_ID, name: DEFAULT_PROJECT_NAME, color: DEFAULT_ENTITY_COLOR, @@ -157,7 +157,7 @@ export const INVALID_PROJECT = { name: 123, } -export const PROJECT_WITH_OPTIONALS_AS_NULL: Project = { +export const PROJECT_WITH_OPTIONALS_AS_NULL: PersonalProject = { ...DEFAULT_PROJECT, parentId: null, } diff --git a/src/types/entities.ts b/src/types/entities.ts index efd3620..7319a09 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -142,16 +142,6 @@ export interface PersonalProject extends z.infer { */ export interface WorkspaceProject extends z.infer {} -/** - * Schema for validating either a personal project or a workspace project. - */ -export const ProjectSchema = z.union([PersonalProjectSchema, WorkspaceProjectSchema]) - -/** - * Represents either a personal project or a workspace project in Todoist. - */ -export type Project = z.infer - // This allows us to accept any string during validation, but provide intellisense for the two possible values in request args /** * @see https://todoist.com/api/v1/docs#tag/Projects diff --git a/src/types/requests.ts b/src/types/requests.ts index 63d2cc1..f14a696 100644 --- a/src/types/requests.ts +++ b/src/types/requests.ts @@ -3,11 +3,12 @@ import type { Comment, Duration, Label, - Project, + PersonalProject, ProjectViewStyle, Section, Task, User, + WorkspaceProject, } from './entities' /** @@ -134,7 +135,7 @@ export type GetProjectsArgs = { * @see https://todoist.com/api/v1/docs#tag/Projects/operation/get_projects_api_v1_projects_get */ export type GetProjectsResponse = { - results: Project[] + results: (PersonalProject | WorkspaceProject)[] nextCursor: string | null } diff --git a/src/utils/validators.ts b/src/utils/validators.ts index 33caecd..2c23ebc 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -3,14 +3,14 @@ import { LabelSchema, CommentSchema, UserSchema, + TaskSchema, type Task, - type Project, type Section, type Label, type Comment, type User, - ProjectSchema, - TaskSchema, + PersonalProjectSchema, + WorkspaceProjectSchema, type WorkspaceProject, type PersonalProject, } from '../types/entities' @@ -28,7 +28,9 @@ export function validateTaskArray(input: unknown[]): Task[] { * @param project The project to check * @returns True if the project is a workspace project */ -export function isWorkspaceProject(project: Project): project is WorkspaceProject { +export function isWorkspaceProject( + project: PersonalProject | WorkspaceProject, +): project is WorkspaceProject { return 'workspaceId' in project } @@ -37,15 +39,25 @@ export function isWorkspaceProject(project: Project): project is WorkspaceProjec * @param project The project to check * @returns True if the project is a personal project */ -export function isPersonalProject(project: Project): project is PersonalProject { +export function isPersonalProject( + project: PersonalProject | WorkspaceProject, +): project is PersonalProject { return !isWorkspaceProject(project) } -export function validateProject(input: unknown): Project { - return ProjectSchema.parse(input) +/** + * Validates and parses a project input. + * @param input The input to validate + * @returns A validated project (either PersonalProject or WorkspaceProject) + */ +export function validateProject(input: unknown): PersonalProject | WorkspaceProject { + if ('workspaceId' in (input as WorkspaceProject)) { + return WorkspaceProjectSchema.parse(input) + } + return PersonalProjectSchema.parse(input) } -export function validateProjectArray(input: unknown[]): Project[] { +export function validateProjectArray(input: unknown[]): (PersonalProject | WorkspaceProject)[] { return input.map(validateProject) } diff --git a/website/docs/api/.md b/website/docs/api/.md index 724c1f8..86a0a13 100644 --- a/website/docs/api/.md +++ b/website/docs/api/.md @@ -55,7 +55,6 @@ | [GetTasksResponse](type-aliases/GetTasksResponse.md) | - | | [MoveTaskArgs](type-aliases/MoveTaskArgs.md) | Arguments for moving a task. | | [Permission](type-aliases/Permission.md) | Permission scopes that can be requested during OAuth2 authorization. | -| [Project](type-aliases/Project.md) | Represents either a personal project or a workspace project in Todoist. | | [ProjectViewStyle](type-aliases/ProjectViewStyle.md) | - | | [QuickAddTaskArgs](type-aliases/QuickAddTaskArgs.md) | Arguments for quick adding a task. | | [RemoveSharedLabelArgs](type-aliases/RemoveSharedLabelArgs.md) | Arguments for removing a shared label. | @@ -74,7 +73,6 @@ | ------ | ------ | | [BaseProjectSchema](variables/BaseProjectSchema.md) | Base schema for all project types in Todoist. Contains common fields shared between personal and workspace projects. | | [PersonalProjectSchema](variables/PersonalProjectSchema.md) | Schema for personal projects in Todoist. | -| [ProjectSchema](variables/ProjectSchema.md) | Schema for validating either a personal project or a workspace project. | | [WorkspaceProjectSchema](variables/WorkspaceProjectSchema.md) | Schema for workspace projects in Todoist. | ## Functions diff --git a/website/docs/api/classes/TodoistApi.md b/website/docs/api/classes/TodoistApi.md index 9c23752..c123b10 100644 --- a/website/docs/api/classes/TodoistApi.md +++ b/website/docs/api/classes/TodoistApi.md @@ -92,53 +92,8 @@ A promise that resolves to the created label. ```ts addProject(args: AddProjectArgs, requestId?: string): Promise< - | { - canAssignTasks: boolean; - childOrder: number; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - id: string; - inboxProject: boolean; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isShared: boolean; - name: string; - parentId: null | string; - updatedAt: null | string; - url: string; - viewStyle: string; - } - | { - canAssignTasks: boolean; - childOrder: number; - collaboratorRoleDefault: string; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - folderId: null | boolean; - id: string; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isInviteOnly: null | boolean; - isLinkSharingEnabled: boolean; - isShared: boolean; - name: string; - role: null | string; - status: string; - updatedAt: null | string; - url: string; - viewStyle: string; - workspaceId: string; -}> + | PersonalProject +| WorkspaceProject> ``` Creates a new project with the provided parameters. @@ -153,53 +108,8 @@ Creates a new project with the provided parameters. #### Returns `Promise`\< - \| \{ - `canAssignTasks`: `boolean`; - `childOrder`: `number`; - `color`: `string`; - `createdAt`: `null` \| `string`; - `defaultOrder`: `number`; - `description`: `string`; - `id`: `string`; - `inboxProject`: `boolean`; - `isArchived`: `boolean`; - `isCollapsed`: `boolean`; - `isDeleted`: `boolean`; - `isFavorite`: `boolean`; - `isFrozen`: `boolean`; - `isShared`: `boolean`; - `name`: `string`; - `parentId`: `null` \| `string`; - `updatedAt`: `null` \| `string`; - `url`: `string`; - `viewStyle`: `string`; - \} - \| \{ - `canAssignTasks`: `boolean`; - `childOrder`: `number`; - `collaboratorRoleDefault`: `string`; - `color`: `string`; - `createdAt`: `null` \| `string`; - `defaultOrder`: `number`; - `description`: `string`; - `folderId`: `null` \| `boolean`; - `id`: `string`; - `isArchived`: `boolean`; - `isCollapsed`: `boolean`; - `isDeleted`: `boolean`; - `isFavorite`: `boolean`; - `isFrozen`: `boolean`; - `isInviteOnly`: `null` \| `boolean`; - `isLinkSharingEnabled`: `boolean`; - `isShared`: `boolean`; - `name`: `string`; - `role`: `null` \| `string`; - `status`: `string`; - `updatedAt`: `null` \| `string`; - `url`: `string`; - `viewStyle`: `string`; - `workspaceId`: `string`; - \}\> + \| [`PersonalProject`](../interfaces/PersonalProject.md) + \| [`WorkspaceProject`](../interfaces/WorkspaceProject.md)\> A promise that resolves to the created project. @@ -483,53 +393,8 @@ A promise that resolves to an array of labels. ```ts getProject(id: string): Promise< - | { - canAssignTasks: boolean; - childOrder: number; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - id: string; - inboxProject: boolean; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isShared: boolean; - name: string; - parentId: null | string; - updatedAt: null | string; - url: string; - viewStyle: string; - } - | { - canAssignTasks: boolean; - childOrder: number; - collaboratorRoleDefault: string; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - folderId: null | boolean; - id: string; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isInviteOnly: null | boolean; - isLinkSharingEnabled: boolean; - isShared: boolean; - name: string; - role: null | string; - status: string; - updatedAt: null | string; - url: string; - viewStyle: string; - workspaceId: string; -}> + | PersonalProject +| WorkspaceProject> ``` Retrieves a project by its ID. @@ -543,53 +408,8 @@ Retrieves a project by its ID. #### Returns `Promise`\< - \| \{ - `canAssignTasks`: `boolean`; - `childOrder`: `number`; - `color`: `string`; - `createdAt`: `null` \| `string`; - `defaultOrder`: `number`; - `description`: `string`; - `id`: `string`; - `inboxProject`: `boolean`; - `isArchived`: `boolean`; - `isCollapsed`: `boolean`; - `isDeleted`: `boolean`; - `isFavorite`: `boolean`; - `isFrozen`: `boolean`; - `isShared`: `boolean`; - `name`: `string`; - `parentId`: `null` \| `string`; - `updatedAt`: `null` \| `string`; - `url`: `string`; - `viewStyle`: `string`; - \} - \| \{ - `canAssignTasks`: `boolean`; - `childOrder`: `number`; - `collaboratorRoleDefault`: `string`; - `color`: `string`; - `createdAt`: `null` \| `string`; - `defaultOrder`: `number`; - `description`: `string`; - `folderId`: `null` \| `boolean`; - `id`: `string`; - `isArchived`: `boolean`; - `isCollapsed`: `boolean`; - `isDeleted`: `boolean`; - `isFavorite`: `boolean`; - `isFrozen`: `boolean`; - `isInviteOnly`: `null` \| `boolean`; - `isLinkSharingEnabled`: `boolean`; - `isShared`: `boolean`; - `name`: `string`; - `role`: `null` \| `string`; - `status`: `string`; - `updatedAt`: `null` \| `string`; - `url`: `string`; - `viewStyle`: `string`; - `workspaceId`: `string`; - \}\> + \| [`PersonalProject`](../interfaces/PersonalProject.md) + \| [`WorkspaceProject`](../interfaces/WorkspaceProject.md)\> A promise that resolves to the requested project. @@ -949,53 +769,8 @@ updateProject( id: string, args: UpdateProjectArgs, requestId?: string): Promise< - | { - canAssignTasks: boolean; - childOrder: number; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - id: string; - inboxProject: boolean; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isShared: boolean; - name: string; - parentId: null | string; - updatedAt: null | string; - url: string; - viewStyle: string; - } - | { - canAssignTasks: boolean; - childOrder: number; - collaboratorRoleDefault: string; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - folderId: null | boolean; - id: string; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isInviteOnly: null | boolean; - isLinkSharingEnabled: boolean; - isShared: boolean; - name: string; - role: null | string; - status: string; - updatedAt: null | string; - url: string; - viewStyle: string; - workspaceId: string; -}> + | PersonalProject +| WorkspaceProject> ``` Updates an existing project by its ID with the provided parameters. @@ -1011,53 +786,8 @@ Updates an existing project by its ID with the provided parameters. #### Returns `Promise`\< - \| \{ - `canAssignTasks`: `boolean`; - `childOrder`: `number`; - `color`: `string`; - `createdAt`: `null` \| `string`; - `defaultOrder`: `number`; - `description`: `string`; - `id`: `string`; - `inboxProject`: `boolean`; - `isArchived`: `boolean`; - `isCollapsed`: `boolean`; - `isDeleted`: `boolean`; - `isFavorite`: `boolean`; - `isFrozen`: `boolean`; - `isShared`: `boolean`; - `name`: `string`; - `parentId`: `null` \| `string`; - `updatedAt`: `null` \| `string`; - `url`: `string`; - `viewStyle`: `string`; - \} - \| \{ - `canAssignTasks`: `boolean`; - `childOrder`: `number`; - `collaboratorRoleDefault`: `string`; - `color`: `string`; - `createdAt`: `null` \| `string`; - `defaultOrder`: `number`; - `description`: `string`; - `folderId`: `null` \| `boolean`; - `id`: `string`; - `isArchived`: `boolean`; - `isCollapsed`: `boolean`; - `isDeleted`: `boolean`; - `isFavorite`: `boolean`; - `isFrozen`: `boolean`; - `isInviteOnly`: `null` \| `boolean`; - `isLinkSharingEnabled`: `boolean`; - `isShared`: `boolean`; - `name`: `string`; - `role`: `null` \| `string`; - `status`: `string`; - `updatedAt`: `null` \| `string`; - `url`: `string`; - `viewStyle`: `string`; - `workspaceId`: `string`; - \}\> + \| [`PersonalProject`](../interfaces/PersonalProject.md) + \| [`WorkspaceProject`](../interfaces/WorkspaceProject.md)\> A promise that resolves to the updated project. diff --git a/website/docs/api/type-aliases/GetProjectsResponse.md b/website/docs/api/type-aliases/GetProjectsResponse.md index 1af4a75..440ce64 100644 --- a/website/docs/api/type-aliases/GetProjectsResponse.md +++ b/website/docs/api/type-aliases/GetProjectsResponse.md @@ -3,7 +3,9 @@ ```ts type GetProjectsResponse = { nextCursor: string | null; - results: Project[]; + results: ( + | PersonalProject + | WorkspaceProject)[]; }; ``` @@ -14,7 +16,7 @@ Response from retrieving projects. | Name | Type | | ------ | ------ | | `nextCursor` | `string` \| `null` | -| `results` | [`Project`](Project.md)[] | +| `results` | ( \| [`PersonalProject`](../interfaces/PersonalProject.md) \| [`WorkspaceProject`](../interfaces/WorkspaceProject.md))[] | ## See diff --git a/website/docs/api/type-aliases/Project.md b/website/docs/api/type-aliases/Project.md deleted file mode 100644 index a683c54..0000000 --- a/website/docs/api/type-aliases/Project.md +++ /dev/null @@ -1,54 +0,0 @@ -# Project - -```ts -type Project = - | { - canAssignTasks: boolean; - childOrder: number; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - id: string; - inboxProject: boolean; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isShared: boolean; - name: string; - parentId: null | string; - updatedAt: null | string; - url: string; - viewStyle: string; - } - | { - canAssignTasks: boolean; - childOrder: number; - collaboratorRoleDefault: string; - color: string; - createdAt: null | string; - defaultOrder: number; - description: string; - folderId: null | boolean; - id: string; - isArchived: boolean; - isCollapsed: boolean; - isDeleted: boolean; - isFavorite: boolean; - isFrozen: boolean; - isInviteOnly: null | boolean; - isLinkSharingEnabled: boolean; - isShared: boolean; - name: string; - role: null | string; - status: string; - updatedAt: null | string; - url: string; - viewStyle: string; - workspaceId: string; -}; -``` - -Represents either a personal project or a workspace project in Todoist. diff --git a/website/docs/api/typedoc-sidebar.cjs b/website/docs/api/typedoc-sidebar.cjs index d9d6774..8cc1ab8 100644 --- a/website/docs/api/typedoc-sidebar.cjs +++ b/website/docs/api/typedoc-sidebar.cjs @@ -1,4 +1,4 @@ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"api/classes/TodoistApi","label":"TodoistApi"},{"type":"doc","id":"api/classes/TodoistRequestError","label":"TodoistRequestError"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"api/interfaces/Attachment","label":"Attachment"},{"type":"doc","id":"api/interfaces/Color","label":"Color"},{"type":"doc","id":"api/interfaces/Comment","label":"Comment"},{"type":"doc","id":"api/interfaces/Deadline","label":"Deadline"},{"type":"doc","id":"api/interfaces/DueDate","label":"DueDate"},{"type":"doc","id":"api/interfaces/Duration","label":"Duration"},{"type":"doc","id":"api/interfaces/Label","label":"Label"},{"type":"doc","id":"api/interfaces/PersonalProject","label":"PersonalProject"},{"type":"doc","id":"api/interfaces/RawComment","label":"RawComment"},{"type":"doc","id":"api/interfaces/Section","label":"Section"},{"type":"doc","id":"api/interfaces/Task","label":"Task"},{"type":"doc","id":"api/interfaces/User","label":"User"},{"type":"doc","id":"api/interfaces/WorkspaceProject","label":"WorkspaceProject"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"api/type-aliases/AddCommentArgs","label":"AddCommentArgs"},{"type":"doc","id":"api/type-aliases/AddLabelArgs","label":"AddLabelArgs"},{"type":"doc","id":"api/type-aliases/AddProjectArgs","label":"AddProjectArgs"},{"type":"doc","id":"api/type-aliases/AddSectionArgs","label":"AddSectionArgs"},{"type":"doc","id":"api/type-aliases/AddTaskArgs","label":"AddTaskArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenRequestArgs","label":"AuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenResponse","label":"AuthTokenResponse"},{"type":"doc","id":"api/type-aliases/GetCommentsArgs","label":"GetCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetCommentsResponse","label":"GetCommentsResponse"},{"type":"doc","id":"api/type-aliases/GetLabelsArgs","label":"GetLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetLabelsResponse","label":"GetLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsArgs","label":"GetProjectCollaboratorsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsResponse","label":"GetProjectCollaboratorsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCommentsArgs","label":"GetProjectCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsArgs","label":"GetProjectsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsResponse","label":"GetProjectsResponse"},{"type":"doc","id":"api/type-aliases/GetSectionsArgs","label":"GetSectionsArgs"},{"type":"doc","id":"api/type-aliases/GetSectionsResponse","label":"GetSectionsResponse"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsArgs","label":"GetSharedLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsResponse","label":"GetSharedLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetTaskCommentsArgs","label":"GetTaskCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetTasksArgs","label":"GetTasksArgs"},{"type":"doc","id":"api/type-aliases/GetTasksByFilterArgs","label":"GetTasksByFilterArgs"},{"type":"doc","id":"api/type-aliases/GetTasksResponse","label":"GetTasksResponse"},{"type":"doc","id":"api/type-aliases/MoveTaskArgs","label":"MoveTaskArgs"},{"type":"doc","id":"api/type-aliases/Permission","label":"Permission"},{"type":"doc","id":"api/type-aliases/Project","label":"Project"},{"type":"doc","id":"api/type-aliases/ProjectViewStyle","label":"ProjectViewStyle"},{"type":"doc","id":"api/type-aliases/QuickAddTaskArgs","label":"QuickAddTaskArgs"},{"type":"doc","id":"api/type-aliases/RemoveSharedLabelArgs","label":"RemoveSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RenameSharedLabelArgs","label":"RenameSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RevokeAuthTokenRequestArgs","label":"RevokeAuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/TaskWithSanitizedContent","label":"TaskWithSanitizedContent"},{"type":"doc","id":"api/type-aliases/UpdateCommentArgs","label":"UpdateCommentArgs"},{"type":"doc","id":"api/type-aliases/UpdateLabelArgs","label":"UpdateLabelArgs"},{"type":"doc","id":"api/type-aliases/UpdateProjectArgs","label":"UpdateProjectArgs"},{"type":"doc","id":"api/type-aliases/UpdateSectionArgs","label":"UpdateSectionArgs"},{"type":"doc","id":"api/type-aliases/UpdateTaskArgs","label":"UpdateTaskArgs"}]},{"type":"category","label":"Variables","items":[{"type":"doc","id":"api/variables/BaseProjectSchema","label":"BaseProjectSchema"},{"type":"doc","id":"api/variables/PersonalProjectSchema","label":"PersonalProjectSchema"},{"type":"doc","id":"api/variables/ProjectSchema","label":"ProjectSchema"},{"type":"doc","id":"api/variables/WorkspaceProjectSchema","label":"WorkspaceProjectSchema"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"api/functions/getAuthorizationUrl","label":"getAuthorizationUrl"},{"type":"doc","id":"api/functions/getAuthStateParameter","label":"getAuthStateParameter"},{"type":"doc","id":"api/functions/getAuthToken","label":"getAuthToken"},{"type":"doc","id":"api/functions/getColorByKey","label":"getColorByKey"},{"type":"doc","id":"api/functions/getSanitizedContent","label":"getSanitizedContent"},{"type":"doc","id":"api/functions/getSanitizedTasks","label":"getSanitizedTasks"},{"type":"doc","id":"api/functions/revokeAuthToken","label":"revokeAuthToken"}]}]}; +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"api/classes/TodoistApi","label":"TodoistApi"},{"type":"doc","id":"api/classes/TodoistRequestError","label":"TodoistRequestError"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"api/interfaces/Attachment","label":"Attachment"},{"type":"doc","id":"api/interfaces/Color","label":"Color"},{"type":"doc","id":"api/interfaces/Comment","label":"Comment"},{"type":"doc","id":"api/interfaces/Deadline","label":"Deadline"},{"type":"doc","id":"api/interfaces/DueDate","label":"DueDate"},{"type":"doc","id":"api/interfaces/Duration","label":"Duration"},{"type":"doc","id":"api/interfaces/Label","label":"Label"},{"type":"doc","id":"api/interfaces/PersonalProject","label":"PersonalProject"},{"type":"doc","id":"api/interfaces/RawComment","label":"RawComment"},{"type":"doc","id":"api/interfaces/Section","label":"Section"},{"type":"doc","id":"api/interfaces/Task","label":"Task"},{"type":"doc","id":"api/interfaces/User","label":"User"},{"type":"doc","id":"api/interfaces/WorkspaceProject","label":"WorkspaceProject"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"api/type-aliases/AddCommentArgs","label":"AddCommentArgs"},{"type":"doc","id":"api/type-aliases/AddLabelArgs","label":"AddLabelArgs"},{"type":"doc","id":"api/type-aliases/AddProjectArgs","label":"AddProjectArgs"},{"type":"doc","id":"api/type-aliases/AddSectionArgs","label":"AddSectionArgs"},{"type":"doc","id":"api/type-aliases/AddTaskArgs","label":"AddTaskArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenRequestArgs","label":"AuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/AuthTokenResponse","label":"AuthTokenResponse"},{"type":"doc","id":"api/type-aliases/GetCommentsArgs","label":"GetCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetCommentsResponse","label":"GetCommentsResponse"},{"type":"doc","id":"api/type-aliases/GetLabelsArgs","label":"GetLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetLabelsResponse","label":"GetLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsArgs","label":"GetProjectCollaboratorsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectCollaboratorsResponse","label":"GetProjectCollaboratorsResponse"},{"type":"doc","id":"api/type-aliases/GetProjectCommentsArgs","label":"GetProjectCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsArgs","label":"GetProjectsArgs"},{"type":"doc","id":"api/type-aliases/GetProjectsResponse","label":"GetProjectsResponse"},{"type":"doc","id":"api/type-aliases/GetSectionsArgs","label":"GetSectionsArgs"},{"type":"doc","id":"api/type-aliases/GetSectionsResponse","label":"GetSectionsResponse"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsArgs","label":"GetSharedLabelsArgs"},{"type":"doc","id":"api/type-aliases/GetSharedLabelsResponse","label":"GetSharedLabelsResponse"},{"type":"doc","id":"api/type-aliases/GetTaskCommentsArgs","label":"GetTaskCommentsArgs"},{"type":"doc","id":"api/type-aliases/GetTasksArgs","label":"GetTasksArgs"},{"type":"doc","id":"api/type-aliases/GetTasksByFilterArgs","label":"GetTasksByFilterArgs"},{"type":"doc","id":"api/type-aliases/GetTasksResponse","label":"GetTasksResponse"},{"type":"doc","id":"api/type-aliases/MoveTaskArgs","label":"MoveTaskArgs"},{"type":"doc","id":"api/type-aliases/Permission","label":"Permission"},{"type":"doc","id":"api/type-aliases/ProjectViewStyle","label":"ProjectViewStyle"},{"type":"doc","id":"api/type-aliases/QuickAddTaskArgs","label":"QuickAddTaskArgs"},{"type":"doc","id":"api/type-aliases/RemoveSharedLabelArgs","label":"RemoveSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RenameSharedLabelArgs","label":"RenameSharedLabelArgs"},{"type":"doc","id":"api/type-aliases/RevokeAuthTokenRequestArgs","label":"RevokeAuthTokenRequestArgs"},{"type":"doc","id":"api/type-aliases/TaskWithSanitizedContent","label":"TaskWithSanitizedContent"},{"type":"doc","id":"api/type-aliases/UpdateCommentArgs","label":"UpdateCommentArgs"},{"type":"doc","id":"api/type-aliases/UpdateLabelArgs","label":"UpdateLabelArgs"},{"type":"doc","id":"api/type-aliases/UpdateProjectArgs","label":"UpdateProjectArgs"},{"type":"doc","id":"api/type-aliases/UpdateSectionArgs","label":"UpdateSectionArgs"},{"type":"doc","id":"api/type-aliases/UpdateTaskArgs","label":"UpdateTaskArgs"}]},{"type":"category","label":"Variables","items":[{"type":"doc","id":"api/variables/BaseProjectSchema","label":"BaseProjectSchema"},{"type":"doc","id":"api/variables/PersonalProjectSchema","label":"PersonalProjectSchema"},{"type":"doc","id":"api/variables/WorkspaceProjectSchema","label":"WorkspaceProjectSchema"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"api/functions/getAuthorizationUrl","label":"getAuthorizationUrl"},{"type":"doc","id":"api/functions/getAuthStateParameter","label":"getAuthStateParameter"},{"type":"doc","id":"api/functions/getAuthToken","label":"getAuthToken"},{"type":"doc","id":"api/functions/getColorByKey","label":"getColorByKey"},{"type":"doc","id":"api/functions/getSanitizedContent","label":"getSanitizedContent"},{"type":"doc","id":"api/functions/getSanitizedTasks","label":"getSanitizedTasks"},{"type":"doc","id":"api/functions/revokeAuthToken","label":"revokeAuthToken"}]}]}; module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/website/docs/api/variables/ProjectSchema.md b/website/docs/api/variables/ProjectSchema.md deleted file mode 100644 index 7bc7a5f..0000000 --- a/website/docs/api/variables/ProjectSchema.md +++ /dev/null @@ -1,7 +0,0 @@ -# ProjectSchema - -```ts -const ProjectSchema: ZodUnion; -``` - -Schema for validating either a personal project or a workspace project.