Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,24 @@
import { taskadeAuth } from '../../';
import { createAction } from '@activepieces/pieces-framework';
import { taskadeProps } from '../common/props';
import { TaskadeAPIClient } from '../common/client';
export const completeTaskAction = createAction({
auth: taskadeAuth,
name: 'taskade-complete-task',
displayName: 'Complete Task',
description: 'Complete a task in a project.',
props: {
workspace_id: taskadeProps.workspace_id,
folder_id: taskadeProps.folder_id,
project_id: taskadeProps.project_id,
task_id: taskadeProps.task_id,
},
async run(context) {
const { project_id, task_id } = context.propsValue;
const client = new TaskadeAPIClient(context.auth.secret_text);
return await client.completeTask(project_id, task_id);
},
});

View File

@@ -0,0 +1,68 @@
import { taskadeAuth } from '../../';
import { createAction, Property } from '@activepieces/pieces-framework';
import { taskadeProps } from '../common/props';
import { TaskadeAPIClient } from '../common/client';
export const createTaskAction = createAction({
auth: taskadeAuth,
name: 'taskade-create-task',
displayName: 'Create Task',
description: 'Creates a new task.',
props: {
workspace_id: taskadeProps.workspace_id,
folder_id: taskadeProps.folder_id,
project_id: taskadeProps.project_id,
content_type: Property.StaticDropdown({
displayName: 'Content Type',
required: true,
defaultValue: 'text/markdown',
options: {
disabled: false,
options: [
{
label: 'text/markdown',
value: 'text/markdown',
},
{
label: 'text/plain',
value: 'text/plain',
},
],
},
}),
content: Property.LongText({
displayName: 'Task Content',
required: true,
}),
placement: Property.StaticDropdown({
displayName: 'Placement',
description: 'Placement of task in block',
required: true,
defaultValue: 'afterbegin',
options: {
disabled: false,
options: [
{
label: 'afterbegin',
value: 'afterbegin',
},
{
label: 'beforeend',
value: 'beforeend',
},
],
},
}),
},
async run(context) {
const { project_id, content_type, content, placement } = context.propsValue;
const client = new TaskadeAPIClient(context.auth.secret_text);
return await client.createTask(project_id, {
content,
contentType: content_type,
placement,
});
},
});

View File

@@ -0,0 +1,24 @@
import { taskadeAuth } from '../../';
import { createAction } from '@activepieces/pieces-framework';
import { taskadeProps } from '../common/props';
import { TaskadeAPIClient } from '../common/client';
export const deleteTaskAction = createAction({
auth: taskadeAuth,
name: 'taskade-delete-task',
displayName: 'Delete Task',
description: 'Delete an existing task in a project.',
props: {
workspace_id: taskadeProps.workspace_id,
folder_id: taskadeProps.folder_id,
project_id: taskadeProps.project_id,
task_id: taskadeProps.task_id,
},
async run(context) {
const { project_id, task_id } = context.propsValue;
const client = new TaskadeAPIClient(context.auth.secret_text);
return await client.deleteTask(project_id, task_id);
},
});

View File

@@ -0,0 +1,105 @@
import {
HttpMessageBody,
HttpMethod,
QueryParams,
httpClient,
HttpRequest,
AuthenticationType,
} from '@activepieces/pieces-common';
import {
CreateTaskDateParams,
CreateTaskParams,
ListAPIResponse,
ProjectResponse,
CreateTaskResponse,
WorkspaceFolderResponse,
WorkspaceResponse,
TaskResponse,
} from './types';
type RequestParams = Record<string, string | number | string[] | undefined>;
export class TaskadeAPIClient {
constructor(private personalToken: string) {}
async makeRequest<T extends HttpMessageBody>(
method: HttpMethod,
resourceUri: string,
query?: RequestParams,
body: any | undefined = undefined,
): Promise<T> {
const baseUrl = 'https://www.taskade.com/api/v1';
const qs: QueryParams = {};
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value !== null && value !== undefined) {
qs[key] = String(value);
}
}
}
const request: HttpRequest = {
method,
url: baseUrl + resourceUri,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: this.personalToken,
},
queryParams: qs,
body,
};
const response = await httpClient.sendRequest<T>(request);
return response.body;
}
async listWorkspaces(): Promise<ListAPIResponse<WorkspaceResponse>> {
return await this.makeRequest(HttpMethod.GET, '/workspaces');
}
async listWorkspaceFolders(
workspace_id: string,
): Promise<ListAPIResponse<WorkspaceFolderResponse>> {
return await this.makeRequest(HttpMethod.GET, `/workspaces/${workspace_id}/folders`);
}
async listProjects(folder_id: string): Promise<ListAPIResponse<ProjectResponse>> {
return await this.makeRequest(HttpMethod.GET, `/folders/${folder_id}/projects`);
}
async createTask(projectId: string, params: CreateTaskParams): Promise<CreateTaskResponse> {
return await this.makeRequest(HttpMethod.POST, `/projects/${projectId}/tasks`, undefined, {
tasks: [params],
});
}
async createTaskDate(projectId: string, taskId: string, params: CreateTaskDateParams) {
return await this.makeRequest(
HttpMethod.PUT,
`/projects/${projectId}/tasks/${taskId}/date`,
undefined,
params,
);
}
async listTasks(
projectId: string,
params: RequestParams,
): Promise<ListAPIResponse<TaskResponse>> {
return await this.makeRequest(HttpMethod.GET, `/projects/${projectId}/tasks`, params);
}
async completeTask(projectId: string, taskId: string) {
return await this.makeRequest(
HttpMethod.POST,
`/projects/${projectId}/tasks/${taskId}/complete`,
undefined,
{},
);
}
async deleteTask(projectId: string, taskId: string) {
return await this.makeRequest(HttpMethod.DELETE, `/projects/${projectId}/tasks/${taskId}`);
}
}

