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,
DynamicPropsValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import { CreateEntryParams } from '../common/types';
export const addNoteToEntityAction = createAction({
auth: capsuleCrmAuth,
name: 'add_note_to_entity',
displayName: 'Add Note to Entity',
description:
'Add a comment/note to an entity (e.g., contact, opportunity, project).',
props: {
content: Property.LongText({
displayName: 'Note Content',
description: 'The body of the note.',
required: true,
}),
entityType: Property.StaticDropdown({
displayName: 'Entity Type',
description: 'The type of entity to add the note to.',
required: true,
options: {
options: [
{ label: 'Party (Contact)', value: 'party' },
{ label: 'Opportunity', value: 'opportunity' },
{ label: 'Project', value: 'project' },
],
},
}),
entityId: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Entity',
required: true,
refreshers: ['entityType'],
props: async ({ auth, entityType }) => {
const fields: DynamicPropsValue = {};
const entityTypeStr = entityType as unknown as string;
if (!auth || !entityTypeStr) return fields;
if (entityTypeStr === 'party') {
const contacts = await capsuleCrmClient.searchContacts(
auth,
''
);
fields['partyId'] = Property.StaticDropdown({
displayName: 'Party',
required: true,
options:{
options: contacts.map((contact) => ({
label:
contact.type === 'person'
? `${contact.firstName} ${contact.lastName}`
: contact.name || `Unnamed ${contact.type}`,
value: contact.id,
})),
}})
} else if (entityTypeStr === 'opportunity') {
const opportunities =
await capsuleCrmClient.searchOpportunities(
auth
);
fields['opportunityId'] = Property.StaticDropdown({
displayName: 'Opportunity',
required: true,
options: {
options: opportunities.map((opportunity) => ({
label: opportunity.name,
value: opportunity.id,
})),
}})
} else if (entityTypeStr === 'project') {
const projects = await capsuleCrmClient.searchProjects(
auth
);
fields['projectId'] = Property.StaticDropdown({
displayName: 'Project',
required: true,
options:{
options: projects.map((project) => ({
label: project.name,
value: project.id,
})),
}})
}
return fields;
},
}),
activityTypeId: Property.Dropdown({
displayName: 'Activity Type',
description: 'The activity type for this entry. Defaults to "Note".',
required: false,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const activityTypes = await capsuleCrmClient.listActivityTypes(
auth
);
return {
options: activityTypes.map((activityType) => ({
label: activityType.name,
value: activityType.id,
})),
};
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const entryData: CreateEntryParams = {
type: 'note',
content: propsValue.content,
};
if (propsValue.activityTypeId) {
entryData.activityType = { id: propsValue.activityTypeId };
}
const entityId = propsValue.entityId as DynamicPropsValue;
if (entityId) {
if (entityId['partyId']) {
entryData.party = { id: entityId['partyId'] as number };
} else if (entityId['opportunityId']) {
entryData.opportunity = { id: entityId['opportunityId'] as number };
} else if (entityId['projectId']) {
entryData.kase = { id: entityId['projectId'] as number };
}
}
return await capsuleCrmClient.createEntry(auth, entryData);
},
});

View File

@@ -0,0 +1,378 @@
import {
createAction,
Property,
DynamicPropsValue,
OAuth2PropertyValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import { CreatePartyParams } from '../common/types';
export const createContactAction = createAction({
auth: capsuleCrmAuth,
name: 'create_contact',
displayName: 'Create Contact',
description: 'Create a new Person or Organisation in Capsule CRM.',
props: {
type: Property.StaticDropdown({
displayName: 'Contact Type',
description: 'The type of contact to create.',
required: true,
options: {
options: [
{ label: 'Person', value: 'person' },
{ label: 'Organisation', value: 'organisation' },
],
},
}),
contactFields: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Details',
required: true,
refreshers: ['type'],
props: async ({ auth, type }) => {
const contactType = type as unknown as string;
const fields: DynamicPropsValue = {};
if (contactType === 'person') {
// Fetch organisations data ONCE at the outer level
let organisationOptions: { label: string; value: number }[] = [];
if (auth) {
try {
const organisations = await capsuleCrmClient.searchContacts(
auth,
''
);
organisationOptions = organisations
.filter((party) => party.type === 'organisation')
.map((org) => ({
label: org.name || 'Unnamed Organisation',
value: org.id,
}));
} catch (error) {
console.error('Failed to load organisations:', error);
}
}
fields['firstName'] = Property.ShortText({
displayName: 'First Name',
required: true,
});
fields['lastName'] = Property.ShortText({
displayName: 'Last Name',
required: true,
});
fields['title'] = Property.ShortText({
displayName: 'Title',
required: false,
});
fields['jobTitle'] = Property.ShortText({
displayName: 'Job Title',
required: false,
});
fields['organisationId'] = Property.StaticDropdown({
displayName: 'Organisation',
required: false,
options: {
options: organisationOptions,
},
});
} else if (contactType === 'organisation') {
fields['organisationName'] = Property.ShortText({
displayName: 'Organisation Name',
required: true,
});
}
return fields;
},
}),
about: Property.LongText({
displayName: 'About',
required: false,
}),
ownerId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Owner',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [] };
const users = await capsuleCrmClient.listUsers(
auth
);
return {
options: users.map((user) => ({
label: user.name,
value: user.id,
})),
};
},
}),
teamId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Team',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [] };
const teams = await capsuleCrmClient.listTeams(
auth
);
return {
options: teams.map((team) => ({
label: team.name,
value: team.id,
})),
};
},
}),
tags: Property.MultiSelectDropdown({
auth: capsuleCrmAuth,
displayName: 'Tags',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [] };
const tags = await capsuleCrmClient.listTags(auth);
return {
options: tags.map((tag) => ({
label: tag.name,
value: tag.name,
})),
};
},
}),
customFields: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Custom Fields',
required: true,
refreshers: [],
props: async ({ auth }) => {
const fields: DynamicPropsValue = {};
if (!auth) return fields;
const customFields = await capsuleCrmClient.listCustomFields(
auth
);
for (const field of customFields) {
switch (field.type) {
case 'list':
fields[field.id] = Property.StaticDropdown({
displayName: field.name,
required: false,
options: {
options:
field.options?.map((option) => ({
label: option,
value: option,
})) || [],
},
});
break;
case 'boolean':
fields[field.id] = Property.Checkbox({
displayName: field.name,
required: false,
});
break;
case 'date':
fields[field.id] = Property.DateTime({
displayName: field.name,
required: false,
});
break;
default:
fields[field.id] = Property.ShortText({
displayName: field.name,
required: false,
});
}
}
return fields;
},
}),
emailAddresses: Property.Array({
displayName: 'Email Addresses',
required: false,
properties: {
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Work', value: 'Work' },
],
},
}),
address: Property.ShortText({
displayName: 'Address',
required: true,
}),
},
}),
phoneNumbers: Property.Array({
displayName: 'Phone Numbers',
required: false,
properties: {
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Work', value: 'Work' },
{ label: 'Mobile', value: 'Mobile' },
{ label: 'Fax', value: 'Fax' },
{ label: 'Direct', value: 'Direct' },
],
},
}),
number: Property.ShortText({
displayName: 'Number',
required: true,
}),
},
}),
addresses: Property.Array({
displayName: 'Addresses',
required: false,
properties: {
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Postal', value: 'Postal' },
{ label: 'Office', value: 'Office' },
{ label: 'Billing', value: 'Billing' },
{ label: 'Shipping', value: 'Shipping' },
],
},
}),
street: Property.ShortText({
displayName: 'Street',
required: false,
}),
city: Property.ShortText({
displayName: 'City',
required: false,
}),
state: Property.ShortText({
displayName: 'State',
required: false,
}),
country: Property.ShortText({
displayName: 'Country',
required: false,
}),
zip: Property.ShortText({
displayName: 'Zip',
required: false,
}),
},
}),
websites: Property.Array({
displayName: 'Websites',
required: false,
properties: {
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Work', value: 'Work' },
],
},
}),
service: Property.StaticDropdown({
displayName: 'Service',
required: true,
options: {
options: [
{ label: 'URL', value: 'URL' },
{ label: 'Skype', value: 'SKYPE' },
{ label: 'Twitter', value: 'TWITTER' },
{ label: 'LinkedIn', value: 'LINKED_IN' },
{ label: 'Facebook', value: 'FACEBOOK' },
{ label: 'Xing', value: 'XING' },
{ label: 'Feed', value: 'FEED' },
{ label: 'Google+', value: 'GOOGLE_PLUS' },
{ label: 'Flickr', value: 'FLICKR' },
{ label: 'GitHub', value: 'GITHUB' },
{ label: 'YouTube', value: 'YOUTUBE' },
{ label: 'Instagram', value: 'INSTAGRAM' },
{ label: 'Pinterest', value: 'PINTEREST' },
],
},
}),
address: Property.ShortText({
displayName: 'Address',
required: true,
}),
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const type = propsValue.type as 'person' | 'organisation';
const contactFields = propsValue.contactFields as DynamicPropsValue;
const contactData: Partial<CreatePartyParams> = {
type: type,
title: contactFields['title'] as string | undefined,
jobTitle: contactFields['jobTitle'] as string | undefined,
about: propsValue.about,
organisationId: contactFields['organisationId'] as number | undefined,
ownerId: propsValue.ownerId,
teamId: propsValue.teamId,
tags: propsValue.tags,
emailAddresses: propsValue.emailAddresses as {
type?: string;
address: string;
}[],
phoneNumbers: propsValue.phoneNumbers as {
type?: string;
number: string;
}[],
addresses: propsValue.addresses as {
type?: string;
street?: string;
city?: string;
state?: string;
country?: string;
zip?: string;
}[],
websites: propsValue.websites as {
type?: string;
service: string;
address: string;
}[],
};
if (type === 'person') {
contactData.firstName = contactFields['firstName'] as string;
contactData.lastName = contactFields['lastName'] as string;
} else if (type === 'organisation') {
contactData.name = contactFields['organisationName'] as string;
}
const customFields = propsValue.customFields as DynamicPropsValue;
if (customFields) {
contactData.fields = Object.entries(customFields)
.filter(([, value]) => value !== undefined && value !== null)
.map(([id, value]) => ({
definition: { id: Number(id) },
value: value,
}));
}
return await capsuleCrmClient.createContact(
auth,
contactData as CreatePartyParams
);
},
});

