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,142 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { systemeIoProps } from '../common/props';
export const addTagToContact = createAction({
auth: systemeIoAuth,
name: 'addTagToContact',
displayName: 'Add Tag to Contact',
description: 'Assign a tag to an existing contact - select an existing tag or create a new one',
props: {
contactId: systemeIoProps.contactIdDropdown,
tagSource: Property.StaticDropdown({
displayName: 'Tag Source',
description: 'Choose whether to use an existing tag or create a new one',
required: true,
defaultValue: 'existing',
options: {
disabled: false,
options: [
{ label: 'Use Existing Tag', value: 'existing' },
{ label: 'Create New Tag', value: 'new' },
],
},
}),
existingTagId: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Existing Tag',
description: 'Select an existing tag',
required: false,
refreshers: ['tagSource'],
options: async ({ auth, tagSource }) => {
if (!auth || tagSource !== 'existing') {
return {
disabled: true,
placeholder: tagSource === 'new' ? 'Not needed when creating new tag' : 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getTags({
auth: auth.secret_text,
});
let tags: any[] = [];
if (Array.isArray(response)) {
tags = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
tags = responseAny.items;
}
}
if (tags.length > 0) {
return {
disabled: false,
options: tags.map((tag: any) => ({
label: tag.name,
value: tag.id,
})),
};
}
return {
disabled: true,
placeholder: 'No tags found',
options: [],
};
} catch (error) {
console.error('Error fetching tags:', error);
return {
disabled: true,
placeholder: 'Error loading tags',
options: [],
};
}
},
}),
newTagName: Property.ShortText({
displayName: 'New Tag Name',
description: 'Enter the name for the new tag (only used when "Create New Tag" is selected)',
required: false,
}),
},
async run(context) {
const { contactId, tagSource, existingTagId, newTagName } = context.propsValue;
let tagId: string | number;
let tagCreated = false;
if (tagSource === 'new') {
if (!newTagName || newTagName.trim() === '') {
throw new Error('New Tag Name is required when "Create New Tag" is selected');
}
try {
const newTag = await systemeIoCommon.apiCall<{ id: number }>({
method: HttpMethod.POST,
url: '/tags',
body: {
name: newTagName.trim(),
},
auth: context.auth.secret_text,
});
tagId = newTag.id;
tagCreated = true;
} catch (error: any) {
throw new Error(`Failed to create tag: ${error.message}`);
}
} else {
if (!existingTagId) {
throw new Error('Please select an existing tag when "Use Existing Tag" is selected');
}
tagId = existingTagId;
}
const response = await systemeIoCommon.apiCall({
method: HttpMethod.POST,
url: `/contacts/${contactId}/tags`,
body: {
tagId: tagId,
},
auth: context.auth.secret_text,
});
return {
success: true,
contactId,
tagId,
tagCreated,
tagName: tagSource === 'new' ? newTagName : undefined,
message: tagCreated
? `New tag "${newTagName}" created and assigned to contact`
: 'Existing tag successfully assigned to contact',
response,
};
},
});

View File

