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,67 @@
import { createAction } from '@activepieces/pieces-framework';
import {
HttpRequest,
HttpMethod,
AuthenticationType,
httpClient,
} from '@activepieces/pieces-common';
import { RegistrationResponse } from '../common/models';
import { getRegistarantProps } from '../common/props';
import { zoomAuth } from '../..';
export const zoomCreateMeetingRegistrant = createAction({
auth: zoomAuth,
name: 'zoom_create_meeting_registrant',
displayName: 'Create Zoom Meeting Registrant',
description: "Create and submit a user's registration to a meeting.",
props: getRegistarantProps(),
async run(context) {
const body: Record<string, unknown> = {
first_name: context.propsValue.first_name,
last_name: context.propsValue.last_name,
email: context.propsValue.email,
address: context.propsValue.address,
city: context.propsValue.city,
state: context.propsValue.state,
zip: context.propsValue.zip,
country: context.propsValue.country,
phone: context.propsValue.phone,
comments: context.propsValue.comments,
industry: context.propsValue.industry,
job_title: context.propsValue.job_title,
no_of_employees: context.propsValue.no_of_employees,
org: context.propsValue.org,
purchasing_time_frame: context.propsValue.purchasing_time_frame,
role_in_purchase_process: context.propsValue.role_in_purchase_process,
};
if (
context.propsValue.custom_questions &&
Object.keys(context.propsValue.custom_questions).length > 0
) {
body.custom_questions = Object.entries(
context.propsValue.custom_questions
).map(([key, value]) => ({ title: key, value: value }));
}
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.zoom.us/v2/meetings/${context.propsValue.meeting_id}/registrants`,
body,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: context.auth.access_token,
},
queryParams: {},
};
const result = await httpClient.sendRequest<RegistrationResponse>(request);
console.debug('Meeting registration response', result);
if (result.status === 201) {
return result.body;
} else {
return result;
}
},
});

View File

@@ -0,0 +1,155 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import {
HttpRequest,
HttpMethod,
AuthenticationType,
httpClient,
} from '@activepieces/pieces-common';
import { MeetingMessageBody, MeetingResponseBody } from '../common/models';
import { zoomAuth } from '../..';
const defaults = {
agenda: 'My Meeting',
default_password: false,
duration: 30,
pre_schedule: false,
settings: {
allow_multiple_devices: true,
approval_type: 2,
audio: 'telephony',
calendar_type: 1,
close_registration: false,
email_notification: true,
host_video: true,
join_before_host: false,
meeting_authentication: true,
mute_upon_entry: false,
participant_video: false,
private_meeting: false,
registrants_confirmation_email: true,
registrants_email_notification: true,
registration_type: 1,
show_share_button: true,
host_save_video_order: true,
},
timezone: 'UTC',
type: 2,
};
const action = () => {
return createAction({
auth: zoomAuth,
name: 'zoom_create_meeting', // Must be a unique across the piece, this shouldn't be changed.
displayName: 'Create Zoom Meeting',
description: 'Create a new Zoom Meeting',
props: {
topic: Property.ShortText({
displayName: "Meeting's topic",
description: "The meeting's topic",
required: true,
}),
start_time: Property.ShortText({
displayName: 'Start Time',
description: 'Meeting start date-time',
required: false,
}),
duration: Property.Number({
displayName: 'Duration (in Minutes)',
description: 'Duration of the meeting',
required: false,
}),
auto_recording: Property.StaticDropdown({
displayName: 'Auto Recording',
required: false,
options: {
disabled: false,
options: [
{ label: 'Local', value: 'local' },
{ label: 'Cloud', value: 'cloud' },
{ label: 'None', value: 'none' },
],
},
}),
audio: Property.StaticDropdown({
displayName: 'Audio',
required: false,
options: {
disabled: false,
options: [
{ label: 'Both telephony and VoIP', value: 'both' },
{ label: 'Telephony only', value: 'telephony' },
{ label: 'VoIP only', value: 'voip' },
{ label: 'Third party audio conference', value: 'thirdParty' },
],
},
}),
agenda: Property.LongText({
displayName: 'Agenda',
description: "The meeting's agenda",
required: false,
}),
password: Property.ShortText({
displayName: 'Password',
description:
'The password required to join the meeting. By default, a password can only have a maximum length of 10 characters and only contain alphanumeric characters and the @, -, _, and * characters.',
required: false,
}),
pre_schedule: Property.Checkbox({
displayName: 'Pre Schedule',
description:
'Whether the prescheduled meeting was created via the GSuite app.',
required: false,
}),
schedule_for: Property.ShortText({
displayName: 'Schedule for',
description:
'The email address or user ID of the user to schedule a meeting for.',
required: false,
}),
join_url: Property.LongText({
displayName: 'Join URL',
description: 'URL for participants to join the meeting.',
required: false,
}),
},
async run(context) {
const body: MeetingMessageBody = {
...defaults,
...context.propsValue,
};
if (context.propsValue.auto_recording) {
body.settings.auto_recording = context.propsValue.auto_recording;
}
if (context.propsValue.audio) {
body.settings.audio = context.propsValue.audio;
}
delete body['auth'];
const request: HttpRequest<MeetingMessageBody> = {
method: HttpMethod.POST,
url: `https://api.zoom.us/v2/users/me/meetings`,
body: body,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: context.auth.access_token,
},
queryParams: {},
};
const result = await httpClient.sendRequest<MeetingResponseBody>(request);
if (result.status === 201) {
return result.body;
} else {
return result;
}
},
});
};
export const zoomCreateMeeting = action();