View File

@@ -0,0 +1,263 @@
import {
createAction,
Property,
DynamicPropsValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import {
CreateOpportunityParams,
OpportunityCustomField,
OpportunityTag,
} from '../common/types';
export const createOpportunityAction = createAction({
auth: capsuleCrmAuth,
name: 'create_opportunity',
displayName: 'Create Opportunity',
description: 'Create a new Opportunity in Capsule CRM.',
props: {
partyId: Property.Dropdown({
displayName: 'Party',
required: true,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const contacts = await capsuleCrmClient.searchContacts(
auth,
''
);
return {
options: contacts.map((contact) => ({
label:
contact.type === 'person'
? `${contact.firstName} ${contact.lastName}`
: contact.name || `Unnamed ${contact.type}`,
value: contact.id,
})),
};
},
}),
name: Property.ShortText({
displayName: 'Name',
description: 'A short description of the opportunity.',
required: true,
}),
description: Property.LongText({
displayName: 'Description',
description: 'More details about the opportunity.',
required: false,
}),
milestoneId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Milestone',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const milestones = await capsuleCrmClient.listMilestones(
auth
);
return {
options: milestones.map((milestone) => ({
label: milestone.name,
value: milestone.id,
})),
};
},
}),
currency: Property.ShortText({
displayName: 'Currency',
description: 'The currency for the opportunity value (e.g., USD, GBP).',
required: false,
}),
amount: Property.Number({
displayName: 'Amount',
description: 'The numerical value of the opportunity.',
required: false,
}),
expectedCloseOn: Property.DateTime({
displayName: 'Expected Close Date',
description: 'The expected closing date for the opportunity.',
required: false,
}),
probability: Property.Number({
displayName: 'Probability',
description: 'The probability of winning the opportunity.',
required: false,
}),
durationBasis: Property.StaticDropdown({
displayName: 'Duration Basis',
required: false,
description: 'The basis of the duration of the opportunity.',
options: {
options: [
{ label: 'Fixed', value: 'FIXED' },
{ label: 'Hour', value: 'HOUR' },
{ label: 'Day', value: 'DAY' },
{ label: 'Week', value: 'WEEK' },
{ label: 'Month', value: 'MONTH' },
{ label: 'Quarter', value: 'QUARTER' },
{ label: 'Year', value: 'YEAR' },
],
},
}),
duration: Property.Number({
displayName: 'Duration',
required: false,
description: 'The duration of the opportunity.',
}),
ownerId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Owner',
required: false,
description: 'The user the opportunity is assigned to.',
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const users = await capsuleCrmClient.listUsers(
auth
);
return {
options: users.map((user) => ({
label: user.name,
value: user.id,
})),
};
},
}),
teamId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Team',
required: false,
description: 'The team the opportunity is assigned to.',
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const teams = await capsuleCrmClient.listTeams(
auth
);
return {
options: teams.map((team) => ({
label: team.name,
value: team.id,
})),
};
},
}),
tags: Property.MultiSelectDropdown({
auth: capsuleCrmAuth,
displayName: 'Tags',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const tags = await capsuleCrmClient.listTags(auth);
return {
options: tags.map((tag) => ({
label: tag.name,
value: tag.name,
})),
};
},
}),
customFields: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Custom Fields',
required: true,
refreshers: [],
props: async ({ auth }) => {
const fields: DynamicPropsValue = {};
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const customFields = await capsuleCrmClient.listCustomFields(
auth
);
for (const field of customFields) {
switch (field.type) {
case 'list':
fields[field.id] = Property.StaticDropdown({
displayName: field.name,
required: false,
options: {
options:
field.options?.map((option) => ({
label: option,
value: option,
})) || [],
},
});
break;
case 'boolean':
fields[field.id] = Property.Checkbox({
displayName: field.name,
required: false,
});
break;
case 'date':
fields[field.id] = Property.DateTime({
displayName: field.name,
required: false,
});
break;
default:
fields[field.id] = Property.ShortText({
displayName: field.name,
required: false,
});
}
}
return fields;
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const opportunityData: CreateOpportunityParams = {
party: { id: propsValue.partyId },
name: propsValue.name,
milestone: { id: propsValue.milestoneId },
description: propsValue.description,
expectedCloseOn: propsValue.expectedCloseOn,
probability: propsValue.probability,
durationBasis: propsValue.durationBasis,
duration: propsValue.duration,
};
if (propsValue.ownerId) {
opportunityData.owner = { id: propsValue.ownerId };
}
if (propsValue.teamId) {
opportunityData.team = { id: propsValue.teamId };
}
if (propsValue.currency && propsValue.amount) {
opportunityData.value = {
currency: propsValue.currency,
amount: propsValue.amount,
};
}
if (propsValue.tags) {
opportunityData.tags = propsValue.tags.map(
(tag) => ({ name: tag } as OpportunityTag)
);
}
const customFields = propsValue.customFields as DynamicPropsValue;
if (customFields) {
opportunityData.fields = Object.entries(customFields)
.filter(([, value]) => value !== undefined && value !== null)
.map(
([id, value]) =>
({
definition: { id: Number(id) },
value: value,
} as OpportunityCustomField)
);
}
return await capsuleCrmClient.createOpportunity(auth, opportunityData);
},
});

View File

