Skip to content

Commit 34504b5

Browse files
feat: Add complete workspace endpoints support (#372)
Co-authored-by: Claude <[email protected]>
1 parent 30bf982 commit 34504b5

File tree

10 files changed

+1579
-45
lines changed

10 files changed

+1579
-45
lines changed

src/TodoistApi.ts

Lines changed: 439 additions & 44 deletions
Large diffs are not rendered by default.

src/TodoistApi.workspaces.test.ts

Lines changed: 504 additions & 0 deletions
Large diffs are not rendered by default.

src/consts/endpoints.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,30 @@ export const ENDPOINT_AUTHORIZATION = 'authorize'
5151
export const ENDPOINT_GET_TOKEN = 'access_token'
5252
export const ENDPOINT_REVOKE_TOKEN = 'access_tokens/revoke'
5353
export const ENDPOINT_REVOKE = 'revoke'
54+
55+
// Workspace endpoints
56+
export const ENDPOINT_WORKSPACE_INVITATIONS = 'workspaces/invitations'
57+
export const ENDPOINT_WORKSPACE_INVITATIONS_ALL = 'workspaces/invitations/all'
58+
export const ENDPOINT_WORKSPACE_INVITATIONS_DELETE = 'workspaces/invitations/delete'
59+
export const ENDPOINT_WORKSPACE_JOIN = 'workspaces/join'
60+
export const ENDPOINT_WORKSPACE_LOGO = 'workspaces/logo'
61+
export const ENDPOINT_WORKSPACE_PLAN_DETAILS = 'workspaces/plan_details'
62+
export const ENDPOINT_WORKSPACE_USERS = 'workspaces/users'
63+
64+
// Workspace invitation actions (require invite_code parameter)
65+
export function getWorkspaceInvitationAcceptEndpoint(inviteCode: string): string {
66+
return `workspaces/invitations/${inviteCode}/accept`
67+
}
68+
69+
export function getWorkspaceInvitationRejectEndpoint(inviteCode: string): string {
70+
return `workspaces/invitations/${inviteCode}/reject`
71+
}
72+
73+
// Workspace projects (require workspace_id parameter)
74+
export function getWorkspaceActiveProjectsEndpoint(workspaceId: number): string {
75+
return `workspaces/${workspaceId}/projects/active`
76+
}
77+
78+
export function getWorkspaceArchivedProjectsEndpoint(workspaceId: number): string {
79+
return `workspaces/${workspaceId}/projects/archived`
80+
}

src/restClient.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ export async function request<T>(
136136
relativePath,
137137
hasSyncCommands ? JSON.stringify(payload) : payload,
138138
)
139+
case 'PUT':
140+
return await axiosClient.put<T>(
141+
relativePath,
142+
hasSyncCommands ? JSON.stringify(payload) : payload,
143+
)
139144
case 'DELETE':
140145
return await axiosClient.delete<T>(relativePath)
141146
}

src/types/entities.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,104 @@ export const ActivityEventSchema = z
472472
* Represents an activity log event in Todoist.
473473
*/
474474
export type ActivityEvent = z.infer<typeof ActivityEventSchema>
475+
476+
/**
477+
* Available workspace roles.
478+
*/
479+
export const WORKSPACE_ROLES = ['ADMIN', 'MEMBER', 'GUEST'] as const
480+
481+
/**
482+
* Role of a user within a workspace.
483+
*/
484+
export type WorkspaceRole = (typeof WORKSPACE_ROLES)[number]
485+
486+
export const WorkspaceRoleSchema = z.enum(WORKSPACE_ROLES)
487+
488+
export const WorkspaceUserSchema = z.object({
489+
userId: z.string(),
490+
workspaceId: z.string(),
491+
userEmail: z.string(),
492+
fullName: z.string(),
493+
timezone: z.string(),
494+
role: WorkspaceRoleSchema,
495+
imageId: z.string().nullable(),
496+
isDeleted: z.boolean().default(false),
497+
})
498+
499+
/**
500+
* Represents a user within a workspace (MemberView from API).
501+
*/
502+
export type WorkspaceUser = z.infer<typeof WorkspaceUserSchema>
503+
504+
export const WorkspaceInvitationSchema = z.object({
505+
id: z.string().default('0'),
506+
inviterId: z.string(),
507+
userEmail: z.string(),
508+
workspaceId: z.string(),
509+
role: WorkspaceRoleSchema,
510+
isExistingUser: z.boolean(),
511+
})
512+
513+
/**
514+
* Represents a workspace invitation.
515+
*/
516+
export type WorkspaceInvitation = z.infer<typeof WorkspaceInvitationSchema>
517+
518+
export const PlanPriceSchema = z.object({
519+
currency: z.string(),
520+
amount: z.union([z.number(), z.string()]),
521+
interval: z.string().optional(),
522+
})
523+
524+
/**
525+
* Plan pricing information.
526+
*/
527+
export type PlanPrice = z.infer<typeof PlanPriceSchema>
528+
529+
export const FormattedPriceListingSchema = z.object({
530+
currency: z.string().optional(),
531+
amount: z.number().optional(),
532+
interval: z.string().optional(),
533+
formatted: z.string().optional(),
534+
})
535+
536+
/**
537+
* Formatted price listing for workspace plans.
538+
*/
539+
export type FormattedPriceListing = z.infer<typeof FormattedPriceListingSchema>
540+
541+
export const WorkspacePlanDetailsSchema = z.object({
542+
currentMemberCount: z.number(),
543+
currentPlan: z.enum(['Business', 'Starter']),
544+
currentPlanStatus: z.enum(['Active', 'Downgraded', 'Cancelled', 'NeverSubscribed']),
545+
downgradeAt: z.string().nullable(),
546+
currentActiveProjects: z.number(),
547+
maximumActiveProjects: z.number(),
548+
priceList: z.array(FormattedPriceListingSchema),
549+
workspaceId: z.number(),
550+
isTrialing: z.boolean(),
551+
trialEndsAt: z.string().nullable(),
552+
cancelAtPeriodEnd: z.boolean(),
553+
hasTrialed: z.boolean(),
554+
planPrice: PlanPriceSchema.nullable(),
555+
hasBillingPortal: z.boolean(),
556+
hasBillingPortalSwitchToAnnual: z.boolean(),
557+
})
558+
559+
/**
560+
* Represents workspace plan and billing details.
561+
*/
562+
export type WorkspacePlanDetails = z.infer<typeof WorkspacePlanDetailsSchema>
563+
564+
export const JoinWorkspaceResultSchema = z.object({
565+
custom_sorting_applied: z.boolean(),
566+
project_sort_preference: z.string(),
567+
role: WorkspaceRoleSchema,
568+
user_id: z.string(),
569+
workspace_id: z.string(),
570+
})
571+
572+
/**
573+
* Result returned when successfully joining a workspace.
574+
*/
575+
export type JoinWorkspaceResult = z.infer<typeof JoinWorkspaceResultSchema>