View File

@@ -0,0 +1,252 @@
import { HttpMessageBody } from '@activepieces/pieces-common';
export interface MeetingRegistrant {
first_name: string;
last_name?: string;
email: string;
address?: string;
city?: string;
state?: string;
zip?: string;
country?: string;
phone?: string;
comments?: string;
custom_questions?: {
title: string;
value: string;
}[];
industry?: string;
job_title?: string;
no_of_employees?: string;
org?: string;
purchasing_time_frame?: string;
role_in_purchase_process?: string;
language?: string;
auto_approve?: boolean;
}
export interface RegistrationResponse extends HttpMessageBody {
id: number;
join_url: string;
registrant_id: string;
start_time: string;
topic: string;
occurrences: {
duration: number;
occurrence_id: string;
start_time: string;
status: string;
}[];
participant_pin_code: number;
}
export interface MeetingResponseBody extends HttpMessageBody {
id?: number;
assistant_id?: string;
host_email?: string;
registration_url?: string;
agenda?: string;
created_at?: string;
duration?: number;
join_url?: string;
occurrences?: {
duration?: number;
occurrence_id?: string;
start_time?: string;
status?: string;
}[];
password?: string;
pmi?: string;
pre_schedule?: boolean;
recurrence?: {
end_date_time?: string;
end_times?: number;
monthly_day?: number;
monthly_week?: number;
monthly_week_day?: number;
repeat_interval?: number;
type: number;
weekly_days?: string;
};
settings: {
allow_multiple_devices: boolean;
alternative_hosts: number;
alternative_hosts_email_notification: boolean;
alternative_host_update_polls: boolean;
approval_type: number;
approved_or_denied_countries_or_regions: {
approved_list: string[];
denied_list: string[];
enable: boolean;
method: string;
};
audio: string;
authentication_domains: string;
authentication_exception: {
email: string;
name: string;
join_url: string;
}[];
authentication_name: string;
authentication_option: string;
auto_recording: string;
breakout_room: {
enable: boolean;
rooms: {
name: string;
participants: string[];
}[];
};
calendar_type: number;
close_registration: boolean;
contact_email: string;
contact_name: string;
custom_keys: {
key: string;
value: string;
}[];
email_notification: boolean;
encryption_type: string;
focus_mode: boolean;
global_dial_in_countries: string[];
global_dial_in_numbers: {
city: string;
country: string;
country_name: string;
number: string;
type: string;
}[];
host_video: boolean;
jbh_time: number;
join_before_host: boolean;
language_interpretation: {
enable: boolean;
interpreters: {
email: string;
languages: string;
}[];
};
meeting_authentication: boolean;
mute_upon_entry: boolean;
participant_video: boolean;
private_meeting: boolean;
registrants_confirmation_email: boolean;
registrants_email_notification: boolean;
registration_type: number;
show_share_button: boolean;
use_pmi: boolean;
waiting_room: boolean;
watermark: boolean;
host_save_video_order: boolean;
};
start_time: string;
start_url: string;
timezone: string;
topic: string;
type: number;
tracking_fields: {
field: string;
value: string;
visible: boolean;
}[];
}
export interface MeetingMessageBody extends HttpMessageBody {
agenda?: string;
password?: string;
duration?: number;
pre_schedule?: boolean;
recurrence?: {
end_date_time: string;
end_times: number;
monthly_day: number;
monthly_week: number;
monthly_week_day: number;
repeat_interval: number;
type: number;
weekly_days: string;
};
settings: {
allow_multiple_devices?: boolean;
alternative_hosts?: number;
alternative_hosts_email_notification?: boolean;
alternative_host_update_polls?: boolean;
approval_type?: number;
approved_or_denied_countries_or_regions?: {
approved_list?: string[];
denied_list?: string[];
enable?: boolean;
method?: string;
};
audio?: string;
authentication_domains?: string;
authentication_exception?: {
email?: string;
name?: string;
join_url?: string;
}[];
authentication_name?: string;
authentication_option?: string;
auto_recording?: string;
breakout_room?: {
enable?: boolean;
rooms?: {
name: string;
participants: string[];
}[];
};
calendar_type?: number;
close_registration?: boolean;
contact_email?: string;
contact_name?: string;
custom_keys?: {
key?: string;
value?: string;
}[];
email_notification?: boolean;
encryption_type?: string;
focus_mode?: boolean;
global_dial_in_countries?: string[];
global_dial_in_numbers?: {
city?: string;
country?: string;
country_name?: string;
number?: string;
type?: string;
}[];
host_video?: boolean;
jbh_time?: number;
join_before_host?: boolean;
language_interpretation?: {
enable?: boolean;
interpreters?: {
email?: string;
languages?: string;
}[];
};
meeting_authentication?: boolean;
mute_upon_entry?: boolean;
participant_video?: boolean;
private_meeting?: boolean;
registrants_confirmation_email?: boolean;
registrants_email_notification?: boolean;
registration_type?: number;
show_share_button?: boolean;
use_pmi?: boolean;
waiting_room?: boolean;
watermark?: boolean;
host_save_video_order?: boolean;
};
start_time?: string;
start_url?: string;
timezone?: string;
topic?: string;
type?: number;
tracking_fields?: {
field: string;
value?: string;
visible?: boolean;
}[];
}