@@ -0,0 +1,288 @@
import {
createAction,
Property,
DynamicPropsValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import {
CreateProjectParams,
OpportunityCustomField,
OpportunityTag,
} from '../common/types';
export const createProjectAction = createAction({
auth: capsuleCrmAuth,
name: 'create_project',
displayName: 'Create Project',
description: 'Create a new Project in Capsule CRM.',
props: {
partyId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Party',
description: 'The main contact for this project.',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const contacts = await capsuleCrmClient.searchContacts(
auth,
''
);
return {
options: contacts.map((contact) => ({
label:
contact.type === 'person'
? `${contact.firstName} ${contact.lastName}`
: contact.name || `Unnamed ${contact.type}`,
value: contact.id,
})),
};
},
}),
name: Property.ShortText({
displayName: 'Name',
description: 'The name of this project.',
required: true,
}),
description: Property.LongText({
displayName: 'Description',
description: 'The description of this project.',
required: false,
}),
opportunityId: Property.Dropdown({
displayName: 'Opportunity',
description:
'An optional link to the opportunity that this project was created to support.',
required: false,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const opportunities = await capsuleCrmClient.searchOpportunities(
auth
);
return {
options: opportunities.map((opportunity) => ({
label: opportunity.name,
value: opportunity.id,
})),
};
},
}),
stageId: Property.Dropdown({
displayName: 'Stage',
description: 'The stage that this project is on.',
required: false,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const stages = await capsuleCrmClient.listStages(
auth
);
return {
options: stages.map((stage) => ({
label: stage.name,
value: stage.id,
})),
};
},
}),
status: Property.StaticDropdown({
displayName: 'Status',
description: 'The status of the project.',
required: false,
options: {
options: [
{ label: 'Open', value: 'OPEN' },
{ label: 'Closed', value: 'CLOSED' },
],
},
}),
expectedCloseOn: Property.DateTime({
displayName: 'Expected Close Date',
description: 'The expected close date of this project.',
required: false,
}),
ownerId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Owner',
description: 'The user this project is assigned to.',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const users = await capsuleCrmClient.listUsers(
auth
);
return {
options: users.map((user) => ({
label: user.name,
value: user.id,
})),
};
},
}),
teamId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Team',
description: 'The team this project is assigned to.',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const teams = await capsuleCrmClient.listTeams(
auth
);
return {
options: teams.map((team) => ({
label: team.name,
value: team.id,
})),
};
},
}),
tags: Property.MultiSelectDropdown({
auth: capsuleCrmAuth,
displayName: 'Tags',
description: 'An array of tags that are added to this project.',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const tags = await capsuleCrmClient.listTags(auth);
return {
options: tags.map((tag) => ({
label: tag.name,
value: tag.name,
})),
};
},
}),
customFields: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Custom Fields',
description: 'An array of custom fields that are defined for this project.',
required: false,
refreshers: [],
props: async ({ auth }) => {
const fields: DynamicPropsValue = {};
if (!auth) return fields;
const customFields = await capsuleCrmClient.listCustomFields(
auth
);
for (const field of customFields) {
switch (field.type) {
case 'list':
fields[field.id] = Property.StaticDropdown({
displayName: field.name,
required: false,
options: {
options:
field.options?.map((option) => ({
label: option,
value: option,
})) || [],
},
});
break;
case 'boolean':
fields[field.id] = Property.Checkbox({
displayName: field.name,
required: false,
});
break;
case 'date':
fields[field.id] = Property.DateTime({
displayName: field.name,
required: false,
});
break;
default:
fields[field.id] = Property.ShortText({
displayName: field.name,
required: false,
});
}
}
return fields;
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const projectData: CreateProjectParams = {
party: { id: propsValue.partyId },
name: propsValue.name,
description: propsValue.description,
status: propsValue.status as 'OPEN' | 'CLOSED' | undefined,
expectedCloseOn: propsValue.expectedCloseOn,
};
if (propsValue.opportunityId) {
projectData.opportunity = { id: propsValue.opportunityId };
}
if (propsValue.stageId) {
projectData.stage = { id: propsValue.stageId };
}
if (propsValue.ownerId) {
projectData.owner = { id: propsValue.ownerId };
}
if (propsValue.teamId) {
projectData.team = { id: propsValue.teamId };
}
if (propsValue.tags) {
projectData.tags = propsValue.tags.map(
(tag) => ({ name: tag } as OpportunityTag)
);
}
const customFields = propsValue.customFields as DynamicPropsValue;
if (customFields) {
projectData.fields = Object.entries(customFields)
.filter(([, value]) => value !== undefined && value !== null)
.map(
([id, value]) =>
({
definition: { id: Number(id) },
value: value,
} as OpportunityCustomField)
);
}
return await capsuleCrmClient.createProject(auth, projectData);
},
});

View File

