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,76 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const enrichCompanyBasicBulk = createAction({
name: 'enrichCompanyBasicBulk',
auth: villageAuth,
displayName: 'Enrich Company - Bulk',
description: 'Enrich multiple company profiles (max 10) with basic information including employee size, industry, and founding details.',
props: {
identifiers: Property.Array({
displayName: 'Identifiers',
description: 'Add companies to enrich (max 10)',
properties: {
identifierType: Property.StaticDropdown({
displayName: 'Identifier Type',
required: true,
options: {
options: [
{ label: 'Village ID', value: 'village_id' },
{ label: 'LinkedIn URL', value: 'linkedin_url' },
{ label: 'Domain', value: 'domain' },
],
},
}),
identifierValue: Property.ShortText({
displayName: 'Identifier Value',
description: 'Enter the Village ID, LinkedIn URL, or Domain',
required: true,
}),
},
required: true,
defaultValue: [],
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { identifiers, user_identifier } = context.propsValue;
if (!identifiers || identifiers.length === 0) {
throw new Error('At least one identifier is required');
}
if (identifiers.length > 10) {
throw new Error('Maximum of 10 identifiers allowed per request');
}
// Transform the array data to match API format
const formattedIdentifiers = identifiers.map((item: any) => ({
[item.identifierType]: item.identifierValue
}));
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.POST,
url: 'https://api.village.do/v1/companies/enrich/basic/bulk',
headers,
body: {
identifiers: formattedIdentifiers,
},
});
return res.body;
},
});

View File