@@ -0,0 +1,331 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { systemeIoProps } from '../common/props';
interface ContactField {
field: string;
value: string;
}
interface TagName {
name: string;
}
export const createContact = createAction({
auth: systemeIoAuth,
name: 'createContact',
displayName: 'Create Contact',
description: 'Create a new contact with email and contact fields from your Systeme.io account, with optional tags',
props: {
email: Property.ShortText({
displayName: 'Email',
description: 'Contact email address',
required: true,
}),
locale: Property.StaticDropdown({
displayName: 'Language',
description: 'Contact preferred language',
required: false,
defaultValue: 'en',
options: {
disabled: false,
options: [
{ label: 'English', value: 'en' },
{ label: 'French', value: 'fr' },
{ label: 'Spanish', value: 'es' },
{ label: 'Italian', value: 'it' },
{ label: 'Portuguese', value: 'pt' },
{ label: 'German', value: 'de' },
{ label: 'Dutch', value: 'nl' },
{ label: 'Russian', value: 'ru' },
{ label: 'Japanese', value: 'jp' },
{ label: 'Turkish', value: 'tr' },
{ label: 'Arabic', value: 'ar' },
{ label: 'Chinese', value: 'zh' },
{ label: 'Swedish', value: 'sv' },
{ label: 'Romanian', value: 'ro' },
{ label: 'Czech', value: 'cs' },
{ label: 'Hungarian', value: 'hu' },
{ label: 'Slovak', value: 'sk' },
{ label: 'Danish', value: 'dk' },
{ label: 'Indonesian', value: 'id' },
{ label: 'Polish', value: 'pl' },
{ label: 'Greek', value: 'el' },
{ label: 'Serbian', value: 'sr' },
{ label: 'Hindi', value: 'hi' },
{ label: 'Norwegian', value: 'no' },
{ label: 'Thai', value: 'th' },
{ label: 'Albanian', value: 'sq' },
{ label: 'Slovenian', value: 'sl' },
{ label: 'Ukrainian', value: 'ua' },
],
},
}),
dynamicContactFields: Property.DynamicProperties({
auth: systemeIoAuth,
displayName: 'Contact Fields',
description: 'Set contact fields from your Systeme.io account',
required: false,
refreshers: [],
props: async ({ auth }) => {
if (!auth) {
return {};
}
try {
const response = await systemeIoCommon.getContactFields({
auth: auth.secret_text,
});
let fields: any[] = [];
if (Array.isArray(response)) {
fields = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
fields = responseAny.items;
}
}
const dynamicProps: any = {};
for (const field of fields) {
dynamicProps[field.slug] = Property.ShortText({
displayName: field.fieldName || field.slug,
description: `Set ${field.fieldName || field.slug} for the new contact`,
required: false,
});
}
return dynamicProps;
} catch (error) {
console.error('Error fetching contact fields:', error);
return {};
}
},
}),
customFields: systemeIoProps.contactFields,
tagSource: Property.StaticDropdown({
displayName: 'Tag Source',
description: 'Choose how to handle tags for this contact',
required: false,
defaultValue: 'none',
options: {
disabled: false,
options: [
{ label: 'No Tags', value: 'none' },
{ label: 'Use Existing Tags', value: 'existing' },
{ label: 'Create New Tags', value: 'new' },
],
},
}),
existingTags: Property.MultiSelectDropdown({
auth: systemeIoAuth,
displayName: 'Existing Tags',
description: 'Select existing tags to assign',
required: false,
refreshers: ['tagSource'],
options: async ({ auth, tagSource }) => {
if (!auth || tagSource !== 'existing') {
return {
disabled: true,
placeholder: tagSource === 'new' ? 'Not needed when creating new tags' :
tagSource === 'none' ? 'Not needed when no tags selected' :
'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getTags({
auth: auth.secret_text,
});
let tags: any[] = [];
if (Array.isArray(response)) {
tags = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
tags = responseAny.items;
}
}
if (tags.length > 0) {
return {
disabled: false,
options: tags.map((tag: any) => ({
label: tag.name || tag.id,
value: tag.id,
})),
};
}
return {
disabled: true,
placeholder: 'No tags found',
options: [],
};
} catch (error) {
console.error('Error fetching tags:', error);
return {
disabled: true,
placeholder: 'Error loading tags',
options: [],
};
}
},
}),
newTagNames: Property.Array({
displayName: 'New Tag Names',
description: 'Enter names for new tags to create and assign (only used when "Create New Tags" is selected)',
required: false,
properties: {
name: Property.ShortText({
displayName: 'Tag Name',
description: 'Enter the tag name',
required: true,
}),
},
}),
},
async run(context) {
const {
email,
locale,
dynamicContactFields,
customFields,
tagSource,
existingTags,
newTagNames
} = context.propsValue;
const fields: any[] = [];
if (dynamicContactFields && typeof dynamicContactFields === 'object') {
const fieldsObj = dynamicContactFields as Record<string, any>;
for (const key in fieldsObj) {
if (Object.prototype.hasOwnProperty.call(fieldsObj, key)) {
const value = fieldsObj[key];
if (value !== undefined && value !== null && value !== '') {
fields.push({
slug: key,
value: String(value)
});
}
}
}
}
if (customFields && Array.isArray(customFields)) {
for (const field of customFields as ContactField[]) {
if (field.field && field.value) {
fields.push({
slug: field.field,
value: field.value
});
}
}
}
const contactData: any = {
email,
};
if (locale) contactData.locale = locale;
if (fields.length > 0) contactData.fields = fields;
const contact = await systemeIoCommon.apiCall<{ id: number }>({
method: HttpMethod.POST,
url: '/contacts',
body: contactData,
auth: context.auth.secret_text,
});
const tagResults = [];
if (tagSource === 'existing' && existingTags && existingTags.length > 0 && contact.id) {
for (const tagId of existingTags) {
try {
const tagResponse = await systemeIoCommon.apiCall({
method: HttpMethod.POST,
url: `/contacts/${contact.id}/tags`,
body: {
tagId: tagId,
},
auth: context.auth.secret_text,
});
tagResults.push({
tagId,
success: true,
response: tagResponse,
});
} catch (error) {
tagResults.push({
tagId,
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
}
} else if (tagSource === 'new' && newTagNames && Array.isArray(newTagNames)) {
for (const tagNameObj of newTagNames as TagName[]) {
const tagName = tagNameObj.name;
if (!tagName || tagName.trim() === '') continue;
try {
const tagResponse = await systemeIoCommon.apiCall<{ id: number }>({
method: HttpMethod.POST,
url: '/tags',
body: {
name: tagName.trim(),
},
auth: context.auth.secret_text,
});
if (tagResponse.id) {
const tagAssignResponse = await systemeIoCommon.apiCall({
method: HttpMethod.POST,
url: `/contacts/${contact.id}/tags`,
body: {
tagId: tagResponse.id,
},
auth: context.auth.secret_text,
});
tagResults.push({
tagId: tagResponse.id,
tagName: tagName,
success: true,
response: tagAssignResponse,
});
}
} catch (error) {
tagResults.push({
tagId: tagName,
tagName: tagName,
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
}
}
return {
contact,
tagResults,
tagSource,
totalTagsAssigned: tagResults.filter(t => t.success).length,
dynamicFieldsProcessed: dynamicContactFields ? Object.keys(dynamicContactFields).filter(key =>
dynamicContactFields[key] !== undefined &&
dynamicContactFields[key] !== null &&
dynamicContactFields[key] !== ''
).length : 0,
customFieldsProcessed: customFields ? customFields.length : 0,
newTagsCreated: tagSource === 'new' ? tagResults.filter(t => t.success).length : 0,
};
},
});

View File

@@ -0,0 +1,72 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
export const findContactByEmail = createAction({
auth: systemeIoAuth,
name: 'findContactByEmail',
displayName: 'Find Contact by Email',
description: 'Locate an existing contact by email address',
props: {
email: Property.ShortText({
displayName: 'Email',
description: 'The email address to search for',
required: true,
}),
},
async run(context) {
const { email } = context.propsValue;
const searchEmail = email.toLowerCase().trim();
const allContacts: any[] = [];
let hasMore = true;
let startingAfter: string | undefined;
while (hasMore) {
const response = await systemeIoCommon.getContacts({
auth: context.auth.secret_text,
limit: 100,
startingAfter,
});
let contacts: any[] = [];
let nextHasMore = false;
if (Array.isArray(response)) {
contacts = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
contacts = responseAny.items;
}
nextHasMore = responseAny.hasMore || false;
}
allContacts.push(...contacts);
if (nextHasMore && contacts.length > 0) {
startingAfter = contacts[contacts.length - 1].id?.toString();
} else {
hasMore = false;
}
}
const foundContact = allContacts.find(contact =>
contact.email && contact.email.toLowerCase().trim() === searchEmail
);
if (foundContact) {
return {
success: true,
contact: foundContact,
message: 'Contact found successfully',
};
} else {
return {
success: false,
contact: null,
message: `No contact found with email: ${email}`,
};
}
},
});

View File

@@ -0,0 +1,110 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { systemeIoProps } from '../common/props';
export const removeTagFromContact = createAction({
auth: systemeIoAuth,
name: 'removeTagFromContact',
displayName: 'Remove Tag from Contact',
description: 'Remove a tag that is currently assigned to an existing contact',
props: {
contactId: systemeIoProps.contactIdDropdown,
tagId: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Tag to Remove',
description: 'Select a tag currently assigned to this contact',
required: true,
refreshers: ['contactId'],
options: async ({ auth, contactId }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
if (!contactId) {
return {
disabled: true,
placeholder: 'Please select a contact first',
options: [],
};
}
try {
const contact = await systemeIoCommon.getContact({
contactId: contactId as string,
auth: auth.secret_text,
});
let contactTags: any[] = [];
if (contact && typeof contact === 'object' && (contact as any).tags) {
contactTags = (contact as any).tags;
}
if (contactTags.length > 0) {
return {
disabled: false,
options: contactTags.map((tag: any) => ({
label: tag.name,
value: tag.id,
})),
};
}
return {
disabled: true,
placeholder: 'This contact has no tags to remove',
options: [],
};
} catch (error) {
console.error('Error fetching contact tags:', error);
return {
disabled: true,
placeholder: 'Error loading contact tags',
options: [],
};
}
},
}),
},
async run(context) {
const { contactId, tagId } = context.propsValue;
let tagName = 'Unknown Tag';
try {
const contact = await systemeIoCommon.getContact({
contactId: contactId as string,
auth: context.auth.secret_text,
});
if (contact && typeof contact === 'object' && (contact as any).tags) {
const contactTags = (contact as any).tags;
const foundTag = contactTags.find((tag: any) => tag.id == tagId);
if (foundTag) {
tagName = foundTag.name;
}
}
} catch (error) {
console.warn('Could not fetch contact details for tag name:', error);
}
const response = await systemeIoCommon.apiCall({
method: HttpMethod.DELETE,
url: `/contacts/${contactId}/tags/${tagId}`,
auth: context.auth.secret_text,
});
return {
success: true,
contactId,
tagId,
tagName,
message: `Tag "${tagName}" successfully removed from contact`,
response,
};
},
});

View File

@@ -0,0 +1,149 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { systemeIoProps } from '../common/props';
interface ContactFieldUpdate {
field: string;
value: string;
}
export const updateContact = createAction({
auth: systemeIoAuth,
name: 'updateContact',
displayName: 'Update Contact',
description: 'Update fields (name, phone, custom fields) of an existing contact using fields from your Systeme.io account',
props: {
contactId: systemeIoProps.contactIdDropdown,
dynamicContactFields: Property.DynamicProperties({
auth: systemeIoAuth,
displayName: 'Contact Fields',
description: 'Select which contact fields to update',
required: false,
refreshers: ['contactId'],
props: async ({ auth, contactId }) => {
if (!auth) {
return {};
}
try {
const response = await systemeIoCommon.getContactFields({
auth: auth.secret_text,
});
let fields: any[] = [];
if (Array.isArray(response)) {
fields = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
fields = responseAny.items;
}
}
const dynamicProps: any = {};
for (const field of fields) {
dynamicProps[field.slug] = Property.ShortText({
displayName: field.fieldName || field.slug,
description: `Update ${field.fieldName || field.slug} (leave empty to keep current value)`,
required: false,
});
}
return dynamicProps;
} catch (error) {
console.error('Error fetching contact fields:', error);
return {};
}
},
}),
customFields: Property.Array({
displayName: 'Custom Fields (Manual Entry)',
description: 'Add or update custom fields with manual slug entry (use empty value to clear field)',
required: false,
properties: {
fieldSlug: Property.ShortText({
displayName: 'Field Slug',
description: 'The unique identifier for this field (e.g., custom_field_1, my_field)',
required: true,
}),
fieldValue: Property.ShortText({
displayName: 'Field Value',
description: 'The value for this field (leave empty to clear the field)',
required: false,
}),
},
}),
},
async run(context) {
const {
contactId,
dynamicContactFields,
customFields
} = context.propsValue;
const fields: any[] = [];
if (dynamicContactFields && typeof dynamicContactFields === 'object') {
const fieldsObj = dynamicContactFields as Record<string, any>;
for (const key in fieldsObj) {
if (Object.prototype.hasOwnProperty.call(fieldsObj, key)) {
const value = fieldsObj[key];
if (value !== undefined && value !== null && value !== '') {
fields.push({
slug: key,
value: String(value)
});
}
}
}
}
if (customFields && Array.isArray(customFields)) {
for (const customField of customFields as any[]) {
if (customField.fieldSlug) {
fields.push({
slug: customField.fieldSlug,
value: customField.fieldValue || null
});
}
}
}
const updateData: any = {};
if (fields.length > 0) updateData.fields = fields;
if (Object.keys(updateData).length === 0) {
return {
success: false,
message: 'No fields provided to update',
contactId,
};
}
const response = await systemeIoCommon.apiCall({
method: HttpMethod.PATCH,
url: `/contacts/${contactId}`,
body: updateData,
auth: context.auth.secret_text,
headers: {
'Content-Type': 'application/merge-patch+json',
},
});
return {
success: true,
contactId,
updatedFields: fields,
customFieldsProcessed: customFields ? customFields.length : 0,
dynamicFieldsProcessed: dynamicContactFields ? Object.keys(dynamicContactFields).filter(key =>
dynamicContactFields[key] !== undefined &&
dynamicContactFields[key] !== null &&
dynamicContactFields[key] !== ''
).length : 0,
response,
};
},
});