@@ -0,0 +1,192 @@
import {
createAction,
Property,
DynamicPropsValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import { CreateTaskParams } from '../common/types';
export const createTaskAction = createAction({
auth: capsuleCrmAuth,
name: 'create_task',
displayName: 'Create Task',
description: 'Create a new Task in Capsule CRM.',
props: {
description: Property.ShortText({
displayName: 'Description',
description: 'A short description of the task.',
required: true,
}),
dueOn: Property.DateTime({
displayName: 'Due Date',
description: 'The date when this task is due.',
required: true,
}),
detail: Property.LongText({
displayName: 'Details',
description: 'More details about the task.',
required: false,
}),
dueTime: Property.ShortText({
displayName: 'Due Time',
description:
"The time when this task is due (e.g., 18:00:00). Note: The time is in the user's timezone.",
required: false,
}),
linkTo: Property.StaticDropdown({
displayName: 'Link To',
description:
'The entity this task is linked to. Only one can be selected.',
required: false,
options: {
options: [
{ label: 'Party (Contact)', value: 'party' },
{ label: 'Opportunity', value: 'opportunity' },
{ label: 'Project', value: 'project' },
],
},
}),
linkedEntityId: Property.DynamicProperties({
displayName: 'Linked Entity',
required: false,
refreshers: ['linkTo'],
auth: capsuleCrmAuth,
props: async ({ auth, linkTo }) => {
const fields: DynamicPropsValue = {};
const linkToType = linkTo as unknown as string;
if (!auth || !linkToType) return fields;
if (linkToType === 'party') {
const contacts = await capsuleCrmClient.searchContacts(
auth,
''
);
const contactOptions = contacts.map((contact) => ({
label:
contact.type === 'person'
? `${contact.firstName} ${contact.lastName}`
: contact.name || `Unnamed ${contact.type}`,
value: contact.id,
}));
fields['partyId'] = Property.StaticDropdown({
displayName: 'Party',
required: true,
options: {
options: contactOptions,
},
});
} else if (linkToType === 'opportunity') {
const opportunities = await capsuleCrmClient.searchOpportunities(
auth
);
const opportunityOptions = opportunities.map((opportunity) => ({
label: opportunity.name,
value: opportunity.id,
}));
fields['opportunityId'] = Property.StaticDropdown({
displayName: 'Opportunity',
required: true,
options: {
options: opportunityOptions,
},
});
} else if (linkToType === 'project') {
const projects = await capsuleCrmClient.searchProjects(
auth
);
const projectOptions = projects.map((project) => ({
label: project.name,
value: project.id,
}));
fields['projectId'] = Property.StaticDropdown({
displayName: 'Project',
required: true,
options: {
options: projectOptions,
},
});
}
return fields;
},
}),
categoryId: Property.Dropdown({
displayName: 'Category',
description: 'The category of this task.',
required: false,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const categories = await capsuleCrmClient.listCategories(
auth
);
return {
options: categories.map((category) => ({
label: category.name,
value: category.id,
})),
};
},
}),
ownerId: Property.Dropdown({
displayName: 'Owner',
description: 'The user this task is assigned to.',
required: false,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth)
return {
options: [],
disabled: true,
placeholder: 'Please connect your Capsule CRM account first',
};
const users = await capsuleCrmClient.listUsers(
auth
);
return {
options: users.map((user) => ({
label: user.name,
value: user.id,
})),
};
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const taskData: CreateTaskParams = {
description: propsValue.description,
dueOn: propsValue.dueOn,
detail: propsValue.detail,
dueTime: propsValue.dueTime,
};
if (propsValue.categoryId) {
taskData.category = { id: propsValue.categoryId };
}
if (propsValue.ownerId) {
taskData.owner = { id: propsValue.ownerId };
}
const linkedEntity = propsValue.linkedEntityId as DynamicPropsValue;
if (linkedEntity) {
if (linkedEntity['partyId']) {
taskData.party = { id: linkedEntity['partyId'] as number };
} else if (linkedEntity['opportunityId']) {
taskData.opportunity = { id: linkedEntity['opportunityId'] as number };
} else if (linkedEntity['projectId']) {
taskData.kase = { id: linkedEntity['projectId'] as number };
}
}
return await capsuleCrmClient.createTask(auth, taskData);
},
});

View File

@@ -0,0 +1,22 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { capsuleCrmAuth } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
export const findContactAction = createAction({
auth: capsuleCrmAuth,
name: 'find_contact',
displayName: 'Find Contact',
description: 'Find a Person by search criteria.',
props: {
term: Property.ShortText({
displayName: 'Search Term',
description: 'The value to search for (e.g., a name or email).',
required: true,
}),
},
async run(context) {
const { auth, propsValue } = context;
const parties = await capsuleCrmClient.findContact(auth, propsValue.term);
return parties.filter((party) => party.type === 'person');
},
});

View File

@@ -0,0 +1,35 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { capsuleCrmAuth } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import { Filter } from '../common/types';
export const findOpportunityAction = createAction({
auth: capsuleCrmAuth,
name: 'find_opportunity',
displayName: 'Find Opportunity',
description: 'Find an Opportunity by search criteria.',
props: {
filter: Property.Json({
displayName: 'Filter',
description:
'The structured filter query. See the [documentation](https://capsulecrm.com/developer/api-v2/filters/) for examples.',
required: true,
defaultValue: {
conditions: [
{
field: 'name',
operator: 'is',
value: 'example',
},
],
},
}),
},
async run(context) {
const { auth, propsValue } = context;
return await capsuleCrmClient.filterOpportunities(
auth,
propsValue.filter as unknown as Filter
);
},
});

View File

@@ -0,0 +1,35 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { capsuleCrmAuth } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import { Filter } from '../common/types';
export const findProjectAction = createAction({
auth: capsuleCrmAuth,
name: 'find_project',
displayName: 'Find Project',
description: 'Find a Project by search criteria.',
props: {
filter: Property.Json({
displayName: 'Filter',
description:
'The structured filter query. See the [documentation](https://capsulecrm.com/developer/api-v2/filters/) for examples.',
required: true,
defaultValue: {
conditions: [
{
field: 'name',
operator: 'is',
value: 'example',
},
],
},
}),
},
async run(context) {
const { auth, propsValue } = context;
return await capsuleCrmClient.filterProjects(
auth,
propsValue.filter as unknown as Filter
);
},
});

View File

@@ -0,0 +1,368 @@
import {
createAction,
Property,
DynamicPropsValue,
OAuth2PropertyValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import { capsuleCrmProps } from '../common/props';
export const updateContactAction = createAction({
auth: capsuleCrmAuth,
name: 'update_contact',
displayName: 'Update Contact',
description: 'Update fields on an existing Person or Organisation.',
props: {
contact_id: capsuleCrmProps.contact_id(),
contactFields: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Details',
required: true,
refreshers: ['contact_id'],
props: async ({ auth, contact_id }) => {
const fields: DynamicPropsValue = {};
if (!auth || !contact_id) return fields;
const contact = await capsuleCrmClient.getContact(
auth,
contact_id as unknown as number
);
if (contact?.type === 'person') {
fields['firstName'] = Property.ShortText({
displayName: 'First Name',
description: "Update the person's first name.",
required: false,
});
fields['lastName'] = Property.ShortText({
displayName: 'Last Name',
description: "Update the person's last name.",
required: false,
});
fields['title'] = Property.ShortText({
displayName: 'Title',
description: "Update the person's job title.",
required: false,
});
} else if (contact?.type === 'organisation') {
fields['organisationName'] = Property.ShortText({
displayName: 'Organisation Name',
description: "Update the organisation's name.",
required: false,
});
}
return fields;
},
}),
ownerId: capsuleCrmProps.owner_id(false),
teamId: capsuleCrmProps.team_id(false),
about: Property.LongText({
displayName: 'About',
description: 'Update the biography or description for the contact.',
required: false,
}),
addresses: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Addresses',
required: false,
refreshers: ['contact_id'],
props: async ({ auth, contact_id }) => {
const fields: DynamicPropsValue = {};
if (!auth || !contact_id) return fields;
const contact = await capsuleCrmClient.getContact(
auth,
contact_id as unknown as number
);
const addressOptions =
contact?.addresses?.map((address) => ({
label: `${address.street}, ${address.city}`,
value: address.id,
})) ?? [];
fields['addresses'] = Property.Array({
displayName: 'Addresses',
required: false,
properties: {
id: Property.StaticDropdown({
displayName: 'Address',
required: false,
options: {
options: addressOptions,
},
}),
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Postal', value: 'Postal' },
{ label: 'Office', value: 'Office' },
{ label: 'Billing', value: 'Billing' },
{ label: 'Shipping', value: 'Shipping' },
],
},
}),
street: Property.ShortText({
displayName: 'Street',
required: false,
}),
city: Property.ShortText({
displayName: 'City',
required: false,
}),
state: Property.ShortText({
displayName: 'State',
required: false,
}),
country: Property.ShortText({
displayName: 'Country',
required: false,
}),
zip: Property.ShortText({
displayName: 'Zip',
required: false,
}),
delete: Property.Checkbox({
displayName: 'Delete',
description: 'Check this to delete the address.',
required: false,
}),
},
});
return fields;
},
}),
websites: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Websites',
required: false,
refreshers: ['contact_id'],
props: async ({ auth, contact_id }) => {
const fields: DynamicPropsValue = {};
if (!auth || !contact_id) return fields;
const contact = await capsuleCrmClient.getContact(
auth,
contact_id as unknown as number
);
const websiteOptions =
contact?.websites?.map((website) => ({
label: website.address,
value: website.id,
})) ?? [];
fields['websites'] = Property.Array({
displayName: 'Websites',
required: false,
properties: {
id: Property.StaticDropdown({
displayName: 'Website',
required: false,
options: {
options: websiteOptions,
},
}),
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Work', value: 'Work' },
],
},
}),
service: Property.StaticDropdown({
displayName: 'Service',
required: false,
options: {
options: [
{ label: 'URL', value: 'URL' },
{ label: 'Skype', value: 'SKYPE' },
{ label: 'Twitter', value: 'TWITTER' },
{ label: 'LinkedIn', value: 'LINKED_IN' },
{ label: 'Facebook', value: 'FACEBOOK' },
{ label: 'Xing', value: 'XING' },
{ label: 'Feed', value: 'FEED' },
{ label: 'Google+', value: 'GOOGLE_PLUS' },
{ label: 'Flickr', value: 'FLICKR' },
{ label: 'GitHub', value: 'GITHUB' },
{ label: 'YouTube', value: 'YOUTUBE' },
{ label: 'Instagram', value: 'INSTAGRAM' },
{ label: 'Pinterest', value: 'PINTEREST' },
],
},
}),
address: Property.ShortText({
displayName: 'Address',
required: false,
}),
delete: Property.Checkbox({
displayName: 'Delete',
description: 'Check this to delete the website.',
required: false,
}),
},
});
return fields;
},
}),
emailAddresses: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Email Addresses',
required: false,
refreshers: ['contact_id'],
props: async ({ auth, contact_id }) => {
const fields: DynamicPropsValue = {};
if (!auth || !contact_id) return fields;
const contact = await capsuleCrmClient.getContact(
auth,
contact_id as unknown as number
);
const emailOptions =
contact?.emailAddresses?.map((email) => ({
label: email.address,
value: email.id,
})) ?? [];
fields['emailAddresses'] = Property.Array({
displayName: 'Email Addresses',
required: false,
properties: {
id: Property.StaticDropdown({
displayName: 'Email Address',
required: false,
options: {
options: emailOptions,
},
}),
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Work', value: 'Work' },
],
},
}),
address: Property.ShortText({
displayName: 'Address',
required: false,
}),
delete: Property.Checkbox({
displayName: 'Delete',
description: 'Check this to delete the email.',
required: false,
}),
},
});
return fields;
},
}),
phoneNumbers: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Phone Numbers',
required: false,
refreshers: ['contact_id'],
props: async ({ auth, contact_id }) => {
const fields: DynamicPropsValue = {};
if (!auth || !contact_id) return fields;
const contact = await capsuleCrmClient.getContact(
auth,
contact_id as unknown as number
);
const phoneOptions =
contact?.phoneNumbers?.map((phone) => ({
label: phone.number,
value: phone.id,
})) ?? [];
fields['phoneNumbers'] = Property.Array({
displayName: 'Phone Numbers',
required: false,
properties: {
id: Property.StaticDropdown({
displayName: 'Phone Number',
required: false,
options: {
options: phoneOptions,
},
}),
type: Property.StaticDropdown({
displayName: 'Type',
required: false,
options: {
options: [
{ label: 'Home', value: 'Home' },
{ label: 'Work', value: 'Work' },
{ label: 'Mobile', value: 'Mobile' },
{ label: 'Fax', value: 'Fax' },
{ label: 'Direct', value: 'Direct' },
],
},
}),
number: Property.ShortText({
displayName: 'Number',
required: false,
}),
delete: Property.Checkbox({
displayName: 'Delete',
description: 'Check this to delete the phone number.',
required: false,
}),
},
});
return fields;
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const contactId = propsValue.contact_id as number;
const contactFields = propsValue.contactFields as DynamicPropsValue;
return await capsuleCrmClient.updateContact(auth, contactId, {
firstName: contactFields['firstName'] as string | undefined,
lastName: contactFields['lastName'] as string | undefined,
name: contactFields['organisationName'] as string | undefined,
title: contactFields['title'] as string | undefined,
about: propsValue.about,
ownerId: propsValue.ownerId,
teamId: propsValue.teamId,
addresses: (
(propsValue.addresses as DynamicPropsValue)?.['addresses'] as any[]
)?.map((address) => ({
...address,
_delete: address.delete,
})),
websites: (
(propsValue.websites as DynamicPropsValue)?.['websites'] as any[]
)?.map((website) => ({
...website,
_delete: website.delete,
})),
emailAddresses: (
(propsValue.emailAddresses as DynamicPropsValue)?.[
'emailAddresses'
] as any[]
)?.map((email) => ({
...email,
_delete: email.delete,
})),
phoneNumbers: (
(propsValue.phoneNumbers as DynamicPropsValue)?.[
'phoneNumbers'
] as any[]
)?.map((phone) => ({
...phone,
_delete: phone.delete,
})),
});
},
});