View File

@@ -0,0 +1,126 @@
import { Property } from '@activepieces/pieces-framework';
export const getRegistarantProps = () => ({
meeting_id: Property.ShortText({
displayName: 'Meeting ID',
description: 'The meeting ID.',
required: true,
}),
first_name: Property.ShortText({
displayName: 'First name',
description: "The registrant's first name.",
required: true,
}),
last_name: Property.ShortText({
displayName: 'Last name',
description: "The registrant's last name.",
required: false,
}),
email: Property.ShortText({
displayName: 'Email',
description: "The registrant's email address.",
required: true,
}),
address: Property.ShortText({
displayName: 'Address',
description: "The registrant's address",
required: false,
}),
city: Property.ShortText({
displayName: 'City',
description: "The registrant's city",
required: false,
}),
state: Property.ShortText({
displayName: 'State',
description: "The registrant's state or province.",
required: false,
}),
zip: Property.ShortText({
displayName: 'Zip',
description: "The registrant's zip or postal code.",
required: false,
}),
country: Property.ShortText({
displayName: 'Country',
description: "The registrant's two-letter country code.",
required: false,
}),
phone: Property.ShortText({
displayName: 'Phone',
description: "The registrant's phone number.",
required: false,
}),
comments: Property.LongText({
displayName: 'Comments',
description: "The registrant's questions and comments.",
required: false,
}),
custom_questions: Property.Object({
displayName: 'Custom questions',
description: '',
required: false,
}),
industry: Property.ShortText({
displayName: 'Industry',
description: "The registrant's industry.",
required: false,
}),
job_title: Property.ShortText({
displayName: 'Job title',
description: "The registrant's job title.",
required: false,
}),
no_of_employees: Property.StaticDropdown({
displayName: 'No of employees',
description: "The registrant's number of employees.",
required: false,
options: {
disabled: false,
options: [
{ label: '1-20', value: '1-20' },
{ label: '21-50', value: '21-50' },
{ label: '51-100', value: '51-100' },
{ label: '101-500', value: '101-500' },
{ label: '500-1,000', value: '500-1,000' },
{ label: '1,001-5,000', value: '1,001-5,000' },
{ label: '5,001-10,000', value: '5,001-10,000' },
{ label: 'More than 10,000', value: 'More than 10,000' },
],
},
}),
org: Property.ShortText({
displayName: 'Organization',
description: "The registrant's organization.",
required: false,
}),
purchasing_time_frame: Property.StaticDropdown({
displayName: 'Purchasing time frame',
description: "The registrant's purchasing time frame.",
required: false,
options: {
disabled: false,
options: [
{ label: 'Within a month', value: 'Within a month' },
{ label: '1-3 months', value: '1-3 months' },
{ label: '4-6 months', value: '4-6 months' },
{ label: 'More than 6 months', value: 'More than 6 months' },
{ label: 'No timeframe', value: 'No timeframe' },
],
},
}),
role_in_purchase_process: Property.StaticDropdown({
displayName: 'Role in purchase process',
description: "The registrant's role in the purchase process.",
required: false,
options: {
disabled: false,
options: [
{ label: 'Decision Maker', value: 'Decision Maker' },
{ label: 'Evaluator/Recommender', value: 'Evaluator/Recommender' },
{ label: 'Influencer', value: 'Influencer' },
{ label: 'Not involved', value: 'Not involved' },
],
},
}),
});