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,22 @@
import { createAction } from '@activepieces/pieces-framework';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
export const createContactFolder = createAction({
auth: microsoft365PeopleAuth,
name: 'createContactFolder',
displayName: 'Create a Contact Folder',
description: 'Organize contacts by adding a new contact folder.',
props: microsoft365PeopleCommon.contactFolderProperties(),
async run({ auth, propsValue }) {
const contactFolder = {
displayName: propsValue.displayName,
parentFolderId: propsValue.parentFolder,
};
return await microsoft365PeopleCommon.createContactFolder({
auth,
contactFolder,
});
},
});

View File

@@ -0,0 +1,75 @@
import { createAction } from '@activepieces/pieces-framework';
import { Contact, EmailAddress } from '@microsoft/microsoft-graph-types';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
export const createContact = createAction({
auth: microsoft365PeopleAuth,
name: 'createContact',
displayName: 'Create a Contact',
description:'Create a new contact in People with detailed attributes (email, phone, address, etc.).',
props: microsoft365PeopleCommon.contactProperties(),
async run({ auth, propsValue }) {
const childrenNames: string[] =
(propsValue.children as Array<{ name: string }> | undefined)?.map(
(child) => child.name
) ?? [];
const emailAddresses: EmailAddress[] =
(propsValue.emailAddresses as Array<EmailAddress> | undefined) ?? [];
const imAddresses: string[] =
(propsValue.imAddresses as Array<{ address: string }> | undefined)?.map(
(im) => im.address
) ?? [];
const contact: Contact = {
assistantName: propsValue.assistantName,
birthday: propsValue.birthday,
businessAddress: {
street: propsValue.businessStreet,
city: propsValue.businessCity,
state: propsValue.businessState,
postalCode: propsValue.businessPostalCode,
countryOrRegion: propsValue.businessCountryOrRegion,
},
children: childrenNames,
companyName: propsValue.companyName,
department: propsValue.department,
displayName: propsValue.displayName,
emailAddresses: emailAddresses,
givenName: propsValue.givenName,
homeAddress: {
street: propsValue.homeStreet,
city: propsValue.homeCity,
state: propsValue.homeState,
postalCode: propsValue.homePostalCode,
countryOrRegion: propsValue.homeCountryOrRegion,
},
imAddresses,
initials: propsValue.initials,
jobTitle: propsValue.jobTitle,
manager: propsValue.manager,
middleName: propsValue.middleName,
mobilePhone: propsValue.mobilePhone,
nickName: propsValue.nickName,
officeLocation: propsValue.officeLocation,
otherAddress: {
street: propsValue.otherStreet,
city: propsValue.otherCity,
state: propsValue.otherState,
postalCode: propsValue.otherPostalCode,
countryOrRegion: propsValue.otherCountryOrRegion,
},
parentFolderId: propsValue.parentFolder,
personalNotes: propsValue.personalNotes,
profession: propsValue.profession,
spouseName: propsValue.spouseName,
surname: propsValue.surname,
title: propsValue.title,
};
return await microsoft365PeopleCommon.createContact({
auth,
contact,
});
},
});

View File

@@ -0,0 +1,24 @@
import { createAction } from '@activepieces/pieces-framework';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
export const deleteContact = createAction({
auth: microsoft365PeopleAuth,
name: 'deleteContact',
displayName: 'Delete a Contact',
description: 'Permanently remove a contact.',
props: {
contactId: microsoft365PeopleCommon.contactDropdown(),
},
async run(context) {
const { contactId } = context.propsValue;
if (!contactId) {
throw new Error('Contact ID is required.');
}
return await microsoft365PeopleCommon.deleteContact({
auth: context.auth,
contactId,
});
},
});

View File

@@ -0,0 +1,20 @@
import { createAction } from '@activepieces/pieces-framework';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
export const getContactFolder = createAction({
auth: microsoft365PeopleAuth,
name: 'getContactFolder',
displayName: 'Get a Contact Folder',
description: 'Retrieve metadata (name, ID) of a specified contact folder.',
props: { contactFolder: microsoft365PeopleCommon.contactFolderDropdown("Contact Folder", "Select a contact folder", true) },
async run({ auth, propsValue }) {
if (!propsValue.contactFolder) {
throw new Error('Contact folder is required.');
}
return await microsoft365PeopleCommon.getContactFolder({
auth,
contactFolderId: propsValue.contactFolder,
});
},
});

View File