@@ -0,0 +1,62 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const enrichCompanyBasic = createAction({
name: 'enrichCompanyBasic',
auth: villageAuth,
displayName: 'Enrich Company',
description: 'Enrich a single company profile with basic information including employee size, industry, and founding details',
props: {
village_id: Property.ShortText({
displayName: 'Village ID',
description: 'Village internal company ID',
required: false,
}),
domain: Property.ShortText({
displayName: 'Domain',
description: 'Company domain name',
required: false,
}),
linkedin_url: Property.ShortText({
displayName: 'LinkedIn URL',
description: 'LinkedIn company page URL',
required: false,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { village_id, domain, linkedin_url, user_identifier } = context.propsValue;
// At least one identifier is required
if (!village_id && !domain && !linkedin_url) {
throw new Error('At least one identifier (village_id, domain, or linkedin_url) is required');
}
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
// Build query parameters
const queryParams = new URLSearchParams();
if (village_id) queryParams.append('village_id', village_id);
if (domain) queryParams.append('domain', domain);
if (linkedin_url) queryParams.append('linkedin_url', linkedin_url);
const res = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.village.do/v1/companies/enrich/basic?${queryParams.toString()}`,
headers,
});
return res.body;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const getCompanyPaths = createAction({
name: 'getCompanyPaths',
auth: villageAuth,
displayName: 'Get Company Paths',
description: 'Returns connection paths to a company using either its LinkedIn URL or domain URL',
props: {
company_url: Property.ShortText({
displayName: 'Company URL',
description: 'LinkedIn URL or domain URL of the target company (URL encoded)',
required: true,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { company_url, user_identifier } = context.propsValue;
// Encode the URL for use in the path
const encodedUrl = encodeURIComponent(company_url);
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.village.do/v1/companies/paths/${encodedUrl}`,
headers,
});
return res.body;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const sortCompanies = createAction({
name: 'sortCompanies',
auth: villageAuth,
displayName: 'Sort Companies',
description: 'Sort a list of companies by relationship strength with the user',
props: {
companies: Property.Array({
displayName: 'Company URLs',
description: 'Array of company LinkedIn URLs or domain URLs',
required: true,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { companies, user_identifier } = context.propsValue;
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.POST,
url: 'https://api.village.do/v1/companies/sort',
headers,
body: {
companies,
},
});
return res.body;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const enrichEmail = createAction({
name: 'enrichEmail',
auth: villageAuth,
displayName: 'Enrich Email',
description: 'Enrich a single LinkedIn profile or Village ID with an email address',
props: {
identifier: Property.ShortText({
displayName: 'Identifier',
description: 'LinkedIn URL or Village ID',
required: true,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { identifier, user_identifier } = context.propsValue;
// Encode the identifier for use in the path
const encodedIdentifier = encodeURIComponent(identifier);
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.village.do/v1/people/enrich/emails/${encodedIdentifier}`,
headers,
});
return res.body;
},
});

View File

@@ -0,0 +1,49 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const enrichEmailsBulk = createAction({
name: 'enrichEmailsBulk',
auth: villageAuth,
displayName: 'Enrich Emails (Bulk)',
description: 'Enrich multiple LinkedIn profiles or Village IDs with email addresses',
props: {
identifiers: Property.Array({
displayName: 'Identifiers',
description: 'Semicolon-separated list of LinkedIn URLs or Village IDs',
required: true,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { identifiers, user_identifier } = context.propsValue;
if (!identifiers || identifiers.length === 0) {
throw new Error('At least one identifier is required');
}
// Join identifiers with semicolon and encode for URL
const identifiersString = identifiers.join(';');
const encodedIdentifiers = encodeURIComponent(identifiersString);
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.village.do/v1/people/enrich/emails/bulk/${encodedIdentifiers}`,
headers,
});
return res.body;
},
});

View File

@@ -0,0 +1,76 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const enrichPersonBasicBulk = createAction({
name: 'enrichPersonBasicBulk',
auth: villageAuth,
displayName: 'Enrich Profile - Bulk',
description: 'Enrich multiple person profiles (max 10) with basic professional information and work history.',
props: {
identifiers: Property.Array({
displayName: 'Identifiers',
description: 'Add people to enrich (max 10)',
properties: {
identifierType: Property.StaticDropdown({
displayName: 'Identifier Type',
required: true,
options: {
options: [
{ label: 'Village ID', value: 'village_id' },
{ label: 'LinkedIn URL', value: 'linkedin_url' },
{ label: 'Email', value: 'email' },
],
},
}),
identifierValue: Property.ShortText({
displayName: 'Identifier Value',
description: 'Enter the Village ID, LinkedIn URL, or Email',
required: true,
}),
},
required: true,
defaultValue: [],
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { identifiers, user_identifier } = context.propsValue;
if (!identifiers || identifiers.length === 0) {
throw new Error('At least one identifier is required');
}
if (identifiers.length > 10) {
throw new Error('Maximum of 10 identifiers allowed per request');
}
// Transform the array data to match API format
const formattedIdentifiers = identifiers.map((item: any) => ({
[item.identifierType]: item.identifierValue
}));
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.POST,
url: 'https://api.village.do/v1/people/enrich/basic/bulk',
headers,
body: {
identifiers: formattedIdentifiers,
},
});
return res.body;
},
});

View File

@@ -0,0 +1,62 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const enrichPersonBasic = createAction({
name: 'enrichPersonBasic',
auth: villageAuth,
displayName: 'Enrich Profile',
description: 'Enrich a single person profile with basic professional information and work history',
props: {
village_id: Property.ShortText({
displayName: 'Village ID',
description: 'Village internal user ID',
required: false,
}),
linkedin_url: Property.ShortText({
displayName: 'LinkedIn URL',
description: 'LinkedIn profile URL',
required: false,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'Email address',
required: false,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { village_id, linkedin_url, email, user_identifier } = context.propsValue;
// At least one identifier is required
if (!village_id && !linkedin_url && !email) {
throw new Error('At least one identifier (village_id, linkedin_url, or email) is required');
}
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
// Build query parameters
const queryParams = new URLSearchParams();
if (village_id) queryParams.append('village_id', village_id);
if (linkedin_url) queryParams.append('linkedin_url', linkedin_url);
if (email) queryParams.append('email', email);
const res = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.village.do/v1/people/enrich/basic?${queryParams.toString()}`,
headers,
});
return res.body;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const getPersonPaths = createAction({
name: 'getPersonPaths',
auth: villageAuth,
displayName: 'Get Person Paths',
description: 'Returns connection paths and warmth score to a LinkedIn profile',
props: {
linkedin_url: Property.ShortText({
displayName: 'LinkedIn URL',
description: 'LinkedIn URL of the target person',
required: true,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { linkedin_url, user_identifier } = context.propsValue;
// Encode the URL for use in the path
const encodedUrl = encodeURIComponent(linkedin_url);
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `https://api.village.do/v1/people/paths/${encodedUrl}`,
headers,
});
return res.body;
},
});

View File

@@ -0,0 +1,44 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { villageAuth } from '../../..';
export const sortPeople = createAction({
name: 'sortPeople',
auth: villageAuth,
displayName: 'Sort People',
description: 'Sort a list of LinkedIn profiles by relationship strength with the user',
props: {
people: Property.Array({
displayName: 'People URLs',
description: 'Array of LinkedIn URLs',
required: true,
}),
user_identifier: Property.ShortText({
displayName: 'User Identifier',
description: 'Specify the user making the request. This identifier should match the one you used when integrating the user with Village.',
required: false,
}),
},
async run(context) {
const { people, user_identifier } = context.propsValue;
const headers: Record<string, string> = {
'secret-key': context.auth.secret_text,
};
if (user_identifier) {
headers['user-identifier'] = user_identifier;
}
const res = await httpClient.sendRequest({
method: HttpMethod.POST,
url: 'https://api.village.do/v1/people/sort',
headers,
body: {
people,
},
});
return res.body;
},
});