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,100 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common';
import { kommoAuth } from '../../index';
import { userDropdown } from '../common/props';
interface KommoCustomFieldValue {
field_id?: number;
field_code?: string;
values: Array<{ value: string | number; enum_id?: number }>;
}
export const createContactAction = createAction({
auth: kommoAuth,
name: 'create_contact',
displayName: 'Create New Contact',
description: 'Add a new contact.',
props: {
name: Property.ShortText({
displayName: 'Full Name',
required: true,
}),
first_name: Property.ShortText({
displayName: 'First Name',
required: false,
}),
last_name: Property.ShortText({
displayName: 'Last Name',
required: false,
}),
email: Property.ShortText({
displayName: 'Email',
required: false,
}),
phone: Property.ShortText({
displayName: 'Phone',
required: false,
}),
responsible_user_id: userDropdown(),
tags_to_add: Property.Array({
displayName: 'Tags to Add',
required: false,
})
},
async run(context) {
const {
name,
first_name,
last_name,
email,
phone,
responsible_user_id,
} = context.propsValue;
const tagsToAdd = context.propsValue.tags_to_add ?? [];
const { subdomain, apiToken } = context.auth.props
const customFields: KommoCustomFieldValue[] = [];
if (email) {
customFields.push({
field_code: 'EMAIL',
values: [{ value: email }],
});
}
if (phone) {
customFields.push({
field_code: 'PHONE',
values: [{ value: phone }],
});
}
const contactPayload: Record<string, any> = {
...(customFields.length > 0 ? { custom_fields_values: customFields } : {}),
};
if (name) contactPayload['name'] = name;
if (first_name) contactPayload['first_name'] = first_name;
if (last_name) contactPayload['last_name'] = last_name;
if (responsible_user_id) contactPayload['responsible_user_id'] = responsible_user_id;
if (tagsToAdd.length > 0) {
contactPayload['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag }))
}
const result = await makeRequest(
{ apiToken, subdomain },
HttpMethod.POST,
`/contacts`,
[contactPayload]
);
return result;
},
});

View File

@@ -0,0 +1,62 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common';
import { kommoAuth } from '../../index';
import {
pipelineDropdown,
statusDropdown,
userDropdown,
lossReasonDropdown,
} from '../common/props';
export const createLeadAction = createAction({
auth: kommoAuth,
name: 'create_lead',
displayName: 'Create New Lead',
description: 'Creates a new lead.',
props: {
name: Property.ShortText({
displayName: 'Lead Name',
required: true,
}),
price: Property.Number({
displayName: 'Price',
required: false,
}),
pipelineId: pipelineDropdown(true),
statusId: statusDropdown(true),
responsible_user_id: userDropdown(),
loss_reason_id: lossReasonDropdown(),
tags_to_add: Property.Array({
displayName: 'Tags to Add',
description: 'List of tags to add.',
required: false,
}),
},
async run(context) {
const { name, price, statusId, pipelineId, loss_reason_id, responsible_user_id } =
context.propsValue;
const tagsToAdd = context.propsValue.tags_to_add ?? [];
const { apiToken, subdomain } = context.auth.props;
const body: Record<string, unknown> = {
name,
};
if (price) body['price'] = price;
if (statusId) body['status_id'] = statusId;
if (pipelineId) body['pipeline_id'] = pipelineId;
if (loss_reason_id) body['loss_reason_id'] = loss_reason_id;
if (responsible_user_id) body['responsible_user_id'] = responsible_user_id;
if (tagsToAdd.length > 0) {
body['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag }));
}
const response = await makeRequest({ apiToken, subdomain }, HttpMethod.POST, '/leads', [body]);
return response;
},
});

View File

@@ -0,0 +1,35 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common';
import { kommoAuth } from '../../index';
export const findCompanyAction = createAction({
auth: kommoAuth,
name: 'find_company',
displayName: 'Find Company',
description: 'Find an existing company.',
props: {
query: Property.ShortText({
displayName: 'Query',
required: true,
description: 'Search query (Searches through the filled fields of the company).'
}),
},
async run(context) {
const { query } = context.propsValue;
const { subdomain, apiToken } = context.auth.props
const result = await makeRequest(
{ apiToken, subdomain },
HttpMethod.GET,
`/companies?query=${encodeURIComponent(query || '')}`
);
const companies = result?._embedded?.companies ?? [];
return {
found: companies.length > 0,
result: companies
};
},
});

View File

@@ -0,0 +1,35 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { kommoAuth } from '../../index';
import { makeRequest } from '../common';
import { HttpMethod } from '@activepieces/pieces-common';
export const findContactAction = createAction({
auth: kommoAuth,
name: 'find_contact',
displayName: 'Find Contact',
description: 'Finds an existing contact.',
props: {
query: Property.ShortText({
displayName: 'Query',
required: true,
description: 'Search query (Searches through the filled fields of the contact).'
}),
},
async run(context) {
const { query } = context.propsValue;
const { subdomain, apiToken } = context.auth.props
const result = await makeRequest(
{ apiToken, subdomain },
HttpMethod.GET,
`/contacts?query=${encodeURIComponent(query)}`
);
const contacts = result?._embedded?.contacts ?? [];
return {
found: contacts.length > 0,
result: contacts
};
},
});

View File

@@ -0,0 +1,34 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common';
import { kommoAuth } from '../../index';
export const findLeadAction = createAction({
auth: kommoAuth,
name: 'find_lead',
displayName: 'Find Lead',
description: "Finds an existing lead.",
props: {
query: Property.ShortText({
displayName: 'Query',
required: true,
description: 'Search query (Searches through the filled fields of the lead).'
}),
},
async run(context) {
const { subdomain, apiToken } = context.auth.props
const result = await makeRequest(
{ apiToken, subdomain },
HttpMethod.GET,
`/leads?query=${encodeURIComponent(context.propsValue.query)}`
);
const leads = result?._embedded?.leads ?? [];
return {
found: leads.length > 0,
result: leads
};
},
});

View File

@@ -0,0 +1,9 @@
import { findLeadAction } from './find-lead';
import { updateContactAction } from './update-contact';
import { createLeadAction } from './create-new-lead';
import { createContactAction } from './create-new-contact';
import { findContactAction } from './find-contact';
import { findCompanyAction } from './find-company';
import { updateLeadAction } from './update-lead'
export { findLeadAction, updateContactAction, createLeadAction, createContactAction, findContactAction, findCompanyAction, updateLeadAction };

View File

@@ -0,0 +1,110 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { kommoAuth } from '../../index';
import { makeRequest } from '../common';
import { contactDropdown, userDropdown } from '../common/props';
interface KommoCustomFieldValue {
field_id?: number;
field_code?: string;
values: Array<{ value: string | number; enum_id?: number }>;
}
export const updateContactAction = createAction({
auth: kommoAuth,
name: 'update_contact',
displayName: 'Update Contact',
description: 'Updates an existing contact.',
props: {
contactId: contactDropdown,
name: Property.ShortText({
displayName: 'Full Name',
required: false,
}),
first_name: Property.ShortText({
displayName: 'First Name',
required: false,
}),
last_name: Property.ShortText({
displayName: 'Last Name',
required: false,
}),
email: Property.ShortText({
displayName: 'Email',
required: false,
}),
phone: Property.ShortText({
displayName: 'Phone',
required: false,
}),
responsible_user_id: userDropdown(),
tags_to_add: Property.Array({
displayName: 'Tags to Add',
description: 'List of tag names or IDs to add.',
required: false,
}),
tags_to_delete: Property.Array({
displayName: 'Tags to Delete',
description: 'List of tag names or IDs to remove.',
required: false,
})
},
async run(context) {
const {
contactId,
name,
first_name,
last_name,
email,
phone,
responsible_user_id,
} = context.propsValue;
const tagsToAdd = context.propsValue.tags_to_add ?? [];
const tagsToDelete = context.propsValue.tags_to_delete ?? [];
const { subdomain, apiToken } = context.auth.props;
const customFields: KommoCustomFieldValue[] = [];
if (email) {
customFields.push({
field_code: 'EMAIL',
values: [{ value: email }],
});
}
if (phone) {
customFields.push({
field_code: 'PHONE',
values: [{ value: phone }],
});
}
const updatePayload: Record<string, any> = {
...(customFields.length > 0 ? { custom_fields_values: customFields } : {}),
};
if (name) updatePayload['name'] = name;
if (first_name) updatePayload['first_name'] = first_name;
if (last_name) updatePayload['last_name'] = last_name;
if (responsible_user_id) updatePayload['responsible_user_id'] = responsible_user_id;
if (tagsToAdd.length > 0) {
updatePayload['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag }))
}
if (tagsToDelete.length > 0) {
updatePayload['tags_to_delete'] = tagsToDelete.map((tag) => ({ name: tag }))
}
const result = await makeRequest(
{ subdomain, apiToken },
HttpMethod.PATCH,
`/contacts/${contactId}`,
updatePayload
);
return result;
},
});

View File

@@ -0,0 +1,78 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { makeRequest } from '../common';
import { kommoAuth } from '../../index';
import { pipelineDropdown, statusDropdown, userDropdown, lossReasonDropdown, leadDropdown } from '../common/props';
export const updateLeadAction = createAction({
auth: kommoAuth,
name: 'update_lead',
displayName: 'Update Lead',
description: 'Update existing lead info.',
props: {
leadId: leadDropdown,
name: Property.ShortText({
displayName: 'Name',
required: false,
}),
price: Property.Number({
displayName: 'Price',
required: false,
}),
pipelineId: pipelineDropdown(false),
statusId: statusDropdown(false),
responsible_user_id: userDropdown(false),
loss_reason_id: lossReasonDropdown(false),
tags_to_add: Property.Array({
displayName: 'Tags to Add',
required: false,
}),
tags_to_delete: Property.Array({
displayName: 'Tags to Delete',
required: false,
}),
},
async run(context) {
const {
leadId,
name,
price,
statusId,
pipelineId,
responsible_user_id,
loss_reason_id,
} = context.propsValue;
const tagsToAdd = context.propsValue.tags_to_add ?? [];
const tagsToDelete = context.propsValue.tags_to_delete ?? [];
const { subdomain, apiToken } = context.auth.props;
const updatePayload: Record<string, any> = {};
if (name) updatePayload['name'] = name;
if (price) updatePayload['price'] = price;
if (statusId) updatePayload['status_id'] = statusId;
if (pipelineId) updatePayload['pipeline_id'] = pipelineId;
if (loss_reason_id) updatePayload['loss_reason_id'] = loss_reason_id;
if (responsible_user_id) updatePayload['responsible_user_id'] = responsible_user_id;
if (tagsToAdd.length > 0) {
updatePayload['tags_to_add'] = tagsToAdd.map((tag) => ({ name: tag }))
}
if (tagsToDelete.length > 0) {
updatePayload['tags_to_delete'] = tagsToDelete.map((tag) => ({ name: tag }))
}
const result = await makeRequest(
{ apiToken, subdomain },
HttpMethod.PATCH,
`/leads/${leadId}`,
updatePayload
);
return result;
},
});