View File

@@ -0,0 +1,286 @@
import {
createAction,
Property,
DynamicPropsValue,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from '../common/auth';
import { capsuleCrmClient } from '../common/client';
import {
UpdateOpportunityParams,
OpportunityCustomField,
OpportunityTag,
} from '../common/types';
export const updateOpportunityAction = createAction({
auth: capsuleCrmAuth,
name: 'update_opportunity',
displayName: 'Update Opportunity',
description: 'Update an existing Opportunity in Capsule CRM.',
props: {
opportunityId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Opportunity',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const opportunities = await capsuleCrmClient.searchOpportunities(
auth
);
return {
options: opportunities.map((opportunity) => ({
label: opportunity.name,
value: opportunity.id,
})),
};
},
}),
name: Property.ShortText({
displayName: 'Name',
required: false,
}),
description: Property.LongText({
displayName: 'Description',
required: false,
}),
milestoneId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Milestone',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const milestones = await capsuleCrmClient.listMilestones(
auth
);
return {
options: milestones.map((milestone) => ({
label: milestone.name,
value: milestone.id,
})),
};
},
}),
currency: Property.ShortText({
displayName: 'Currency',
required: false,
}),
amount: Property.Number({
displayName: 'Amount',
required: false,
}),
expectedCloseOn: Property.DateTime({
displayName: 'Expected Close Date',
required: false,
}),
probability: Property.Number({
displayName: 'Probability',
required: false,
}),
durationBasis: Property.StaticDropdown({
displayName: 'Duration Basis',
required: false,
options: {
options: [
{ label: 'Fixed', value: 'FIXED' },
{ label: 'Hour', value: 'HOUR' },
{ label: 'Day', value: 'DAY' },
{ label: 'Week', value: 'WEEK' },
{ label: 'Month', value: 'MONTH' },
{ label: 'Quarter', value: 'QUARTER' },
{ label: 'Year', value: 'YEAR' },
],
},
}),
duration: Property.Number({
displayName: 'Duration',
required: false,
}),
ownerId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Owner',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const users = await capsuleCrmClient.listUsers(
auth
);
return {
options: users.map((user) => ({
label: user.name,
value: user.id,
})),
};
},
}),
teamId: Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Team',
required: false,
refreshers: [],
options: async ({ auth }) => {
if (!auth) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first" };
const teams = await capsuleCrmClient.listTeams(
auth
);
return {
options: teams.map((team) => ({
label: team.name,
value: team.id,
})),
};
},
}),
tags: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Tags',
required: false,
refreshers: ['opportunityId'],
props: async ({ auth, opportunityId }) => {
const fields: DynamicPropsValue = {};
if (!auth || !opportunityId) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first or select an opportunity" };
const opportunity = await capsuleCrmClient.getOpportunity(
auth,
opportunityId as unknown as number
);
const tagOptions =
opportunity.tags?.map((tag) => ({
label: tag.name,
value: tag.id,
})) ?? [];
fields['tags'] = Property.Array({
displayName: 'Tags',
required: false,
properties: {
id: Property.StaticDropdown({
displayName: 'Tag',
required: false,
options: {
options: tagOptions,
},
}),
name: Property.ShortText({
displayName: 'New Tag Name',
description: 'Enter a name to create a new tag.',
required: false,
}),
delete: Property.Checkbox({
displayName: 'Delete',
description: 'Check this to delete the tag.',
required: false,
}),
},
});
return fields;
},
}),
customFields: Property.DynamicProperties({
auth: capsuleCrmAuth,
displayName: 'Custom Fields',
required: false,
refreshers: ['opportunityId'],
props: async ({ auth, opportunityId }) => {
const fields: DynamicPropsValue = {};
if (!auth || !opportunityId) return { options: [], disabled: true, placeholder: "Please connect your Capsule CRM account first or select an opportunity" };
await capsuleCrmClient.getOpportunity(
auth,
opportunityId as unknown as number
);
const allCustomFields = await capsuleCrmClient.listCustomFields(
auth
);
const customFieldOptions =
allCustomFields?.map((field) => ({
label: field.name,
value: field.id,
})) ?? [];
fields['customFields'] = Property.Array({
displayName: 'Custom Fields',
required: false,
properties: {
definitionId: Property.StaticDropdown({
displayName: 'Field',
required: true,
options: {
options: customFieldOptions,
},
}),
value: Property.ShortText({
displayName: 'Value',
required: true,
}),
delete: Property.Checkbox({
displayName: 'Delete',
description: 'Check this to delete the custom field value.',
required: false,
}),
},
});
return fields;
},
}),
},
async run(context) {
const { auth, propsValue } = context;
const { opportunityId } = propsValue;
const opportunityData: UpdateOpportunityParams = {
name: propsValue.name,
description: propsValue.description,
expectedCloseOn: propsValue.expectedCloseOn,
probability: propsValue.probability,
durationBasis: propsValue.durationBasis,
duration: propsValue.duration,
};
if (propsValue.ownerId) {
opportunityData.owner = { id: propsValue.ownerId };
}
if (propsValue.teamId) {
opportunityData.team = { id: propsValue.teamId };
}
if (propsValue.milestoneId) {
opportunityData.milestone = { id: propsValue.milestoneId };
}
if (propsValue.currency && propsValue.amount) {
opportunityData.value = {
currency: propsValue.currency,
amount: propsValue.amount,
};
}
const tags = (propsValue.tags as { tags: OpportunityTag[] })?.tags;
if (tags) {
opportunityData.tags = tags.map((tag) => ({
id: tag.id,
name: tag.name,
_delete: tag._delete,
}));
}
const customFields = (
propsValue.customFields as {
customFields: { definitionId: number; value: unknown; delete: boolean }[];
}
)?.customFields;
if (customFields) {
opportunityData.fields = customFields.map((field) => ({
definition: { id: field.definitionId },
value: field.value,
_delete: field.delete,
}));
}
return await capsuleCrmClient.updateOpportunity(
auth,
opportunityId,
opportunityData
);
},
});

View File

@@ -0,0 +1,42 @@
import { OAuth2PropertyValue, PieceAuth } from '@activepieces/pieces-framework';
import {
httpClient,
HttpMethod,
AuthenticationType,
} from '@activepieces/pieces-common';
export const capsuleCrmAuth = PieceAuth.OAuth2({
description: `
To authenticate with Capsule CRM:
1. Go to your Capsule CRM user settings.
2. Navigate to "My Preferences" > "API Authentication Tokens".
3. Register a new application to get a Client ID and Client Secret.
4. Add https://cloud.activepieces.com/redirect to the authorized redirect URIs.
5. Use the OAuth2 flow below.`,
authUrl: 'https://api.capsulecrm.com/oauth/authorise',
tokenUrl: 'https://api.capsulecrm.com/oauth/token',
required: true,
scope: ['read', 'write'],
validate: async ({ auth }) => {
try {
await httpClient.sendRequest({
method: HttpMethod.GET,
url: 'https://api.capsulecrm.com/api/v2/site',
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
});
return {
valid: true,
};
} catch (e) {
return {
valid: false,
error: 'Invalid credentials',
};
}
},
});
export type CapsuleCrmAuthType = OAuth2PropertyValue;

View File

