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,121 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { addGreet, addProfile } from '../api';
import { bonjoroAuth, BonjoroAuthType } from '../auth';
import {
buildCampaignDropdown,
buildTemplateDropdown,
buildUserDropdown,
} from '../props';
export const addGreetAction = createAction({
name: 'add_greet',
auth: bonjoroAuth,
displayName: 'Create a Greet',
description: 'Create a new Greet in Bonjoro',
props: {
note: Property.LongText({
displayName: 'Note',
description: 'Note to send with the greet',
required: true,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'Email to send the greet to',
required: true,
}),
first: Property.ShortText({
displayName: 'First Name',
description: 'First name of the person to greet',
required: false,
}),
last: Property.ShortText({
displayName: 'Last Name',
description: 'Last name of the person to greet',
required: false,
}),
assignee: Property.Dropdown({
auth: bonjoroAuth,
displayName: 'Assignee',
description: 'Who to assign the greet to',
required: false,
refreshers: [],
options: async ({ auth }) =>
{
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your Bonjoro account first',
};
}
return await buildUserDropdown(auth.props);
}
}),
campaign: Property.Dropdown({
auth: bonjoroAuth,
displayName: 'Campaign',
description: 'The campaign to add the greet to',
required: false,
refreshers: [],
options: async ({ auth }) =>
{
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your Bonjoro account first',
};
}
return await buildCampaignDropdown(auth.props);
}
}),
template: Property.Dropdown({
auth: bonjoroAuth,
displayName: 'Template',
description: 'The template to use for the greet',
required: false,
refreshers: [],
options: async ({ auth }) =>
{
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your Bonjoro account first',
};
}
return await buildTemplateDropdown(auth.props);
}
}),
custom: Property.Json({
displayName: 'Custom Attributes',
description: 'Enter custom attributes to send with the greet',
required: false,
defaultValue: {},
}),
},
async run(context) {
const user = {
email: context.propsValue.email,
first_name: context.propsValue.first,
last_name: context.propsValue.last,
};
addProfile(context.auth.props, user);
const greet = {
profiles: [context.propsValue.email],
note: context.propsValue.note,
assignee_id: context.propsValue.assignee,
campaign_id: context.propsValue.campaign,
template_id: context.propsValue.template,
custom_attributes: context.propsValue.custom,
};
if (!greet.assignee_id) delete greet.assignee_id;
if (!greet.campaign_id) delete greet.campaign_id;
if (!greet.template_id) delete greet.template_id;
if (!greet.custom_attributes) delete greet.custom_attributes;
return await addGreet(context.auth.props, greet);
},
});

View File

@@ -0,0 +1,66 @@
import {
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
import { BonjoroAuthType } from './auth';
export type KeyValuePair = {
[key: string]: string | boolean | object | undefined;
};
const bonjoroAPI = async (
api: string,
auth: BonjoroAuthType,
method: HttpMethod = HttpMethod.GET,
body: KeyValuePair = {}
) => {
const baseUrl = 'https://www.bonjoro.com/api/v2/';
const request: HttpRequest = {
body: body,
method: method,
url: `${baseUrl}${api}`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${auth.apiKey}`,
},
};
const response = await httpClient.sendRequest(request);
if (response.status > 201 || response.body['data'] === undefined) {
throw new Error(`Bonjoro API error: ${response.status} ${response.body}`);
}
let data = [];
data = response.body['data'];
return {
success: true,
data: data,
};
};
export async function getUsers(auth: BonjoroAuthType) {
const api = 'users';
return bonjoroAPI(api, auth);
}
export async function getCampaigns(auth: BonjoroAuthType) {
const api = 'campaigns';
return bonjoroAPI(api, auth);
}
export async function getTemplates(auth: BonjoroAuthType) {
const api = 'message-templates';
return bonjoroAPI(api, auth);
}
export async function addGreet(auth: BonjoroAuthType, data: KeyValuePair) {
const api = 'greets';
return bonjoroAPI(api, auth, HttpMethod.POST, data);
}
export async function addProfile(auth: BonjoroAuthType, data: KeyValuePair) {
const api = 'profiles';
return bonjoroAPI(api, auth, HttpMethod.POST, data);
}

View File

@@ -0,0 +1,43 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { getCampaigns } from './api';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
export type BonjoroAuthType = { apiKey: string };
export const bonjoroAuth = PieceAuth.CustomAuth({
description: 'Authenticate with your Bonjoro account',
props: {
apiKey: PieceAuth.SecretText({
displayName: 'API Key',
description: 'The API key for your Bonjoro account',
required: true,
}),
},
validate: async ({ auth }) => {
try {
await propsValidation.validateZod(auth, {
apiKey: z.string().min(1),
});
await validateAuth(auth);
return {
valid: true,
};
} catch (e) {
return {
valid: false,
error: (e as Error)?.message,
};
}
},
required: true,
});
const validateAuth = async (auth: BonjoroAuthType) => {
const response = await getCampaigns(auth);
if (response.success !== true) {
throw new Error(
'Authentication failed. Please check your domain and API key and try again.'
);
}
};

View File

@@ -0,0 +1,55 @@
import { BonjoroAuthType } from './auth';
import { getCampaigns, getTemplates, getUsers } from './api';
type BonjoroData = { id: string; name: string; uuid: string };
export async function buildCampaignDropdown(auth: BonjoroAuthType) {
if (!auth) {
return {
options: [],
disabled: true,
placeholder: 'Please authenticate first',
};
}
const response = await getCampaigns(auth as BonjoroAuthType);
const options = (response.data as BonjoroData[]).map((campaign) => {
return { label: campaign.name, value: campaign.uuid };
});
return {
options: options,
};
}
export async function buildTemplateDropdown(auth: BonjoroAuthType) {
if (!auth) {
return {
options: [],
disabled: true,
placeholder: 'Please authenticate first',
};
}
const response = await getTemplates(auth as BonjoroAuthType);
const options = (response.data as BonjoroData[]).map((template) => {
return { label: template.name, value: template.id };
});
return {
options: options,
};
}
export async function buildUserDropdown(auth: BonjoroAuthType) {
if (!auth) {
return {
options: [],
disabled: true,
placeholder: 'Please authenticate first',
};
}
const response = await getUsers(auth as BonjoroAuthType);
const options = (response.data as BonjoroData[]).map((user) => {
return { label: user.name, value: user.id };
});
return {
options: options,
};
}