View File

@@ -0,0 +1,133 @@
import { DropdownOption, Property } from '@activepieces/pieces-framework';
import { TaskadeAPIClient } from './client';
import { taskadeAuth } from '../..';
const createEmptyOptions = (placeholder: string) => {
return {
disabled: true,
options: [],
placeholder,
};
};
export const taskadeProps = {
workspace_id: Property.Dropdown({
auth: taskadeAuth,
displayName: 'Workspace',
refreshers: [],
required: true,
options: async ({ auth }) => {
if (!auth) {
return createEmptyOptions('Please connect account first.');
}
const client = new TaskadeAPIClient(auth.secret_text);
const response = await client.listWorkspaces();
const options: DropdownOption<string>[] = [];
for (const workspace of response.items) {
options.push({ label: workspace.name, value: workspace.id });
}
return {
disabled: false,
options,
};
},
}),
folder_id: Property.Dropdown({
auth: taskadeAuth,
displayName: 'Folder',
refreshers: ['workspace_id'],
required: false,
options: async ({ auth, workspace_id }) => {
if (!auth) {
return createEmptyOptions('Please connect account first.');
}
if (!workspace_id) {
return createEmptyOptions('Please select workspace.');
}
const client = new TaskadeAPIClient(auth.secret_text);
const response = await client.listWorkspaceFolders(workspace_id as string);
const options: DropdownOption<string>[] = [];
for (const folder of response.items) {
options.push({ label: folder.name, value: folder.id });
}
return {
disabled: false,
options,
};
},
}),
project_id: Property.Dropdown({
auth: taskadeAuth,
displayName: 'Project',
refreshers: ['workspace_id', 'folder_id'],
required: true,
options: async ({ auth, workspace_id, folder_id }) => {
if (!auth) {
return createEmptyOptions('Please connect account first.');
}
if (!workspace_id) {
return createEmptyOptions('Please select workspace.');
}
const workspaceId = workspace_id as string;
const folderId = (folder_id as string) ?? workspaceId;
const client = new TaskadeAPIClient(auth.secret_text);
const response = await client.listProjects(folderId as string);
const options: DropdownOption<string>[] = [];
for (const project of response.items) {
options.push({ label: project.name, value: project.id });
}
return {
disabled: false,
options,
};
},
}),
task_id: Property.Dropdown({
auth: taskadeAuth,
displayName: 'Task',
refreshers: ['project_id'],
required: true,
options: async ({ auth, project_id }) => {
if (!auth) {
return createEmptyOptions('Please connect account first.');
}
if (!project_id) {
return createEmptyOptions('Please select project.');
}
const client = new TaskadeAPIClient(auth.secret_text);
const options: DropdownOption<string>[] = [];
let after;
let moreTasks = true;
while (moreTasks) {
const response = await client.listTasks(project_id as string, { limit: 100, after });
if (response.items.length === 0) {
moreTasks = false;
} else {
after = response.items[response.items.length - 1].id;
for (const task of response.items) {
options.push({ label: task.text, value: task.id });
}
}
}
return {
disabled: false,
options,
};
},
}),
};

View File

@@ -0,0 +1,42 @@
export interface ListAPIResponse<T> {
ok: boolean;
items: Array<T>;
}
export interface BaseResponse {
id: string;
name: string;
}
export type WorkspaceResponse = BaseResponse;
export type WorkspaceFolderResponse = BaseResponse;
export type ProjectResponse = BaseResponse;
export interface CreateTaskParams {
contentType: string;
content: string;
placement: string;
}
export interface TaskResponse {
id: string;
parentId: string;
text: string;
completed: boolean;
}
export interface CreateTaskResponse {
ok: boolean;
item: TaskResponse[];
}
export interface CreateTaskDateParams {
start: {
date: string;
time: string;
};
end?: {
date: string;
time: string;
};
}