View File

@@ -0,0 +1,35 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { systemeIoCommon } from './client';
export const systemeIoAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: 'Your Systeme.io API key. You can find this in your Systeme.io dashboard under Profile Settings > Public API Keys.',
required: true,
validate: async ({ auth }) => {
try {
await systemeIoCommon.apiCall({
method: HttpMethod.GET,
url: '/tags',
auth: { apiKey: auth },
});
return {
valid: true,
message: 'API key validated successfully. Connected to Systeme.io.'
};
} catch (error: any) {
if (error.message.includes('401') || error.message.includes('403')) {
return {
valid: false,
error: 'Invalid API key. Please check your API key and try again.',
};
}
return {
valid: false,
error: `Authentication failed: ${error.message}. Please verify your API key is correct.`,
};
}
},
});

View File

@@ -0,0 +1,158 @@
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import crypto from 'crypto';
export const systemeIoCommon = {
baseUrl: 'https://api.systeme.io/api',
async apiCall<T>({
method,
url,
body,
auth,
headers,
}: {
method: HttpMethod;
url: string;
body?: any;
auth: string | { apiKey: string };
headers?: Record<string, string>;
}): Promise<T> {
const apiKey = typeof auth === 'string' ? auth : auth.apiKey;
const response = await httpClient.sendRequest<T>({
method,
url: `${this.baseUrl}${url}`,
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
...headers,
},
body,
});
if (response.status >= 400) {
throw new Error(`Systeme.io API error: ${response.status}`);
}
return response.body;
},
verifyWebhookSignature: (
webhookSecret?: string,
webhookSignatureHeader?: string,
webhookRawBody?: any,
): boolean => {
if (!webhookSecret || !webhookSignatureHeader || !webhookRawBody) {
return false;
}
try {
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(webhookRawBody);
const expectedSignature = hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(webhookSignatureHeader, 'hex'),
Buffer.from(expectedSignature, 'hex'),
);
} catch (error) {
return false;
}
},
async createWebhook({
eventType,
webhookUrl,
auth,
secret,
}: {
eventType: string;
webhookUrl: string;
auth: string | { apiKey: string };
secret: string;
}) {
return this.apiCall<{ id: string }>({
method: HttpMethod.POST,
url: '/webhooks',
body: {
name: `Activepieces Webhook - ${eventType}`,
url: webhookUrl,
subscriptions: [eventType],
secret: secret,
},
auth,
});
},
async deleteWebhook({
webhookId,
auth,
}: {
webhookId: string;
auth: string | { apiKey: string };
}) {
return this.apiCall({
method: HttpMethod.DELETE,
url: `/webhooks/${webhookId}`,
auth,
});
},
async getContacts({
auth,
limit = 50,
startingAfter,
}: {
auth: string | { apiKey: string };
limit?: number;
startingAfter?: string;
}) {
const params = new URLSearchParams();
if (limit) params.append('limit', limit.toString());
if (startingAfter) params.append('startingAfter', startingAfter);
return this.apiCall({
method: HttpMethod.GET,
url: `/contacts?${params.toString()}`,
auth,
});
},
async getContact({
contactId,
auth,
}: {
contactId: string;
auth: string | { apiKey: string };
}) {
return this.apiCall({
method: HttpMethod.GET,
url: `/contacts/${contactId}`,
auth,
});
},
async getTags({
auth,
}: {
auth: string | { apiKey: string };
}) {
return this.apiCall({
method: HttpMethod.GET,
url: '/tags',
auth,
});
},
async getContactFields({
auth,
}: {
auth: string | { apiKey: string };
}) {
return this.apiCall({
method: HttpMethod.GET,
url: '/contact_fields',
auth,
});
},
};

