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:
@@ -0,0 +1,127 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getBaseUrl, instasentAuth } from '../../index';
|
||||
import { BOOLEAN_OPTIONS, IGNORED_ATTRIBUTES, LONG_TEXT_TYPES } from '../common/constants';
|
||||
|
||||
const PROPERTY_ITERATIONS = [
|
||||
{ check: (spec: any) => spec.requiredInWebhook },
|
||||
{ check: (spec: any) => spec.important },
|
||||
{ check: (spec: any) => spec.visible !== false },
|
||||
{ check: (spec: any) => !spec.custom },
|
||||
{ check: () => true }
|
||||
];
|
||||
|
||||
export const addOrUpdateContact = createAction({
|
||||
name: 'add_or_update_contact',
|
||||
displayName: 'Add/Update contact',
|
||||
description: 'Add or update a single contact',
|
||||
auth: instasentAuth,
|
||||
props: {
|
||||
contact: Property.DynamicProperties({
|
||||
auth: instasentAuth,
|
||||
displayName: 'Contact Properties',
|
||||
description: 'Enter the contact properties, the User ID is mandatory',
|
||||
required: true,
|
||||
refreshers: ['authentication'],
|
||||
props: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {};
|
||||
}
|
||||
const authData = auth;
|
||||
const baseUrl = getBaseUrl({
|
||||
projectId: authData.props.projectId,
|
||||
datasourceId: authData.props.datasourceId
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${baseUrl}/stream/specs/attributes`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authData.props.apiKey}`
|
||||
}
|
||||
});
|
||||
|
||||
const properties: Record<string, any> = {};
|
||||
|
||||
for (const iteration of PROPERTY_ITERATIONS) {
|
||||
for (const spec of response.body.specs) {
|
||||
// Ignored properties
|
||||
if (spec.readOnly || properties[spec.uid] || IGNORED_ATTRIBUTES.includes(spec.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multiple iterations to sort the properties based on their attributes
|
||||
if (!iteration.check(spec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const displayLabel = spec.displayLabel;
|
||||
let description = spec.description;
|
||||
if (spec.dataType === 'date') {
|
||||
description += ' (ISO 8601 format YYYY-MM-DD)';
|
||||
}
|
||||
if (spec.multivalue > 1) {
|
||||
properties[spec.uid] = Property.Array({
|
||||
displayName: displayLabel,
|
||||
description: `${description} (Max ${spec.multivalue} values)`,
|
||||
required: spec.requiredInWebhook
|
||||
});
|
||||
} else if (spec.dataType === 'bool') {
|
||||
properties[spec.uid] = Property.StaticDropdown({
|
||||
displayName: displayLabel,
|
||||
description: `${description} [0=false|1=true|null=unknown]`,
|
||||
required: spec.requiredInWebhook,
|
||||
options: {
|
||||
options: BOOLEAN_OPTIONS
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const PropType = LONG_TEXT_TYPES.includes(spec.dataType) ? Property.LongText : Property.ShortText;
|
||||
|
||||
properties[spec.uid] = PropType({
|
||||
displayName: displayLabel,
|
||||
description: description,
|
||||
required: spec.requiredInWebhook
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
} catch (error) {
|
||||
throw new Error('Failed to load contact properties');
|
||||
}
|
||||
}
|
||||
}),
|
||||
instant: Property.Checkbox({
|
||||
displayName: 'Instant',
|
||||
description: 'Process contact immediately instead of queuing. Only enable this when you need to add an event for this contact in the next step. Not recommended for high-volume operations.',
|
||||
required: false,
|
||||
defaultValue: false
|
||||
})
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const contact = context.propsValue.contact;
|
||||
const instant = context.propsValue.instant;
|
||||
const auth = context.auth;
|
||||
const baseUrl = getBaseUrl({ projectId: auth.props.projectId, datasourceId: auth.props.datasourceId });
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `${baseUrl}/stream/contacts${instant ? '?_sync' : ''}`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${auth.props.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: [contact]
|
||||
});
|
||||
|
||||
if (response.body.entitiesSuccess !== 1) {
|
||||
throw new Error(`Failed to add or update contact: ${JSON.stringify(response.body.errors)}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getBaseUrl, instasentAuth } from '../..';
|
||||
import { ApiResponse, EventParameter, EventSpec, InstasentAuthType } from '../common/types';
|
||||
import { BOOLEAN_OPTIONS } from '../common/constants';
|
||||
|
||||
export const createEvent = createAction({
|
||||
name: 'add_event',
|
||||
displayName: 'Add Event',
|
||||
description: 'Add a contact event',
|
||||
auth: instasentAuth,
|
||||
props: {
|
||||
user_id: Property.ShortText({
|
||||
displayName: 'User ID',
|
||||
description: 'Unique identifier of the user',
|
||||
required: true
|
||||
}),
|
||||
event_id: Property.ShortText({
|
||||
displayName: 'Event ID',
|
||||
description: 'Unique identifier for this event. Used for deduplication.',
|
||||
required: true
|
||||
}),
|
||||
event_date: Property.ShortText({
|
||||
displayName: 'Event Date',
|
||||
description: 'Date and time when the event occurred, will default to now (ISO 8601 format YYYY-MM-DDTHH:MM:SS.SSSZ)',
|
||||
required: false
|
||||
}),
|
||||
event_type: Property.Dropdown({
|
||||
auth: instasentAuth,
|
||||
displayName: 'Event Type',
|
||||
description: 'Select the type of event to create',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
const authData = auth;
|
||||
const baseUrl = getBaseUrl({
|
||||
projectId: authData.props.projectId,
|
||||
datasourceId: authData.props.datasourceId
|
||||
});
|
||||
|
||||
const response = await httpClient.sendRequest<{ specs: EventSpec[] }>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${baseUrl}/stream/specs/events`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authData.props.apiKey}`
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
options: response.body.specs.map(spec => ({
|
||||
label: `${spec.emoji} ${spec.name}`,
|
||||
value: spec.uid
|
||||
}))
|
||||
};
|
||||
}
|
||||
}),
|
||||
event_parameters: Property.DynamicProperties({
|
||||
auth: instasentAuth,
|
||||
displayName: 'Event Parameters',
|
||||
description: 'Parameters for the selected event type',
|
||||
required: true,
|
||||
refreshers: ['event_type'],
|
||||
props: async ({ auth, event_type }) => {
|
||||
if (!auth || !event_type) return {};
|
||||
const authData = auth;
|
||||
const baseUrl = getBaseUrl({
|
||||
projectId: authData.props.projectId,
|
||||
datasourceId: authData.props.datasourceId
|
||||
});
|
||||
|
||||
const response = await httpClient.sendRequest<{ specs: EventParameter[] }>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${baseUrl}/stream/specs/event-parameters/${event_type}`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authData.props.apiKey}`
|
||||
}
|
||||
});
|
||||
|
||||
const props: Record<string, any> = {};
|
||||
|
||||
response.body.specs.forEach(param => {
|
||||
if (param.multiValue > 1) {
|
||||
props[param.parameter] = Property.Array({
|
||||
displayName: param.title,
|
||||
description: `${param.description} (Max ${param.multiValue} values)`,
|
||||
required: param.required
|
||||
});
|
||||
} else {
|
||||
// Convert API parameter specs to ActivePieces properties
|
||||
switch (param.dataType) {
|
||||
case 'bool':
|
||||
props[param.parameter] = Property.StaticDropdown({
|
||||
displayName: param.title,
|
||||
description: `${param.description} [0=false|1=true|null=unknown]`,
|
||||
required: param.required,
|
||||
options: {
|
||||
options: BOOLEAN_OPTIONS
|
||||
}
|
||||
})
|
||||
break;
|
||||
case "string":
|
||||
case "payload":
|
||||
props[param.parameter] = Property.LongText({
|
||||
displayName: param.title,
|
||||
description: param.description,
|
||||
required: param.required
|
||||
});
|
||||
break;
|
||||
default:
|
||||
props[param.parameter] = Property.ShortText({
|
||||
displayName: param.title,
|
||||
description: param.description,
|
||||
required: param.required
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return props;
|
||||
}
|
||||
})
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const authData = auth as InstasentAuthType;
|
||||
const baseUrl = getBaseUrl({
|
||||
projectId: authData.props.projectId,
|
||||
datasourceId: authData.props.datasourceId
|
||||
});
|
||||
|
||||
const eventData = {
|
||||
_user_id: propsValue.user_id,
|
||||
_event_id: propsValue.event_id,
|
||||
_event_type: propsValue.event_type,
|
||||
_event_date: propsValue.event_date,
|
||||
_event_parameters: propsValue.event_parameters
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<ApiResponse<typeof eventData>>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${baseUrl}/stream/events`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authData.props.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: [eventData]
|
||||
});
|
||||
|
||||
if (response.body.entitiesSuccess !== 1) {
|
||||
throw new Error(`Failed to create event: ${JSON.stringify(response.body.errors)}`);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
|
||||
import { getBaseUrl, instasentAuth } from '../../index';
|
||||
|
||||
export const deleteContact = createAction({
|
||||
name: 'delete_contact',
|
||||
displayName: 'Delete Contact',
|
||||
description: 'Delete a single contact by User ID',
|
||||
auth: instasentAuth,
|
||||
props: {
|
||||
userId: Property.ShortText({
|
||||
displayName: 'User ID',
|
||||
description: 'Unique identifier of the contact to delete',
|
||||
required: true
|
||||
})
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const { userId } = context.propsValue;
|
||||
const auth = context.auth;
|
||||
const baseUrl = getBaseUrl({ projectId: auth.props.projectId, datasourceId: auth.props.datasourceId });
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.DELETE,
|
||||
url: `${baseUrl}/stream/contacts/${userId}`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${auth.props.apiKey}`
|
||||
}
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user