@@ -0,0 +1,24 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
export const searchContacts = createAction({
auth: microsoft365PeopleAuth,
name: 'searchContacts',
displayName: 'Search Contacts',
description: 'Find contacts by name, email, or other properties.',
props: {
searchValue: Property.ShortText({
displayName: 'Search Value',
description: 'Find contacts by name, email, or other properties.',
required: true,
}),
},
async run({ auth, propsValue }) {
const { searchValue } = propsValue;
return microsoft365PeopleCommon.listContacts({ auth, queryParams: {
$search: `"${searchValue}"`,
} });
},
});

View File

@@ -0,0 +1,84 @@
import { createAction } from '@activepieces/pieces-framework';
import { Contact, EmailAddress } from '@microsoft/microsoft-graph-types';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
export const updateContact = createAction({
auth: microsoft365PeopleAuth,
name: 'updateContact',
displayName: 'Update a Contact',
description: 'Modify fields of an existing contact.',
props: {
contactId: microsoft365PeopleCommon.contactDropdown(),
...microsoft365PeopleCommon.contactProperties(),
},
async run({ auth, propsValue }) {
const contactId = propsValue.contactId;
if (!contactId || typeof contactId !== 'string') {
throw new Error('contactId is required.');
}
const childrenNames: string[] | undefined = (
propsValue.children as Array<{ name: string }> | undefined
)?.map((child) => child.name);
const emailAddresses: EmailAddress[] | undefined =
propsValue.emailAddresses as Array<EmailAddress> | undefined;
const imAddresses: string[] | undefined = (
propsValue.imAddresses as Array<{ address: string }> | undefined
)?.map((im) => im.address);
// Use the JSON methods to create the contact object without any undefined values
const contact: Contact = JSON.parse(
JSON.stringify({
assistantName: propsValue.assistantName,
birthday: propsValue.birthday,
businessAddress: {
street: propsValue.businessStreet,
city: propsValue.businessCity,
state: propsValue.businessState,
postalCode: propsValue.businessPostalCode,
countryOrRegion: propsValue.businessCountryOrRegion,
},
children: childrenNames,
companyName: propsValue.companyName,
department: propsValue.department,
displayName: propsValue.displayName,
emailAddresses: emailAddresses,
givenName: propsValue.givenName,
homeAddress: {
street: propsValue.homeStreet,
city: propsValue.homeCity,
state: propsValue.homeState,
postalCode: propsValue.homePostalCode,
countryOrRegion: propsValue.homeCountryOrRegion,
},
imAddresses,
initials: propsValue.initials,
jobTitle: propsValue.jobTitle,
manager: propsValue.manager,
middleName: propsValue.middleName,
mobilePhone: propsValue.mobilePhone,
nickName: propsValue.nickName,
officeLocation: propsValue.officeLocation,
otherAddress: {
street: propsValue.otherStreet,
city: propsValue.otherCity,
state: propsValue.otherState,
postalCode: propsValue.otherPostalCode,
countryOrRegion: propsValue.otherCountryOrRegion,
},
parentFolderId: propsValue.parentFolder,
personalNotes: propsValue.personalNotes,
profession: propsValue.profession,
spouseName: propsValue.spouseName,
surname: propsValue.surname,
title: propsValue.title,
})
);
return microsoft365PeopleCommon.updateContact({
auth,
contactId,
contact,
});
},
});

View File

@@ -0,0 +1,37 @@
import { PieceAuth } from "@activepieces/pieces-framework";
const authDesc = `
1. Sign in to [Microsoft Azure Portal](https://portal.azure.com/).
2. From the left sidebar, go to **Microsoft Enfra ID**.
3. Under **Manage**, click on **App registrations**.
4. Click the **New registration** button.
5. Enter a **Name** for your app.
6. For **Supported account types**, choose:
- **Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts**
- Or select based on your requirement.
7. In **Redirect URI**, select **Web** and add the given URL.
8. Click **Register**.
9. After registration, youll be redirected to the apps overview page. Copy the **Application (client) ID**.
10. From the left menu, go to **Certificates & secrets**.
- Under **Client secrets**, click **New client secret**.
- Provide a description, set an expiry, and click **Add**.
- Copy the **Value** of the client secret (this will not be shown again).
11. Go to **API permissions** from the left menu.
- Click **Add a permission**.
- Select **Microsoft Graph** → **Delegated permissions**.
- Add the following scopes:
- Contacts.ReadWrite
- offline_access
- Click **Add permissions**.
12. Copy your **Client ID** and **Client Secret**.
`
export const microsoft365PeopleAuth = PieceAuth.OAuth2({
description: authDesc,
authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
required: true,
scope: ['Contacts.ReadWrite', "offline_access"],
prompt: 'omit',
});

View File

