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,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,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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.`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { systemeIoAuth } from './auth';
|
||||
export { systemeIoCommon } from './client';
|
||||
export { systemeIoProps } from './props';
|
||||
@@ -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: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -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];
|
||||
}
|
||||
});
|
||||
@@ -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];
|
||||
}
|
||||
});
|
||||
@@ -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];
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user