src/types/http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export type HttpMethod = 'POST' | 'GET' | 'DELETE'
1+
export type HttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT'

src/types/requests.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,3 +570,154 @@ export type DeleteUploadArgs = {
570570
*/
571571
fileUrl: string
572572
}
573+
574+
// Workspace-related types
575+
576+
/**
577+
* Arguments for getting workspace invitations.
578+
*/
579+
export type GetWorkspaceInvitationsArgs = {
580+
/**
581+
* The workspace ID to get invitations for.
582+
*/
583+
workspaceId: number
584+
}
585+
586+
/**
587+
* Arguments for deleting a workspace invitation.
588+
*/
589+
export type DeleteWorkspaceInvitationArgs = {
590+
/**
591+
* The workspace ID.
592+
*/
593+
workspaceId: number
594+
/**
595+
* The email address of the invitation to delete.
596+
*/
597+
userEmail: string
598+
}
599+
600+
/**
601+
* Arguments for accepting/rejecting a workspace invitation.
602+
*/
603+
export type WorkspaceInvitationActionArgs = {
604+
/**
605+
* The invitation code from the email.
606+
*/
607+
inviteCode: string
608+
}
609+
610+
/**
611+
* Arguments for joining a workspace.
612+
*/
613+
export type JoinWorkspaceArgs = {
614+
/**
615+
* Optional invitation code/link to join via.
616+
*/
617+
inviteCode?: string | null
618+
/**
619+
* Optional workspace ID to join via domain auto-join.
620+
*/
621+
workspaceId?: number | null
622+
}
623+
624+
/**
625+
* Arguments for uploading/updating workspace logo.
626+
*/
627+
export type WorkspaceLogoArgs = {
628+
/**
629+
* The workspace ID.
630+
*/
631+
workspaceId: number
632+
/**
633+
* The image file to upload (Buffer, Stream, or file path).
634+
*/
635+
file?: Buffer | NodeJS.ReadableStream | string
636+
/**
637+
* The file name (required for Buffer/Stream uploads).
638+
*/
639+
fileName?: string
640+
/**
641+
* Whether to delete the logo instead of updating it.
642+
*/
643+
delete?: boolean
644+
}
645+
646+
/**
647+
* Arguments for getting workspace plan details.
648+
*/
649+
export type GetWorkspacePlanDetailsArgs = {
650+
/**
651+
* The workspace ID.
652+
*/
653+
workspaceId: number
654+
}
655+
656+
/**
657+
* Arguments for getting workspace users (paginated).
658+
*/
659+
export type GetWorkspaceUsersArgs = {
660+
/**
661+
* Optional workspace ID. If not provided, returns users for all workspaces.
662+
*/
663+
workspaceId?: number | null
664+
/**
665+
* Cursor for pagination.
666+
*/
667+
cursor?: string | null
668+
/**
669+
* Maximum number of users to return (default: 100).
670+
*/
671+
limit?: number
672+
}
673+
674+
/**
675+
* Arguments for getting workspace projects (paginated).
676+
*/
677+
export type GetWorkspaceProjectsArgs = {
678+
/**
679+
* The workspace ID.
680+
*/
681+
workspaceId: number
682+
/**
683+
* Cursor for pagination.
684+
*/
685+
cursor?: string | null
686+
/**
687+
* Maximum number of projects to return (default: 100).
688+
*/
689+
limit?: number
690+
}
691+
692+
/**
693+
* Paginated response for workspace users.
694+
*/
695+
export type GetWorkspaceUsersResponse = {
696+
/**
697+
* Whether there are more users available.
698+
*/
699+
hasMore: boolean
700+
/**
701+
* Cursor for the next page of results.
702+
*/
703+
nextCursor?: string
704+
/**
705+
* Array of workspace users.
706+
*/
707+
workspaceUsers: import('./entities').WorkspaceUser[]
708+
}
709+
710+
/**
711+
* Response type for workspace invitations endpoint (simple email list).
712+
*/
713+
export type WorkspaceInvitationsResponse = string[]
714+
715+
/**
716+
* Response type for all workspace invitations endpoint (detailed objects).
717+
*/
718+
export type AllWorkspaceInvitationsResponse = import('./entities').WorkspaceInvitation[]
719+
720+
/**
721+
* Response type for workspace logo upload.
722+
*/
723+
export type WorkspaceLogoResponse = Record<string, unknown> | null

0 commit comments

Comments
 (0)