View File

@@ -0,0 +1,3 @@
export { systemeIoAuth } from './auth';
export { systemeIoCommon } from './client';
export { systemeIoProps } from './props';

View File

@@ -0,0 +1,361 @@
import { Property } from '@activepieces/pieces-framework';
import { systemeIoCommon } from './client';
import { systemeIoAuth } from './auth';
export const systemeIoProps = {
contactDropdown: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Contact',
description: 'Select a contact',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getContacts({
auth: auth.secret_text,
limit: 100,
});
let contacts: any[] = [];
if (Array.isArray(response)) {
contacts = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
contacts = responseAny.items;
}
}
if (contacts.length > 0) {
return {
disabled: false,
options: contacts.map((contact: any) => ({
label: `${contact.first_name || ''} ${contact.last_name || ''} (${contact.email})`.trim(),
value: contact.id,
})),
};
}
return {
disabled: true,
placeholder: 'No contacts found',
options: [],
};
} catch (error) {
console.error('Error fetching contacts:', error);
return {
disabled: true,
placeholder: 'Error loading contacts',
options: [],
};
}
},
}),
tagDropdown: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Tag',
description: 'Select a tag',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getTags({
auth: auth.secret_text,
});
let tags: any[] = [];
if (Array.isArray(response)) {
tags = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
tags = responseAny.items;
}
}
if (tags.length > 0) {
return {
disabled: false,
options: tags.map((tag: any) => ({
label: tag.name,
value: tag.id,
})),
};
}
return {
disabled: true,
placeholder: 'No tags found',
options: [],
};
} catch (error) {
console.error('Error fetching tags:', error);
return {
disabled: true,
placeholder: 'Error loading tags',
options: [],
};
}
},
}),
tagsMultiSelectDropdown: Property.MultiSelectDropdown({
auth: systemeIoAuth,
displayName: 'Tags',
description: 'Select tags to assign to the contact',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getTags({
auth: auth.secret_text,
});
let tags: any[] = [];
if (Array.isArray(response)) {
tags = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
tags = responseAny.items;
}
}
if (tags.length > 0) {
return {
disabled: false,
options: tags.map((tag: any) => ({
label: tag.name || tag.id,
value: tag.id,
})),
};
}
return {
disabled: true,
placeholder: 'No tags found',
options: [],
};
} catch (error) {
console.error('Error fetching tags:', error);
return {
disabled: true,
placeholder: 'Error loading tags',
options: [],
};
}
},
}),
contactIdDropdown: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Contact ID',
description: 'Select a contact by ID',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getContacts({
auth: auth.secret_text,
limit: 100,
});
let contacts: any[] = [];
if (Array.isArray(response)) {
contacts = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
contacts = responseAny.items;
}
}
if (contacts.length > 0) {
return {
disabled: false,
options: contacts.map((contact: any) => ({
label: `ID: ${contact.id} - ${contact.first_name || ''} ${contact.last_name || ''} (${contact.email})`.trim(),
value: contact.id,
})),
};
}
return {
disabled: true,
placeholder: 'No contacts found',
options: [],
};
} catch (error) {
console.error('Error fetching contacts:', error);
return {
disabled: true,
placeholder: 'Error loading contacts',
options: [],
};
}
},
}),
tagNameDropdown: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Tag Name',
description: 'Select a tag by name',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getTags({
auth: auth.secret_text,
});
let tags: any[] = [];
if (Array.isArray(response)) {
tags = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
tags = responseAny.items;
}
}
if (tags.length > 0) {
return {
disabled: false,
options: tags.map((tag: any) => ({
label: tag.name,
value: tag.name,
})),
};
}
return {
disabled: true,
placeholder: 'No tags found',
options: [],
};
} catch (error) {
console.error('Error fetching tags:', error);
return {
disabled: true,
placeholder: 'Error loading tags',
options: [],
};
}
},
}),
contactFields: Property.Array({
displayName: 'Custom Contact Fields',
description: 'Add custom contact field values (e.g., country, company, etc.)',
required: false,
properties: {
field: Property.ShortText({
displayName: 'Field Slug',
description: 'Enter the field slug (e.g., country, company, custom1)',
required: true,
}),
value: Property.ShortText({
displayName: 'Value',
description: 'Enter the field value',
required: false,
}),
},
}),
contactFieldDropdown: Property.Dropdown({
auth: systemeIoAuth,
displayName: 'Contact Field',
description: 'Select a contact field',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first',
options: [],
};
}
try {
const response = await systemeIoCommon.getContactFields({
auth: auth.secret_text,
});
let fields: any[] = [];
if (Array.isArray(response)) {
fields = response;
} else if (response && typeof response === 'object' && response !== null) {
const responseAny = response as any;
if (responseAny.items && Array.isArray(responseAny.items)) {
fields = responseAny.items;
}
}
if (fields.length > 0) {
return {
disabled: false,
options: fields.map((field: any) => ({
label: field.fieldName || field.slug,
value: field.slug,
})),
};
}
return {
disabled: true,
placeholder: 'No contact fields found',
options: [],
};
} catch (error) {
console.error('Error fetching contact fields:', error);
return {
disabled: true,
placeholder: 'Error loading contact fields',
options: [],
};
}
},
}),
};