@@ -0,0 +1,778 @@
import {
AuthenticationType,
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
import {
CreateOpportunityParams,
CreatePartyParams,
CreateProjectParams,
Milestone,
Opportunity,
Party,
Project,
UpdateOpportunityParams,
UpdatePartyParams,
Case,
CreateTaskParams,
Task,
User,
Team,
Tag,
CustomField,
CreateNoteParams,
Note,
FindProjectParams,
FindOpportunityParams,
Webhook,
Stage,
Category,
ActivityType,
CreateEntryParams,
Entry,
Filter,
CreateRestHookParams,
RestHook,
} from './types';
import { CapsuleCrmAuthType } from './auth';
export const capsuleCrmClient = {
async createContact(
auth: CapsuleCrmAuthType,
params: CreatePartyParams
): Promise<Party> {
const partyData: { [key: string]: unknown } = {
type: params.type,
};
if (params.type === 'person') {
partyData['firstName'] = params.firstName;
partyData['lastName'] = params.lastName;
} else {
partyData['name'] = params.name;
}
if (params.title) partyData['title'] = params.title;
if (params.jobTitle) partyData['jobTitle'] = params.jobTitle;
if (params.about) partyData['about'] = params.about;
if (params.organisationId) {
partyData['organisation'] = { id: params.organisationId };
}
if (params.ownerId) {
partyData['owner'] = { id: params.ownerId };
}
if (params.teamId) {
partyData['team'] = { id: params.teamId };
}
if (params.tags) {
partyData['tags'] = params.tags.map((tag) => ({ name: tag }));
}
if (params.fields) {
partyData['fields'] = params.fields;
}
if (params.emailAddresses) {
partyData['emailAddresses'] = params.emailAddresses;
}
if (params.phoneNumbers) {
partyData['phoneNumbers'] = params.phoneNumbers;
}
if (params.addresses) {
partyData['addresses'] = params.addresses;
}
if (params.websites) {
partyData['websites'] = params.websites;
}
const request: HttpRequest = {
method: HttpMethod.POST,
url: 'https://api.capsulecrm.com/api/v2/parties',
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
party: partyData,
},
};
const response = await httpClient.sendRequest<Party>(request);
return response.body;
},
async searchContacts(
auth: CapsuleCrmAuthType,
searchTerm: string
): Promise<Party[]> {
const isSearch = searchTerm && searchTerm.length > 0;
const url = isSearch
? `https://api.capsulecrm.com/api/v2/parties/search`
: `https://api.capsulecrm.com/api/v2/parties`;
const queryParams: Record<string, string> = {};
if (isSearch) {
queryParams['q'] = searchTerm;
}
const request: HttpRequest = {
method: HttpMethod.GET,
url: url,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: queryParams,
};
const response = await httpClient.sendRequest<{ parties: Party[] }>(
request
);
return response.body.parties;
},
async searchOrganisations(
auth: CapsuleCrmAuthType,
searchTerm?: string
): Promise<Party[]> {
try {
const url = searchTerm
? `https://api.capsulecrm.com/api/v2/parties/search`
: `https://api.capsulecrm.com/api/v2/parties`;
const queryParams: Record<string, string> = {
perPage: '100',
};
if (searchTerm) {
queryParams['q'] = searchTerm;
}
const request: HttpRequest = {
method: HttpMethod.GET,
url: url,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: queryParams,
};
const response = await httpClient.sendRequest<{ parties: Party[] }>(
request
);
if (!response.body || !response.body.parties) {
return [];
}
// Temporarily return all parties to debug
console.log('Parties response:', response.body.parties);
const organisations = response.body.parties.filter(
(party) => party.type === 'organisation'
);
console.log('Filtered organisations:', organisations);
return organisations;
} catch (error) {
console.error('searchOrganisations error:', error);
return [];
}
},
async listTeams(auth: CapsuleCrmAuthType): Promise<Team[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/teams`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ teams: Team[] }>(request);
return response.body.teams;
},
async listTags(auth: CapsuleCrmAuthType): Promise<Tag[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/parties/tags`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ tags: Tag[] }>(request);
return response.body.tags;
},
async listCustomFields(auth: CapsuleCrmAuthType): Promise<CustomField[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/parties/fields/definitions`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{
definitions: CustomField[];
}>(request);
return response.body.definitions;
},
async updateContact(
auth: CapsuleCrmAuthType,
partyId: number,
params: UpdatePartyParams
): Promise<Party> {
const partyData: { [key: string]: unknown } = {};
if (params.firstName) partyData['firstName'] = params.firstName;
if (params.lastName) partyData['lastName'] = params.lastName;
if (params.name) partyData['name'] = params.name;
if (params.title) partyData['title'] = params.title;
if (params.about) partyData['about'] = params.about;
if (params.ownerId) partyData['owner'] = { id: params.ownerId };
if (params.teamId) partyData['team'] = { id: params.teamId };
if (params.addresses) partyData['addresses'] = params.addresses;
if (params.websites) partyData['websites'] = params.websites;
if (params.emailAddresses)
partyData['emailAddresses'] = params.emailAddresses;
if (params.phoneNumbers) partyData['phoneNumbers'] = params.phoneNumbers;
const request: HttpRequest = {
method: HttpMethod.PUT,
url: `https://api.capsulecrm.com/api/v2/parties/${partyId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
party: partyData,
},
};
const response = await httpClient.sendRequest<Party>(request);
return response.body;
},
async listMilestones(auth: CapsuleCrmAuthType): Promise<Milestone[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/milestones`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ milestones: Milestone[] }>(
request
);
return response.body.milestones;
},
async searchOpportunities(
auth: CapsuleCrmAuthType
): Promise<Opportunity[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/opportunities`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{
opportunities: Opportunity[];
}>(request);
return response.body.opportunities;
},
async getOpportunity(
auth: CapsuleCrmAuthType,
opportunityId: number
): Promise<Opportunity> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/opportunities/${opportunityId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: {
embed: 'tags,fields',
},
};
const response = await httpClient.sendRequest<{ opportunity: Opportunity }>(
request
);
return response.body.opportunity;
},
async createOpportunity(
auth: CapsuleCrmAuthType,
params: CreateOpportunityParams
): Promise<Opportunity> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/opportunities`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
opportunity: params,
},
};
const response = await httpClient.sendRequest<{ opportunity: Opportunity }>(
request
);
return response.body.opportunity;
},
async listOpportunities(auth: CapsuleCrmAuthType): Promise<Opportunity[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/opportunities`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{
opportunities: Opportunity[];
}>(request);
return response.body.opportunities;
},
async updateOpportunity(
auth: CapsuleCrmAuthType,
opportunityId: number,
params: UpdateOpportunityParams
): Promise<Opportunity> {
const request: HttpRequest = {
method: HttpMethod.PUT,
url: `https://api.capsulecrm.com/api/v2/opportunities/${opportunityId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
opportunity: params,
},
};
const response = await httpClient.sendRequest<{ opportunity: Opportunity }>(
request
);
return response.body.opportunity;
},
async listCategories(auth: CapsuleCrmAuthType): Promise<Category[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/categories`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ categories: Category[] }>(
request
);
return response.body.categories;
},
async searchProjects(auth: CapsuleCrmAuthType): Promise<Project[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/kases`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ kases: Project[] }>(request);
return response.body.kases;
},
async subscribeRestHook(
auth: CapsuleCrmAuthType,
params: CreateRestHookParams
): Promise<RestHook> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/resthooks`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
restHook: params,
},
};
const response = await httpClient.sendRequest<{ restHook: RestHook }>(
request
);
return response.body.restHook;
},
async unsubscribeRestHook(
auth: CapsuleCrmAuthType,
hookId: number
): Promise<void> {
const request: HttpRequest = {
method: HttpMethod.DELETE,
url: `https://api.capsulecrm.com/api/v2/resthooks/${hookId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
await httpClient.sendRequest(request);
},
async listActivityTypes(auth: CapsuleCrmAuthType): Promise<ActivityType[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/activitytypes`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{
activityTypes: ActivityType[];
}>(request);
return response.body.activityTypes;
},
async findContact(
auth: CapsuleCrmAuthType,
term: string
): Promise<Party[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/parties/search`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: {
q: term,
},
};
const response = await httpClient.sendRequest<{ parties: Party[] }>(
request
);
return response.body.parties;
},
async filterOpportunities(
auth: CapsuleCrmAuthType,
filter: Filter
): Promise<Opportunity[]> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/opportunities/filters/results`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
filter: filter,
},
};
const response = await httpClient.sendRequest<{
opportunities: Opportunity[];
}>(request);
return response.body.opportunities;
},
async filterProjects(
auth: CapsuleCrmAuthType,
filter: Filter
): Promise<Project[]> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/kases/filters/results`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
filter: filter,
},
};
const response = await httpClient.sendRequest<{ kases: Project[] }>(
request
);
return response.body.kases;
},
async listStages(auth: CapsuleCrmAuthType): Promise<Stage[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/stages`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ stages: Stage[] }>(request);
return response.body.stages;
},
async createProject(
auth: CapsuleCrmAuthType,
params: CreateProjectParams
): Promise<Project> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/kases`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
kase: params,
},
};
const response = await httpClient.sendRequest<{ kase: Project }>(request);
return response.body.kase;
},
async listProjects(auth: CapsuleCrmAuthType): Promise<Project[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/kases`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ kases: Project[] }>(
request
);
return response.body.kases;
},
async listCases(auth: CapsuleCrmAuthType): Promise<Case[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/kases`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ kases: Case[] }>(request);
return response.body.kases;
},
async listUsers(auth: CapsuleCrmAuthType): Promise<User[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/users`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ users: User[] }>(request);
return response.body.users;
},
async createEntry(
auth: CapsuleCrmAuthType,
params: CreateEntryParams
): Promise<Entry> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/entries`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
entry: params,
},
};
const response = await httpClient.sendRequest<{ entry: Entry }>(request);
return response.body.entry;
},
async createTask(
auth: CapsuleCrmAuthType,
params: CreateTaskParams
): Promise<Task> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/tasks`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
task: params,
},
};
const response = await httpClient.sendRequest<{ task: Task }>(request);
return response.body.task;
},
async createNote(
auth: CapsuleCrmAuthType,
params: CreateNoteParams
): Promise<Note> {
const entryData: { [key: string]: unknown } = {
type: 'Note',
content: params.content,
};
if (params.partyId) entryData['party'] = { id: params.partyId };
if (params.opportunityId)
entryData['opportunity'] = { id: params.opportunityId };
if (params.caseId) entryData['case'] = { id: params.caseId };
if (params.projectId) entryData['project'] = { id: params.projectId };
const request: HttpRequest = {
method: HttpMethod.POST,
url: 'https://api.capsulecrm.com/api/v2/entries',
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
entry: entryData,
},
};
const response = await httpClient.sendRequest<Note>(request);
return response.body;
},
async getContact(
auth: CapsuleCrmAuthType,
contactId: number
): Promise<Party | null> {
try {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/parties/${contactId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: {
embed: 'addresses,phoneNumbers,websites,emailAddresses',
},
};
const response = await httpClient.sendRequest<{ party: Party }>(request);
return response.body.party;
} catch (e) {
return null;
}
},
async findProject(
auth: CapsuleCrmAuthType,
params: FindProjectParams
): Promise<Project[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/kases`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: {
q: params.searchTerm,
},
};
const response = await httpClient.sendRequest<{ kases: Project[] }>(
request
);
return response.body.kases;
},
async getProject(
auth: CapsuleCrmAuthType,
projectId: number
): Promise<Project | null> {
try {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/kases/${projectId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
const response = await httpClient.sendRequest<{ kase: Project }>(request);
return response.body.kase;
} catch (e) {
return null;
}
},
async findOpportunity(
auth: CapsuleCrmAuthType,
params: FindOpportunityParams
): Promise<Opportunity[]> {
const request: HttpRequest = {
method: HttpMethod.GET,
url: `https://api.capsulecrm.com/api/v2/opportunities/search`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
queryParams: {
q: params.searchTerm,
},
};
const response = await httpClient.sendRequest<{
opportunities: Opportunity[];
}>(request);
return response.body.opportunities;
},
async subscribeWebhook(
auth: CapsuleCrmAuthType,
url: string,
event: string
): Promise<Webhook> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `https://api.capsulecrm.com/api/v2/webhooks`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
body: {
webhook: {
url: url,
event: event,
},
},
};
const response = await httpClient.sendRequest<{ webhook: Webhook }>(
request
);
return response.body.webhook;
},
async unsubscribeWebhook(
auth: CapsuleCrmAuthType,
webhookId: number
): Promise<void> {
const request: HttpRequest = {
method: HttpMethod.DELETE,
url: `https://api.capsulecrm.com/api/v2/webhooks/${webhookId}`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: auth.access_token,
},
};
await httpClient.sendRequest(request);
},
};