@@ -0,0 +1,376 @@
import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
import { Client } from '@microsoft/microsoft-graph-client';
import { Contact, ContactFolder } from '@microsoft/microsoft-graph-types';
import { microsoft365PeopleAuth } from './auth';
export type authProps = { auth: OAuth2PropertyValue };
type createContactProps = authProps & { contact: Contact };
type listContactProps = authProps & { queryParams?: Record<string, any> };
type updateContactProps = authProps & { contactId: string; contact: Contact };
type getContactProps = authProps & { contactId: string };
type deleteContactProps = getContactProps;
type createContactFolderProps = authProps & { contactFolder: ContactFolder };
type getContactFolderProps = authProps & { contactFolderId: string };
type deleteContactFolderProps = getContactFolderProps;
export const microsoft365PeopleCommon = {
// Initialize Microsoft Graph client
getClient: ({ auth }: authProps) => {
return Client.initWithMiddleware({
authProvider: {
getAccessToken: () => Promise.resolve(auth.access_token),
},
});
},
// Logged user profile
getMe: async ({ auth }: authProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
const user = await client.api('/me').get();
return user;
},
// Contact methods
getContact: async ({ auth, contactId }: getContactProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
const response = await client.api(`/me/contacts/${contactId}`).get();
return response;
},
listContacts: async ({
auth,
queryParams,
}: listContactProps): Promise<Contact[]> => {
const client = microsoft365PeopleCommon.getClient({ auth });
let apiRequest = client.api('/me/contacts');
if (queryParams) {
Object.entries(queryParams).forEach(([key, value]) => {
apiRequest = apiRequest.query({ [key]: value });
});
}
const response = await apiRequest.get();
return response.value;
},
createContact: async ({ auth, contact }: createContactProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
const response = await client.api('/me/contacts').post(contact);
return response;
},
updateContact: async ({ auth, contactId, contact }: updateContactProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
const response = await client
.api(`/me/contacts/${contactId}`)
.patch(contact);
return response;
},
deleteContact: async ({ auth, contactId }: deleteContactProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
await client.api(`/me/contacts/${contactId}`).delete();
return { success: true };
},
// Contact folder methods
listContactFolders: async ({ auth }: authProps): Promise<ContactFolder[]> => {
const client = microsoft365PeopleCommon.getClient({ auth });
const response = await client.api('/me/contactFolders').get();
return response.value;
},
createContactFolder: async ({
auth,
contactFolder,
}: createContactFolderProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
const response = await client.api('/me/contactFolders').post(contactFolder);
return response;
},
getContactFolder: async ({
auth,
contactFolderId,
}: getContactFolderProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
const response = await client
.api(`/me/contactFolders/${contactFolderId}`)
.get();
return response;
},
deleteContactFolder: async ({
auth,
contactFolderId,
}: deleteContactFolderProps) => {
const client = microsoft365PeopleCommon.getClient({ auth });
await client.api(`/me/contactFolders/${contactFolderId}`).delete();
return { success: true };
},
// Dropdowns
contactDropdown: (
displayName = 'Contact',
description = 'Select a Contact',
required = true
) =>
Property.Dropdown({
auth: microsoft365PeopleAuth,
displayName,
description,
required,
refreshers: ['auth'],
refreshOnSearch: false,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
};
}
const contacts = await microsoft365PeopleCommon.listContacts({
auth: auth as OAuth2PropertyValue,
});
const options = contacts.map((contact) => ({
label: contact.displayName ?? '',
value: contact.id,
}));
return {
placeholder:
options.length === 0
? "You don't have any contacts."
: 'Select a contact',
options: options,
disabled: options.length === 0,
};
},
}),
contactFolderDropdown: (
displayName = 'Contact Folder',
description = 'Select an option',
required = true
) =>
Property.Dropdown({
auth: microsoft365PeopleAuth,
displayName,
description,
required,
refreshers: ['auth'],
refreshOnSearch: false,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
};
}
const contactFolders =
await microsoft365PeopleCommon.listContactFolders({
auth: auth as OAuth2PropertyValue,
});
const options = contactFolders.map((folder) => ({
label: folder.displayName ?? '',
value: folder.id,
}));
return {
placeholder:
options.length === 0
? "You don't have any folders created."
: 'Select a folder',
options: options,
disabled: options.length === 0,
};
},
}),
// Properties
contactProperties: () => ({
displayName: Property.ShortText({
displayName: 'Display Name',
required: false,
}),
givenName: Property.ShortText({
displayName: 'Given Name',
required: false,
}),
middleName: Property.ShortText({
displayName: 'Middle Name',
required: false,
}),
surname: Property.ShortText({
displayName: 'Surname',
required: false,
}),
emailAddresses: Property.Array({
displayName: 'Email Addresses',
required: false,
properties: {
address: Property.ShortText({
displayName: 'Email Address',
required: true,
}),
name: Property.ShortText({
displayName: 'Name',
required: false,
}),
},
}),
mobilePhone: Property.ShortText({
displayName: 'Mobile Phone',
required: false,
}),
assistantName: Property.ShortText({
displayName: 'Assistant Name',
required: false,
}),
birthday: Property.DateTime({
displayName: 'Birthday',
required: false,
}),
businessStreet: Property.ShortText({
displayName: 'Business Street',
required: false,
}),
businessCity: Property.ShortText({
displayName: 'Business City',
required: false,
}),
businessState: Property.ShortText({
displayName: 'Business State',
required: false,
}),
businessPostalCode: Property.ShortText({
displayName: 'Business Postal Code',
required: false,
}),
businessCountryOrRegion: Property.ShortText({
displayName: 'Business Country or Region',
required: false,
}),
children: Property.Array({
displayName: 'Children',
required: false,
properties: {
name: Property.ShortText({
displayName: 'Name',
required: true,
}),
},
}),
companyName: Property.ShortText({
displayName: 'Company Name',
required: false,
}),
department: Property.ShortText({
displayName: 'Department',
required: false,
}),
homeStreet: Property.ShortText({
displayName: 'Home Street',
required: false,
}),
homeCity: Property.ShortText({
displayName: 'Home City',
required: false,
}),
homeState: Property.ShortText({
displayName: 'Home State',
required: false,
}),
homePostalCode: Property.ShortText({
displayName: 'Home Postal Code',
required: false,
}),
homeCountryOrRegion: Property.ShortText({
displayName: 'Home Country or Region',
required: false,
}),
imAddresses: Property.Array({
displayName: 'Instant Messaging Addresses',
required: false,
properties: {
address: Property.ShortText({
displayName: 'IM Address',
required: true,
}),
},
}),
initials: Property.ShortText({
displayName: 'Initials',
required: false,
}),
jobTitle: Property.ShortText({
displayName: 'Job Title',
required: false,
}),
manager: Property.ShortText({
displayName: 'Manager',
required: false,
}),
nickName: Property.ShortText({
displayName: 'Nick Name',
required: false,
}),
officeLocation: Property.ShortText({
displayName: 'Office Location',
required: false,
}),
otherStreet: Property.ShortText({
displayName: 'Other Street',
required: false,
}),
otherCity: Property.ShortText({
displayName: 'Other City',
required: false,
}),
otherState: Property.ShortText({
displayName: 'Other State',
required: false,
}),
otherPostalCode: Property.ShortText({
displayName: 'Other Postal Code',
required: false,
}),
otherCountryOrRegion: Property.ShortText({
displayName: 'Other Country or Region',
required: false,
}),
parentFolder: microsoft365PeopleCommon.contactFolderDropdown(
'Parent Folder',
'Select a parent folder',
false
),
personalNotes: Property.LongText({
displayName: 'Personal Notes',
required: false,
}),
profession: Property.ShortText({
displayName: 'Profession',
required: false,
}),
spouseName: Property.ShortText({
displayName: 'Spouse Name',
required: false,
}),
title: Property.ShortText({
displayName: 'Title',
required: false,
}),
}),
contactFolderProperties: () => ({
displayName: Property.ShortText({
displayName: 'Contact Folder Name',
required: true,
}),
parentFolder: microsoft365PeopleCommon.contactFolderDropdown(
'Parent Folder',
'Select a parent folder',
false
),
}),
};

View File

@@ -0,0 +1,60 @@
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import {
AppConnectionValueForAuthProperty,
createTrigger,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import dayjs from 'dayjs';
import { microsoft365PeopleAuth } from '../common/auth';
import { microsoft365PeopleCommon } from '../common/common';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof microsoft365PeopleAuth>,
Record<string, never>
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS }) => {
const contacts = await microsoft365PeopleCommon.listContacts({
auth,
queryParams:lastFetchEpochMS ===0?{$top:'10'} :{
$filter: `lastModifiedDateTime gt ${dayjs(lastFetchEpochMS).toISOString()}`,
$orderby: 'lastModifiedDateTime desc',
},
});
return contacts.map((contact) => ({
epochMilliSeconds: dayjs(contact.lastModifiedDateTime).valueOf(),
data: contact,
}));
},
};
export const newOrUpdatedContact = createTrigger({
auth: microsoft365PeopleAuth,
name: 'newOrUpdatedContact',
displayName: 'New or Updated Contact',
description:
'Triggers when a contact is created or updated in Microsoft 365 People.',
props: {},
sampleData: {},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});