View File

@@ -0,0 +1,88 @@
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { randomBytes } from 'crypto';
export const newContact = createTrigger({
auth: systemeIoAuth,
name: 'newContact',
displayName: 'New Contact',
description: 'Fires when a new contact is created',
props: {},
sampleData: {
contact: {
id: 12345,
email: "email@example.com",
registeredAt: "2024-01-01T00:00:00+00:00",
locale: "en",
sourceURL: null,
unsubscribed: false,
bounced: false,
needsConfirmation: false,
fields: [
{
fieldName: "first_name",
slug: "first_name",
value: "John"
},
{
fieldName: "last_name",
slug: "last_name",
value: "Doe"
},
{
fieldName: "phone_number",
slug: "phone_number",
value: "+1234567890"
}
],
tags: [
{
id: 1,
name: "new_customer"
},
{
id: 2,
name: "email_subscriber"
}
]
}
},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
const secret = randomBytes(32).toString('hex');
const response = await systemeIoCommon.createWebhook({
eventType: 'CONTACT_CREATED',
webhookUrl: context.webhookUrl,
auth: context.auth.secret_text,
secret: secret,
});
await context.store.put('new_contact_webhook_id', response.id);
await context.store.put('new_contact_webhook_secret', secret);
},
async onDisable(context) {
const webhookId = await context.store.get<string>('new_contact_webhook_id');
if (webhookId) {
await systemeIoCommon.deleteWebhook({
webhookId,
auth: context.auth.secret_text,
});
await context.store.put('new_contact_webhook_id', null);
await context.store.put('new_contact_webhook_secret', null);
}
},
async run(context) {
const webhookSecret = await context.store.get<string>('new_contact_webhook_secret');
const webhookSignatureHeader = context.payload.headers['x-webhook-signature'];
const rawBody = context.payload.rawBody;
if (!systemeIoCommon.verifyWebhookSignature(webhookSecret || undefined, webhookSignatureHeader, rawBody)) {
console.warn('Systeme.io webhook signature verification failed');
return [];
}
const payload = context.payload.body as any;
return [payload.contact || payload];
}
});