View File

@@ -0,0 +1,215 @@
import { Property } from '@activepieces/pieces-framework';
import { capsuleCrmAuth, CapsuleCrmAuthType } from './auth';
import { capsuleCrmClient } from './client';
export const capsuleCrmProps = {
contact_id: (required = true) =>
Property.Dropdown({
displayName: 'Contact',
description: 'The contact (Person or Organisation) to select.',
required: required,
refreshers: [],
auth: capsuleCrmAuth,
options: async (props) => {
const { auth } = props;
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const searchTerm = (props['searchValue'] as string) ?? '';
const contacts = await capsuleCrmClient.searchContacts(
auth,
searchTerm
);
const options = contacts.map((contact) => {
const label =
contact.type === 'person'
? `${contact.firstName} ${contact.lastName}`
: contact.name;
return {
label: label || 'Unnamed Contact',
value: contact.id,
};
});
return {
disabled: false,
options: options,
};
},
}),
milestone_id: (required = true) =>
Property.Dropdown({
displayName: 'Milestone',
description: 'The milestone to assign the opportunity to.',
required: required,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const milestones = await capsuleCrmClient.listMilestones(
auth
);
const options = milestones.map((milestone) => {
return {
label: milestone.name,
value: milestone.id,
};
});
return {
disabled: false,
options: options,
};
},
}),
opportunity_id: (required = true) =>
Property.Dropdown({
displayName: 'Opportunity',
description: 'The opportunity to associate with this item.',
required: required,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const opportunities = await capsuleCrmClient.listOpportunities(
auth
);
return {
disabled: false,
options: opportunities.map((opportunity) => ({
label: opportunity.name,
value: opportunity.id,
})),
};
},
}),
project_id: (required = false) =>
Property.Dropdown({
displayName: 'Project',
description: 'The project to associate this item with.',
required: required,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const projects = await capsuleCrmClient.listProjects(
auth
);
return {
disabled: false,
options: projects.map((project) => ({
label: project.name,
value: project.id,
})),
};
},
}),
case_id: (required = false) =>
Property.Dropdown({
displayName: 'Case',
description: 'The case to associate this item with.',
required: required,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const cases = await capsuleCrmClient.listCases(
auth
);
return {
disabled: false,
options: cases.map((kase) => ({ label: kase.name, value: kase.id })),
};
},
}),
owner_id: (required = false) =>
Property.Dropdown({
auth: capsuleCrmAuth,
displayName: 'Owner',
description: 'The user to assign the task to.',
required: required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const users = await capsuleCrmClient.listUsers(
auth
);
return {
disabled: false,
options: users.map((user) => ({
label: user.username,
value: user.id,
})),
};
},
}),
team_id: (required = false) =>
Property.Dropdown({
displayName: 'Team',
description: 'The team to assign the contact to.',
required: required,
refreshers: [],
auth: capsuleCrmAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Please connect your account first.',
options: [],
};
}
const teams = await capsuleCrmClient.listTeams(
auth
);
return {
disabled: false,
options: teams.map((team) => ({
label: team.name,
value: team.id,
})),
};
},
}),
};

View File

@@ -0,0 +1,56 @@
import {
createTrigger,
TriggerStrategy,
} from '@activepieces/pieces-framework';
import { capsuleCrmAuth } from './auth';
import { capsuleCrmClient } from './client';
type TriggerParams = {
name: string;
displayName: string;
description: string;
event: string;
sampleData: unknown;
};
export const capsuleCrmCreateTrigger = ({
name,
displayName,
description,
event,
sampleData,
}: TriggerParams) => {
return createTrigger({
auth: capsuleCrmAuth,
name: name,
displayName: displayName,
description: description,
props: {},
type: TriggerStrategy.WEBHOOK,
sampleData: sampleData,
async onEnable(context) {
const hook = await capsuleCrmClient.subscribeRestHook(context.auth, {
targetUrl: context.webhookUrl,
event: event,
description: `Activepieces - ${displayName}`,
});
await context.store.put(`capsule_crm_trigger_${name}`, {
hookId: hook.id,
});
},
async onDisable(context) {
const storedData = await context.store.get<{ hookId: number }>(
`capsule_crm_trigger_${name}`
);
if (storedData) {
await capsuleCrmClient.unsubscribeRestHook(context.auth, storedData.hookId);
}
},
async run(context) {
const body = context.payload.body as { payload: unknown[] };
return body.payload;
},
});
};

View File