View File

@@ -0,0 +1,84 @@
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { randomBytes } from 'crypto';
export const newSale = createTrigger({
auth: systemeIoAuth,
name: 'newSale',
displayName: 'New Sale',
description: 'Fires when a new purchase is made within a funnel',
props: {},
sampleData: {
sale: {
id: 67890,
amount: 99.99,
currency: "USD",
status: "completed",
createdAt: "2024-01-01T00:00:00+00:00",
updatedAt: "2024-01-01T00:00:00+00:00",
product: {
id: 123,
name: "Premium Course",
type: "digital_product"
},
funnel: {
id: 456,
name: "Sales Funnel",
step: "checkout"
},
contact: {
id: 12345,
email: "customer@example.com",
firstName: "John",
lastName: "Doe"
},
payment: {
method: "stripe",
transactionId: "txn_1234567890",
gateway: "stripe"
},
affiliate: {
id: null,
commission: null
}
}
},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
const secret = randomBytes(32).toString('hex');
const response = await systemeIoCommon.createWebhook({
eventType: 'SALE_NEW',
webhookUrl: context.webhookUrl,
auth: context.auth.secret_text,
secret: secret,
});
await context.store.put('new_sale_webhook_id', response.id);
await context.store.put('new_sale_webhook_secret', secret);
},
async onDisable(context) {
const webhookId = await context.store.get<string>('new_sale_webhook_id');
if (webhookId) {
await systemeIoCommon.deleteWebhook({
webhookId,
auth: context.auth.secret_text,
});
await context.store.put('new_sale_webhook_id', null);
await context.store.put('new_sale_webhook_secret', null);
}
},
async run(context) {
const webhookSecret = await context.store.get<string>('new_sale_webhook_secret');
const webhookSignatureHeader = context.payload.headers['x-webhook-signature'];
const rawBody = context.payload.rawBody;
if (!systemeIoCommon.verifyWebhookSignature(webhookSecret || undefined, webhookSignatureHeader, rawBody)) {
console.warn('Systeme.io webhook signature verification failed');
return [];
}
const payload = context.payload.body as any;
return [payload.sale || payload];
}
});

View File

@@ -0,0 +1,93 @@
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { systemeIoAuth } from '../common/auth';
import { systemeIoCommon } from '../common/client';
import { randomBytes } from 'crypto';
export const newTagAddedToContact = createTrigger({
auth: systemeIoAuth,
name: 'newTagAddedToContact',
displayName: 'New Tag Added to Contact',
description: 'Fires when a specific tag is assigned to a contact',
props: {},
sampleData: {
contact: {
id: 12345,
email: "customer@example.com",
registeredAt: "2024-01-01T00:00:00+00:00",
locale: "en",
sourceURL: null,
unsubscribed: false,
bounced: false,
needsConfirmation: false,
fields: [
{
fieldName: "first_name",
slug: "first_name",
value: "John"
},
{
fieldName: "last_name",
slug: "last_name",
value: "Doe"
},
{
fieldName: "phone_number",
slug: "phone_number",
value: "+1234567890"
}
],
tags: [
{
id: 1,
name: "existing_customer"
},
{
id: 2,
name: "VIP Customer"
}
]
},
tag: {
id: 2,
name: "VIP Customer"
},
addedAt: "2024-01-01T10:30:00+00:00"
},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
const secret = randomBytes(32).toString('hex');
const response = await systemeIoCommon.createWebhook({
eventType: 'CONTACT_TAG_ADDED',
webhookUrl: context.webhookUrl,
auth: context.auth.secret_text ,
secret: secret,
});
await context.store.put('new_tag_added_webhook_id', response.id);
await context.store.put('new_tag_added_webhook_secret', secret);
},
async onDisable(context) {
const webhookId = await context.store.get<string>('new_tag_added_webhook_id');
if (webhookId) {
await systemeIoCommon.deleteWebhook({
webhookId,
auth: context.auth.secret_text,
});
await context.store.put('new_tag_added_webhook_id', null);
await context.store.put('new_tag_added_webhook_secret', null);
}
},
async run(context) {
const webhookSecret = await context.store.get<string>('new_tag_added_webhook_secret');
const webhookSignatureHeader = context.payload.headers['x-webhook-signature'];
const rawBody = context.payload.rawBody;
if (!systemeIoCommon.verifyWebhookSignature(webhookSecret || undefined, webhookSignatureHeader, rawBody)) {
console.warn('Systeme.io webhook signature verification failed');
return [];
}
const payload = context.payload.body as any;
return [payload];
}
});