@@ -0,0 +1,337 @@
export interface Party {
id: number;
type: 'person' | 'organisation';
firstName?: string;
lastName?: string;
name?: string;
addresses?: {
id: number;
street: string;
city: string;
}[];
websites?: {
id: number;
address: string;
}[];
emailAddresses?: {
id: number;
address: string;
}[];
phoneNumbers?: {
id: number;
number: string;
}[];
}
export interface CreatePartyParams {
type: 'person' | 'organisation';
firstName?: string;
lastName?: string;
name?: string;
emailAddresses?: {
type?: string;
address: string;
}[];
phoneNumbers?: {
type?: string;
number: string;
}[];
title?: string;
jobTitle?: string;
about?: string;
organisationId?: number;
ownerId?: number;
teamId?: number;
tags?: string[];
fields?: { definition: { id: number }; value: unknown }[];
addresses?: {
type?: string;
street?: string;
city?: string;
state?: string;
country?: string;
zip?: string;
}[];
websites?: {
type?: string;
service: string;
address: string;
}[];
}
export interface UpdatePartyParams {
firstName?: string;
lastName?: string;
name?: string;
title?: string;
about?: string;
ownerId?: number;
teamId?: number;
addresses?: {
id?: number;
type?: string;
street?: string;
city?: string;
state?: string;
country?: string;
zip?: string;
_delete?: boolean;
}[];
websites?: {
id?: number;
type?: string;
service?: string;
address?: string;
_delete?: boolean;
}[];
emailAddresses?: {
id?: number;
type?: string;
address?: string;
_delete?: boolean;
}[];
phoneNumbers?: {
id?: number;
type?: string;
number?: string;
_delete?: boolean;
}[];
}
export interface Milestone {
id: number;
name: string;
description?: string;
probability: number;
complete: boolean;
daysUntilStale: number;
}
export interface OpportunityValue {
amount: number;
currency: string;
}
export interface OpportunityCustomField {
id?: number;
definition: {
id: number;
};
value: unknown;
_delete?: boolean;
}
export interface OpportunityTag {
id?: number;
name: string;
_delete?: boolean;
}
export interface Opportunity extends CreateOpportunityParams {
id: number;
createdAt: string;
updatedAt: string;
tags?: OpportunityTag[];
fields?: OpportunityCustomField[];
}
export interface Stage {
id: number;
name: string;
}
export interface CreateProjectParams {
party: { id: number };
name: string;
description?: string;
owner?: { id: number };
team?: { id: number };
status?: 'OPEN' | 'CLOSED';
opportunity?: { id: number };
stage?: { id: number };
expectedCloseOn?: string;
tags?: OpportunityTag[];
fields?: OpportunityCustomField[];
}
export interface Project extends CreateProjectParams {
id: number;
createdAt: string;
updatedAt: string;
closedOn?: string;
}
export interface CreateOpportunityParams {
name: string;
party: {
id: number;
};
milestone: {
id: number;
};
description?: string;
owner?: { id: number };
team?: { id: number };
value?: OpportunityValue;
expectedCloseOn?: string;
probability?: number;
durationBasis?: string;
duration?: number;
tags?: OpportunityTag[];
fields?: OpportunityCustomField[];
}
export interface UpdateOpportunityParams {
name?: string;
description?: string;
owner?: { id: number };
team?: { id: number };
milestone?: { id: number };
value?: OpportunityValue;
expectedCloseOn?: string;
probability?: number;
durationBasis?: string;
duration?: number;
tags?: OpportunityTag[];
fields?: OpportunityCustomField[];
}
export interface Project {
id: number;
name: string;
}
export interface Case {
id: number;
name: string;
}
export interface User {
id: number;
username: string;
name: string;
}
export interface Team {
id: number;
name: string;
}
export interface Tag {
id: number;
name: string;
dataTag: boolean;
}
export interface CustomField {
id: number;
name: string;
type: string;
options?: string[];
}
export interface Task {
id: number;
type: 'Task';
description: string;
}
export interface CreateTaskParams {
description: string;
dueOn: string;
detail?: string;
category?: { id: number };
owner?: { id: number };
party?: { id: number };
opportunity?: { id: number };
kase?: { id: number };
dueTime?: string;
}
export interface Task extends CreateTaskParams {
id: number;
createdAt: string;
updatedAt: string;
status: 'OPEN' | 'COMPLETED' | 'PENDING';
}
export interface ActivityType {
id: number;
name: string;
}
export interface CreateEntryParams {
type: 'note';
content: string;
activityType?: { id: number };
party?: { id: number };
opportunity?: { id: number };
kase?: { id: number };
}
export interface Entry extends CreateEntryParams {
id: number;
createdAt: string;
updatedAt: string;
}
export interface Note {
id: number;
type: 'Note';
content: string;
}
export interface CreateNoteParams {
content: string;
partyId?: number;
opportunityId?: number;
caseId?: number;
projectId?: number;
}
export interface FindContactParams {
email?: string;
searchTerm?: string;
}
export interface FindProjectParams {
searchTerm: string;
}
export interface FindOpportunityParams {
searchTerm: string;
}
export interface Webhook {
id: number;
url: string;
event: string;
}
export interface Category {
id: number;
name: string;
colour: string;
}
export interface FilterCondition {
field: string;
operator: string;
value: string | number | boolean;
}
export interface Filter {
conditions: FilterCondition[];
}
export interface RestHook {
id: number;
event: string;
targetUrl: string;
}
export interface CreateRestHookParams {
event: string;
targetUrl: string;
description: string;
}

View File

@@ -0,0 +1,38 @@
import { capsuleCrmCreateTrigger } from '../common/trigger';
export const newCaseTrigger = capsuleCrmCreateTrigger({
name: 'new_case',
displayName: 'New Case',
description: 'Fires when a new case (project) is created in Capsule CRM.',
event: 'kase/created',
sampleData: {
event: 'kase/created',
payload: [
{
id: 12,
party: {
id: 892,
type: 'organisation',
name: 'Zestia',
pictureURL:
'https://capsulecrm.com/theme/default/images/org_avatar_70.png',
},
owner: {
id: 61,
username: 'ted',
name: 'Ted Danson',
},
status: 'OPEN',
stage: {
name: 'Project Brief',
id: 149,
},
createdAt: '2015-12-07T16:54:27Z',
updatedAt: '2015-12-07T16:54:27Z',
expectedCloseOn: '2015-12-09',
description: 'Scope and design web site shopping cart',
name: 'Consulting',
},
],
},
});

View File

@@ -0,0 +1,46 @@
import { capsuleCrmCreateTrigger } from '../common/trigger';
export const newOpportunityTrigger = capsuleCrmCreateTrigger({
name: 'new_opportunity',
displayName: 'New Opportunity',
description: 'Fires when a new opportunity is created.',
event: 'opportunity/created',
sampleData: {
event: 'opportunity/created',
payload: [
{
id: 83948362,
updatedAt: '2015-10-29T12:55:12Z',
description: 'Scope and design web site shopping cart',
owner: {
id: 6,
username: 'scottspacey',
name: 'Scott Spacey',
},
party: {
id: 581,
pictureURL:
'https://capsulecrm.com/theme/default/images/person_avatar_70.png',
type: 'organisation',
name: 'Capsule',
},
lostReason: null,
milestone: {
id: 14,
name: 'Bid',
},
value: {
amount: 500,
currency: 'GBP',
},
expectedCloseOn: '2015-10-31',
probability: 50,
durationBasis: 'FIXED',
duration: null,
closedOn: null,
createdAt: '2015-10-29T12:55:12Z',
name: 'Consulting',
},
],
},
});

View File

@@ -0,0 +1,38 @@
import { capsuleCrmCreateTrigger } from '../common/trigger';
export const newProjectTrigger = capsuleCrmCreateTrigger({
name: 'new_project',
displayName: 'New Project',
description: 'Fires when a project is created.',
event: 'kase/created',
sampleData: {
event: 'kase/created',
payload: [
{
id: 12,
party: {
id: 892,
type: 'organisation',
name: 'Zestia',
pictureURL:
'https://capsulecrm.com/theme/default/images/org_avatar_70.png',
},
owner: {
id: 61,
username: 'ted',
name: 'Ted Danson',
},
status: 'OPEN',
stage: {
name: 'Project Brief',
id: 149,
},
createdAt: '2015-12-07T16:54:27Z',
updatedAt: '2015-12-07T16:54:27Z',
expectedCloseOn: '2015-12-09',
description: 'Scope and design web site shopping cart',
name: 'Consulting',
},
],
},
});

View File

@@ -0,0 +1,36 @@
import { capsuleCrmCreateTrigger } from '../common/trigger';
export const newTaskTrigger = capsuleCrmCreateTrigger({
name: 'new_task',
displayName: 'New Task',
description: 'Fires when a new task is created.',
event: 'task/created',
sampleData: {
event: 'task/created',
payload: [
{
id: 530,
description: 'Email product details',
dueTime: '18:00:00',
status: 'OPEN',
party: {
id: 11587,
type: 'person',
firstName: 'Scott',
lastName: 'Spacey',
pictureURL:
'https://capsulecrm.com/theme/default/images/person_avatar_70.png',
},
owner: {
id: 1,
username: 'john',
name: 'John Spacey',
},
createdAt: '2015-12-21T13:51:38Z',
updatedAt: '2015-12-21T13:51:38Z',
dueOn: '2014-05-20',
hasTrack: false,
},
],
},
});