first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.affinity",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Sales"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/affinity/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.affinity/"
}
]
}
}
@@ -0,0 +1,432 @@
import type {
IExecuteFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { affinityApiRequest, affinityApiRequestAllItems } from './GenericFunctions';
import { listFields, listOperations } from './ListDescription';
import { listEntryFields, listEntryOperations } from './ListEntryDescription';
import { organizationFields, organizationOperations } from './OrganizationDescription';
import type { IOrganization } from './OrganizationInterface';
import { personFields, personOperations } from './PersonDescription';
import type { IPerson } from './PersonInterface';
export class Affinity implements INodeType {
description: INodeTypeDescription = {
displayName: 'Affinity',
name: 'affinity',
icon: { light: 'file:affinity.svg', dark: 'file:affinity.dark.svg' },
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Affinity API',
defaults: {
name: 'Affinity',
},
usableAsTool: true,
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'affinityApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'List',
value: 'list',
},
{
name: 'List Entry',
value: 'listEntry',
},
{
name: 'Organization',
value: 'organization',
},
{
name: 'Person',
value: 'person',
},
],
default: 'organization',
},
...listOperations,
...listFields,
...listEntryOperations,
...listEntryFields,
...organizationOperations,
...organizationFields,
...personOperations,
...personFields,
],
};
methods = {
loadOptions: {
// Get all the available organizations to display them to user so that they can
// select them easily
async getOrganizations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const organizations = await affinityApiRequestAllItems.call(
this,
'organizations',
'GET',
'/organizations',
{},
);
for (const organization of organizations) {
const organizationName = organization.name;
const organizationId = organization.id;
returnData.push({
name: organizationName,
value: organizationId,
});
}
return returnData;
},
// Get all the available persons to display them to user so that they can
// select them easily
async getPersons(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const persons = await affinityApiRequestAllItems.call(
this,
'persons',
'GET',
'/persons',
{},
);
for (const person of persons) {
let personName = `${person.first_name} ${person.last_name}`;
if (person.primary_email !== null) {
personName += ` (${person.primary_email})`;
}
const personId = person.id;
returnData.push({
name: personName,
value: personId,
});
}
return returnData;
},
// Get all the available lists to display them to user so that they can
// select them easily
async getLists(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const lists = await affinityApiRequest.call(this, 'GET', '/lists');
for (const list of lists) {
returnData.push({
name: list.name,
value: list.id,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const length = items.length;
let responseData;
const qs: IDataObject = {};
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
try {
if (resource === 'list') {
//https://api-docs.affinity.co/#get-a-specific-list
if (operation === 'get') {
const listId = this.getNodeParameter('listId', i) as string;
responseData = await affinityApiRequest.call(this, 'GET', `/lists/${listId}`, {}, qs);
}
//https://api-docs.affinity.co/#get-all-lists
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
responseData = await affinityApiRequest.call(this, 'GET', '/lists', {}, qs);
if (!returnAll) {
const limit = this.getNodeParameter('limit', i);
responseData = responseData.splice(0, limit);
}
}
}
if (resource === 'listEntry') {
//https://api-docs.affinity.co/#create-a-new-list-entry
if (operation === 'create') {
const listId = this.getNodeParameter('listId', i) as string;
const entityId = this.getNodeParameter('entityId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IDataObject = {
entity_id: parseInt(entityId, 10),
};
Object.assign(body, additionalFields);
responseData = await affinityApiRequest.call(
this,
'POST',
`/lists/${listId}/list-entries`,
body,
);
}
//https://api-docs.affinity.co/#get-a-specific-list-entry
if (operation === 'get') {
const listId = this.getNodeParameter('listId', i) as string;
const listEntryId = this.getNodeParameter('listEntryId', i) as string;
responseData = await affinityApiRequest.call(
this,
'GET',
`/lists/${listId}/list-entries/${listEntryId}`,
{},
qs,
);
}
//https://api-docs.affinity.co/#get-all-list-entries
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
const listId = this.getNodeParameter('listId', i) as string;
if (returnAll) {
responseData = await affinityApiRequestAllItems.call(
this,
'list_entries',
'GET',
`/lists/${listId}/list-entries`,
{},
qs,
);
} else {
qs.page_size = this.getNodeParameter('limit', i);
responseData = await affinityApiRequest.call(
this,
'GET',
`/lists/${listId}/list-entries`,
{},
qs,
);
responseData = responseData.list_entries;
}
}
//https://api-docs.affinity.co/#delete-a-specific-list-entry
if (operation === 'delete') {
const listId = this.getNodeParameter('listId', i) as string;
const listEntryId = this.getNodeParameter('listEntryId', i) as string;
responseData = await affinityApiRequest.call(
this,
'DELETE',
`/lists/${listId}/list-entries/${listEntryId}`,
{},
qs,
);
}
}
if (resource === 'person') {
//https://api-docs.affinity.co/#create-a-new-person
if (operation === 'create') {
const firstName = this.getNodeParameter('firstName', i) as string;
const lastName = this.getNodeParameter('lastName', i) as string;
const emails = this.getNodeParameter('emails', i) as string[];
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IPerson = {
first_name: firstName,
last_name: lastName,
emails,
};
if (additionalFields.organizations) {
body.organization_ids = additionalFields.organizations as number[];
}
responseData = await affinityApiRequest.call(this, 'POST', '/persons', body);
}
//https://api-docs.affinity.co/#update-a-person
if (operation === 'update') {
const personId = this.getNodeParameter('personId', i) as number;
const updateFields = this.getNodeParameter('updateFields', i);
const emails = this.getNodeParameter('emails', i) as string[];
const body: IPerson = {
emails,
};
if (updateFields.firstName) {
body.first_name = updateFields.firstName as string;
}
if (updateFields.lastName) {
body.last_name = updateFields.lastName as string;
}
if (updateFields.organizations) {
body.organization_ids = updateFields.organizations as number[];
}
responseData = await affinityApiRequest.call(this, 'PUT', `/persons/${personId}`, body);
}
//https://api-docs.affinity.co/#get-a-specific-person
if (operation === 'get') {
const personId = this.getNodeParameter('personId', i) as number;
const options = this.getNodeParameter('options', i);
if (options.withInteractionDates) {
qs.with_interaction_dates = options.withInteractionDates as boolean;
}
responseData = await affinityApiRequest.call(
this,
'GET',
`/persons/${personId}`,
{},
qs,
);
}
//https://api-docs.affinity.co/#search-for-persons
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
const options = this.getNodeParameter('options', i);
if (options.term) {
qs.term = options.term as string;
}
if (options.withInteractionDates) {
qs.with_interaction_dates = options.withInteractionDates as boolean;
}
if (returnAll) {
responseData = await affinityApiRequestAllItems.call(
this,
'persons',
'GET',
'/persons',
{},
qs,
);
} else {
qs.page_size = this.getNodeParameter('limit', i);
responseData = await affinityApiRequest.call(this, 'GET', '/persons', {}, qs);
responseData = responseData.persons;
}
}
//https://api-docs.affinity.co/#delete-a-person
if (operation === 'delete') {
const personId = this.getNodeParameter('personId', i) as number;
responseData = await affinityApiRequest.call(
this,
'DELETE',
`/persons/${personId}`,
{},
qs,
);
}
}
if (resource === 'organization') {
//https://api-docs.affinity.co/#create-a-new-organization
if (operation === 'create') {
const name = this.getNodeParameter('name', i) as string;
const domain = this.getNodeParameter('domain', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IOrganization = {
name,
domain,
};
if (additionalFields.persons) {
body.person_ids = additionalFields.persons as number[];
}
responseData = await affinityApiRequest.call(this, 'POST', '/organizations', body);
}
//https://api-docs.affinity.co/#update-an-organization
if (operation === 'update') {
const organizationId = this.getNodeParameter('organizationId', i) as number;
const updateFields = this.getNodeParameter('updateFields', i);
const body: IOrganization = {};
if (updateFields.name) {
body.name = updateFields.name as string;
}
if (updateFields.domain) {
body.domain = updateFields.domain as string;
}
if (updateFields.persons) {
body.person_ids = updateFields.persons as number[];
}
responseData = await affinityApiRequest.call(
this,
'PUT',
`/organizations/${organizationId}`,
body,
);
}
//https://api-docs.affinity.co/#get-a-specific-organization
if (operation === 'get') {
const organizationId = this.getNodeParameter('organizationId', i) as number;
const options = this.getNodeParameter('options', i);
if (options.withInteractionDates) {
qs.with_interaction_dates = options.withInteractionDates as boolean;
}
responseData = await affinityApiRequest.call(
this,
'GET',
`/organizations/${organizationId}`,
{},
qs,
);
}
//https://api-docs.affinity.co/#search-for-organizations
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
const options = this.getNodeParameter('options', i);
if (options.term) {
qs.term = options.term as string;
}
if (options.withInteractionDates) {
qs.with_interaction_dates = options.withInteractionDates as boolean;
}
if (returnAll) {
responseData = await affinityApiRequestAllItems.call(
this,
'organizations',
'GET',
'/organizations',
{},
qs,
);
} else {
qs.page_size = this.getNodeParameter('limit', i);
responseData = await affinityApiRequest.call(this, 'GET', '/organizations', {}, qs);
responseData = responseData.organizations;
}
}
//https://api-docs.affinity.co/#delete-an-organization
if (operation === 'delete') {
const organizationId = this.getNodeParameter('organizationId', i) as number;
responseData = await affinityApiRequest.call(
this,
'DELETE',
`/organizations/${organizationId}`,
{},
qs,
);
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } },
);
returnData.push(...executionData);
} catch (error) {
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.message }),
{ itemData: { item: i } },
);
returnData.push(...executionErrorData);
continue;
}
throw error;
}
}
return [returnData];
}
}
@@ -0,0 +1,18 @@
{
"node": "n8n-nodes-base.affinityTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Sales"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/affinity/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.affinitytrigger/"
}
]
}
}
@@ -0,0 +1,250 @@
import type {
IHookFunctions,
IWebhookFunctions,
IDataObject,
INodeType,
INodeTypeDescription,
IWebhookResponseData,
} from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import { affinityApiRequest, eventsExist, mapResource } from './GenericFunctions';
export class AffinityTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Affinity Trigger',
name: 'affinityTrigger',
icon: { light: 'file:affinity.svg', dark: 'file:affinity.dark.svg' },
group: ['trigger'],
version: 1,
description: 'Handle Affinity events via webhooks',
defaults: {
name: 'Affinity Trigger',
},
inputs: [],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'affinityApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'field_value.created',
value: 'field_value.created',
},
{
name: 'field_value.deleted',
value: 'field_value.deleted',
},
{
name: 'field_value.updated',
value: 'field_value.updated',
},
{
name: 'field.created',
value: 'field.created',
},
{
name: 'field.deleted',
value: 'field.deleted',
},
{
name: 'field.updated',
value: 'field.updated',
},
{
name: 'file.created',
value: 'file.created',
},
{
name: 'file.deleted',
value: 'file.deleted',
},
{
name: 'list_entry.created',
value: 'list_entry.created',
},
{
name: 'list_entry.deleted',
value: 'list_entry.deleted',
},
{
name: 'list.created',
value: 'list.created',
},
{
name: 'list.deleted',
value: 'list.deleted',
},
{
name: 'list.updated',
value: 'list.updated',
},
{
name: 'note.created',
value: 'note.created',
},
{
name: 'note.deleted',
value: 'note.deleted',
},
{
name: 'note.updated',
value: 'note.updated',
},
{
name: 'opportunity.created',
value: 'opportunity.created',
},
{
name: 'opportunity.deleted',
value: 'opportunity.deleted',
},
{
name: 'opportunity.updated',
value: 'opportunity.updated',
},
{
name: 'organization.created',
value: 'organization.created',
},
{
name: 'organization.deleted',
value: 'organization.deleted',
},
{
name: 'organization.updated',
value: 'organization.updated',
},
{
name: 'person.created',
value: 'person.created',
},
{
name: 'person.deleted',
value: 'person.deleted',
},
{
name: 'person.updated',
value: 'person.updated',
},
],
default: [],
required: true,
description: 'Webhook events that will be enabled for that endpoint',
},
],
};
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const endpoint = '/webhook';
const responseData = await affinityApiRequest.call(this, 'GET', endpoint, {});
const webhookUrl = this.getNodeWebhookUrl('default');
const events = this.getNodeParameter('events') as string[];
for (const webhook of responseData) {
if (
eventsExist(webhook.subscriptions as string[], events) &&
webhook.webhook_url === webhookUrl
) {
// Set webhook-id to be sure that it can be deleted
const webhookData = this.getWorkflowStaticData('node');
webhookData.webhookId = webhook.id as string;
return true;
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default') as string;
if (webhookUrl.includes('%20')) {
throw new NodeOperationError(
this.getNode(),
'The name of the Affinity Trigger Node is not allowed to contain any spaces!',
);
}
const events = this.getNodeParameter('events') as string[];
const endpoint = '/webhook/subscribe';
const body = {
webhook_url: webhookUrl,
subscriptions: events,
};
const responseData = await affinityApiRequest.call(this, 'POST', endpoint, body);
if (responseData.id === undefined) {
// Required data is missing so was not successful
return false;
}
const webhookData = this.getWorkflowStaticData('node');
webhookData.webhookId = responseData.id as string;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const endpoint = `/webhook/${webhookData.webhookId}`;
const responseData = await affinityApiRequest.call(this, 'DELETE', endpoint);
if (!responseData.success) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const bodyData = this.getBodyData();
if (bodyData.type === 'sample.webhook') {
return {};
}
let responseData: IDataObject = {};
if (bodyData.type && bodyData.body) {
const resource = (bodyData.type as string).split('.')[0];
//@ts-ignore
const id = bodyData.body.id;
responseData = await affinityApiRequest.call(this, 'GET', `/${mapResource(resource)}/${id}`);
responseData.type = bodyData.type;
}
return {
workflowData: [this.helpers.returnJsonArray(responseData)],
};
}
}
@@ -0,0 +1,95 @@
import type {
IDataObject,
IExecuteFunctions,
ILoadOptionsFunctions,
IHookFunctions,
IWebhookFunctions,
JsonObject,
IRequestOptions,
IHttpRequestMethods,
} from 'n8n-workflow';
import { BINARY_ENCODING, NodeApiError } from 'n8n-workflow';
export async function affinityApiRequest(
this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
resource: string,
body: any = {},
query: IDataObject = {},
uri?: string,
option: IDataObject = {},
): Promise<any> {
const credentials = await this.getCredentials('affinityApi');
const apiKey = `:${credentials.apiKey}`;
const endpoint = 'https://api.affinity.co';
let options: IRequestOptions = {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${Buffer.from(apiKey).toString(BINARY_ENCODING)}`,
},
method,
body,
qs: query,
uri: uri || `${endpoint}${resource}`,
json: true,
};
if (!Object.keys(body as IDataObject).length) {
delete options.body;
}
if (!Object.keys(query).length) {
delete options.qs;
}
options = Object.assign({}, options, option);
try {
return await this.helpers.request(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export async function affinityApiRequestAllItems(
this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions,
propertyName: string,
method: IHttpRequestMethods,
resource: string,
body: any = {},
query: IDataObject = {},
): Promise<any> {
const returnData: IDataObject[] = [];
let responseData;
query.page_size = 500;
do {
responseData = await affinityApiRequest.call(this, method, resource, body, query);
query.page_token = responseData.page_token;
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
} while (responseData.page_token !== undefined && responseData.page_token !== null);
return returnData;
}
export function eventsExist(subscriptions: string[], currentSubsriptions: string[]) {
for (const subscription of currentSubsriptions) {
if (!subscriptions.includes(subscription)) {
return false;
}
}
return true;
}
export function mapResource(key: string) {
return {
person: 'persons',
list: 'lists',
note: 'notes',
organization: 'organizatitons',
list_entry: 'list-entries',
field: 'fields',
file: 'files',
}[key];
}
@@ -0,0 +1,84 @@
import type { INodeProperties } from 'n8n-workflow';
export const listOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['list'],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a list',
action: 'Get a list',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many lists',
action: 'Get many lists',
},
],
default: 'get',
},
];
export const listFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* list:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'List ID',
name: 'listId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['list'],
operation: ['get'],
},
},
description: 'The unique ID of the list object to be retrieved',
},
/* -------------------------------------------------------------------------- */
/* list:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['list'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['list'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 10,
},
default: 5,
description: 'Max number of results to return',
},
];
@@ -0,0 +1,225 @@
import type { INodeProperties } from 'n8n-workflow';
export const listEntryOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['listEntry'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a list entry',
action: 'Create a list entry',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a list entry',
action: 'Delete a list entry',
},
{
name: 'Get',
value: 'get',
description: 'Get a list entry',
action: 'Get a list entry',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many list entries',
action: 'Get many list entries',
},
],
default: 'create',
},
];
export const listEntryFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* listEntry:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'List Name or ID',
name: 'listId',
type: 'options',
required: true,
typeOptions: {
loadOptionsMethod: 'getLists',
},
default: '',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['create'],
},
},
description:
'The unique ID of the list whose list entries are to be retrieved. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Entity ID',
name: 'entityId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['create'],
},
},
description:
'The unique ID of the entity (person, organization, or opportunity) to add to this list',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['create'],
},
},
options: [
{
displayName: 'Creator ID',
name: 'creator_id',
type: 'string',
default: '',
description:
'The ID of a Person resource who should be recorded as adding the entry to the list. Must be a person who can access Affinity. If not provided the creator defaults to the owner of the API key.',
},
],
},
/* -------------------------------------------------------------------------- */
/* listEntry:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'List Name or ID',
name: 'listId',
type: 'options',
required: true,
typeOptions: {
loadOptionsMethod: 'getLists',
},
default: '',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['get'],
},
},
description:
'The unique ID of the list that contains the specified list_entry_id. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'List Entry ID',
name: 'listEntryId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['get'],
},
},
description: 'The unique ID of the list entry object to be retrieved',
},
/* -------------------------------------------------------------------------- */
/* listEntry:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'List Name or ID',
name: 'listId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getLists',
},
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['getAll'],
},
},
default: '',
description:
'The unique ID of the list whose list entries are to be retrieved. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 10,
},
default: 5,
description: 'Max number of results to return',
},
/* -------------------------------------------------------------------------- */
/* listEntry:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'List Name or ID',
name: 'listId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getLists',
},
required: true,
default: '',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['delete'],
},
},
description:
'The unique ID of the list that contains the specified list_entry_id. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'List Entry ID',
name: 'listEntryId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['listEntry'],
operation: ['delete'],
},
},
description: 'The unique ID of the list entry object to be deleted',
},
];
@@ -0,0 +1,285 @@
import type { INodeProperties } from 'n8n-workflow';
export const organizationOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['organization'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an organization',
action: 'Create an organization',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete an organization',
action: 'Delete an organization',
},
{
name: 'Get',
value: 'get',
description: 'Get an organization',
action: 'Get an organization',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many organizations',
action: 'Get many organizations',
},
{
name: 'Update',
value: 'update',
description: 'Update an organization',
action: 'Update an organization',
},
],
default: 'create',
},
];
export const organizationFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* organization:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Name',
name: 'name',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['organization'],
operation: ['create'],
},
},
description: 'The name of the organization',
},
{
displayName: 'Domain',
name: 'domain',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['organization'],
operation: ['create'],
},
},
description: 'The domain name of the organization',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['organization'],
operation: ['create'],
},
},
options: [
{
displayName: 'Person Names or IDs',
name: 'persons',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getPersons',
},
default: [],
description:
'Persons that the new organization will be associated with. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
],
},
/* -------------------------------------------------------------------------- */
/* organization:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['organization'],
operation: ['update'],
},
},
description: 'Unique identifier for the organization',
},
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['organization'],
operation: ['update'],
},
},
options: [
{
displayName: 'Domain',
name: 'domain',
type: 'string',
default: '',
description: 'The domain name of the organization',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'The name of the organization',
},
{
displayName: 'Person Names or IDs',
name: 'persons',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getPersons',
},
default: [],
description:
'Persons that the new organization will be associated with. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
],
},
/* -------------------------------------------------------------------------- */
/* organization:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['organization'],
operation: ['get'],
},
},
description: 'Unique identifier for the organization',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
resource: ['organization'],
operation: ['get'],
},
},
options: [
{
displayName: 'With Interaction Dates',
name: 'withInteractionDates',
type: 'boolean',
default: false,
description: 'Whether interaction dates will be present on the returned resources',
},
],
},
/* -------------------------------------------------------------------------- */
/* organization:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['organization'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['organization'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 10,
},
default: 5,
description: 'Max number of results to return',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
resource: ['organization'],
operation: ['getAll'],
},
},
options: [
{
displayName: 'Term',
name: 'term',
type: 'string',
default: '',
description:
'A string used to search all the organizations in your teams address book. This could be an email address, a first name or a last name.',
},
{
displayName: 'With Interaction Dates',
name: 'withInteractionDates',
type: 'boolean',
default: false,
description: 'Whether interaction dates will be present on the returned resources',
},
],
},
/* -------------------------------------------------------------------------- */
/* organization:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization ID',
name: 'organizationId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['organization'],
operation: ['delete'],
},
},
description: 'Unique identifier for the organization',
},
];
@@ -0,0 +1,5 @@
export interface IOrganization {
name?: string;
domain?: string;
person_ids?: number[];
}
@@ -0,0 +1,321 @@
import type { INodeProperties } from 'n8n-workflow';
export const personOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['person'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a person',
action: 'Create a person',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a person',
action: 'Delete a person',
},
{
name: 'Get',
value: 'get',
description: 'Get a person',
action: 'Get a person',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many persons',
action: 'Get many people',
},
{
name: 'Update',
value: 'update',
description: 'Update a person',
action: 'Update a person',
},
],
default: 'create',
},
];
export const personFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* person:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'First Name',
name: 'firstName',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['person'],
operation: ['create'],
},
},
description: 'The first name of the person',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['person'],
operation: ['create'],
},
},
description: 'The last name of the person',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['person'],
operation: ['create'],
},
},
options: [
{
displayName: 'Organization Names or IDs',
name: 'organizations',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: [],
description:
'Organizations that the person is associated with. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
],
},
{
displayName: 'Emails',
name: 'emails',
type: 'string',
description: 'The email addresses of the person',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add To Email',
},
displayOptions: {
show: {
resource: ['person'],
operation: ['create'],
},
},
placeholder: 'info@example.com',
default: [],
},
/* -------------------------------------------------------------------------- */
/* person:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Person ID',
name: 'personId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['person'],
operation: ['update'],
},
},
description: 'Unique identifier for the person',
},
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['person'],
operation: ['update'],
},
},
options: [
{
displayName: 'First Name',
name: 'firstName',
type: 'string',
default: '',
description: 'The first name of the person',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
default: '',
description: 'The last name of the person',
},
{
displayName: 'Organization Names or IDs',
name: 'organizations',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: [],
description:
'Organizations that the person is associated with. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
],
},
{
displayName: 'Emails',
name: 'emails',
type: 'string',
description: 'The email addresses of the person',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add To Email',
},
displayOptions: {
show: {
resource: ['person'],
operation: ['update'],
},
},
placeholder: 'info@example.com',
default: [],
},
/* -------------------------------------------------------------------------- */
/* person:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Person ID',
name: 'personId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['person'],
operation: ['get'],
},
},
description: 'Unique identifier for the person',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
resource: ['person'],
operation: ['get'],
},
},
options: [
{
displayName: 'With Interaction Dates',
name: 'withInteractionDates',
type: 'boolean',
default: false,
description: 'Whether interaction dates will be present on the returned resources',
},
],
},
/* -------------------------------------------------------------------------- */
/* person:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['person'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['person'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 10,
},
default: 5,
description: 'Max number of results to return',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
resource: ['person'],
operation: ['getAll'],
},
},
options: [
{
displayName: 'Term',
name: 'term',
type: 'string',
default: '',
description:
'A string used to search all the persons in your teams address book. This could be an email address, a first name or a last name.',
},
{
displayName: 'With Interaction Dates',
name: 'withInteractionDates',
type: 'boolean',
default: false,
description: 'Whether interaction dates will be present on the returned resources',
},
],
},
/* -------------------------------------------------------------------------- */
/* person:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Person ID',
name: 'personId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['person'],
operation: ['delete'],
},
},
description: 'Unique identifier for the person',
},
];
@@ -0,0 +1,6 @@
export interface IPerson {
first_name?: string;
last_name?: string;
emails?: string[];
organization_ids?: number[];
}
@@ -0,0 +1,27 @@
{
"type": "object",
"properties": {
"creator_id": {
"type": "integer"
},
"id": {
"type": "integer"
},
"list_size": {
"type": "integer"
},
"name": {
"type": "string"
},
"owner_id": {
"type": "integer"
},
"public": {
"type": "boolean"
},
"type": {
"type": "integer"
}
},
"version": 1
}
@@ -0,0 +1,50 @@
{
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"creator_id": {
"type": "integer"
},
"entity": {
"type": "object",
"properties": {
"crunchbase_uuid": {
"type": "null"
},
"domain": {
"type": "string"
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"global": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"entity_id": {
"type": "integer"
},
"entity_type": {
"type": "integer"
},
"id": {
"type": "integer"
},
"list_id": {
"type": "integer"
}
},
"version": 1
}
@@ -0,0 +1,47 @@
{
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"creator_id": {
"type": "integer"
},
"entity": {
"type": "object",
"properties": {
"crunchbase_uuid": {
"type": "null"
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"global": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"entity_id": {
"type": "integer"
},
"entity_type": {
"type": "integer"
},
"id": {
"type": "integer"
},
"list_id": {
"type": "integer"
}
},
"version": 3
}
@@ -0,0 +1,33 @@
{
"type": "object",
"properties": {
"crunchbase_uuid": {
"type": "null"
},
"domain": {
"type": "string"
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"global": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"person_ids": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"version": 1
}
@@ -0,0 +1,59 @@
{
"type": "object",
"properties": {
"crunchbase_uuid": {
"type": "null"
},
"domain": {
"type": "string"
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"global": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"list_entries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"creator_id": {
"type": "integer"
},
"entity_id": {
"type": "integer"
},
"entity_type": {
"type": "integer"
},
"id": {
"type": "integer"
},
"list_id": {
"type": "integer"
}
}
}
},
"name": {
"type": "string"
},
"person_ids": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"version": 3
}
@@ -0,0 +1,27 @@
{
"type": "object",
"properties": {
"crunchbase_uuid": {
"type": "null"
},
"domain": {
"type": "string"
},
"domains": {
"type": "array",
"items": {
"type": "string"
}
},
"global": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
},
"version": 2
}
@@ -0,0 +1,56 @@
{
"type": "object",
"properties": {
"emails": {
"type": "array",
"items": {
"type": "string"
}
},
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"list_entries": {
"type": "array",
"items": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"creator_id": {
"type": "integer"
},
"entity_id": {
"type": "integer"
},
"entity_type": {
"type": "integer"
},
"id": {
"type": "integer"
},
"list_id": {
"type": "integer"
}
}
}
},
"organization_ids": {
"type": "array",
"items": {
"type": "integer"
}
},
"type": {
"type": "integer"
}
},
"version": 1
}
@@ -0,0 +1,24 @@
{
"type": "object",
"properties": {
"emails": {
"type": "array",
"items": {
"type": "string"
}
},
"first_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"last_name": {
"type": "string"
},
"type": {
"type": "integer"
}
},
"version": 1
}
@@ -0,0 +1,8 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_1002_8324" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40">
<path d="M32.4632 23.7334L34.3904 25.6595C35.5228 26.8199 36.152 28.3799 36.1418 30.0012C36.1315 31.6225 35.4825 33.1744 34.3356 34.3204C33.1887 35.4664 31.6362 36.1141 30.0149 36.1231C28.3936 36.132 26.8341 35.5015 25.6746 34.3682L23.7308 32.4226C23.8337 32.0677 23.8864 31.7001 23.8875 31.3306C23.8873 31.005 23.8452 30.6807 23.7621 30.3658L30.3478 23.7813C31.043 23.9627 31.7751 23.9461 32.4614 23.7334M5.70165 34.296C4.55763 33.1545 3.90875 31.6086 3.89524 29.9925C3.88174 28.3765 4.5047 26.82 5.62949 25.6595L7.63246 23.6689C8.30009 23.9123 9.02155 23.9686 9.71884 23.8316L16.2128 30.322C16.124 30.6499 16.0791 30.988 16.0791 31.3277C16.0785 31.712 16.1347 32.0943 16.246 32.4622L14.3435 34.3658C13.1809 35.4886 11.6239 36.1101 10.0077 36.0965C8.3915 36.0828 6.84523 35.4353 5.70165 34.2931M20.8783 12.1628L27.5853 18.8656C27.3587 19.6074 27.3587 20.4 27.5853 21.1418V21.1377L21.1161 27.6009C20.3906 27.3803 19.6165 27.3758 18.8884 27.5879L12.6292 21.3341C12.944 20.4731 12.944 19.5284 12.6292 18.6674L19.1321 12.1734C19.7073 12.3038 20.3048 12.3002 20.8783 12.1628ZM16.2826 9.60611V9.60256V9.60848V9.60611ZM23.6971 9.56056C23.8241 9.17437 23.889 8.77043 23.8893 8.36387C23.8882 8.07789 23.856 7.79287 23.7935 7.51381L25.6705 5.63743C26.8335 4.52383 28.3862 3.90983 29.9963 3.92691C31.6064 3.94399 33.1458 4.59079 34.2849 5.72882C35.424 6.86685 36.0723 8.40564 36.0909 10.0157C36.1095 11.6258 35.497 13.1792 34.3845 14.3432L32.459 16.2699C32.0911 16.159 31.7088 16.1027 31.3244 16.1031C31.0019 16.1025 30.6807 16.1433 30.3685 16.2243L23.6971 9.56056ZM5.62949 14.3414C4.50287 13.1801 3.87837 11.6222 3.89091 10.0042C3.90346 8.38625 4.55204 6.83818 5.69654 5.69447C6.84103 4.55075 8.38954 3.90322 10.0075 3.89177C11.6255 3.88033 13.183 4.50588 14.3435 5.63329L16.1856 7.47655C16.0168 8.18138 16.0505 8.91955 16.2826 9.60611L9.72062 16.1723C9.0221 16.0358 8.29948 16.095 7.63246 16.3432L5.62949 14.3414ZM9.98859 2.34538e-06C8.01364 -0.0013512 6.08268 0.583186 4.44006 1.67964C2.79744 2.7761 1.51699 4.33519 0.760763 6.15962C0.00453271 7.98404 -0.193492 9.9918 0.191747 11.9288C0.576986 13.8658 1.52817 15.645 2.92494 17.0412L5.13022 19.2483C5.03143 19.7437 5.03143 20.2537 5.13022 20.7491L2.92257 22.9555C1.05059 24.8291 -0.000455671 27.3695 0.000653692 30.018C0.00176306 32.6665 1.05494 35.2061 2.92849 37.0781C4.80204 38.9501 7.3425 40.0011 9.99099 40C12.6395 39.9989 15.1791 38.9457 17.051 37.0722L19.0138 35.1082C19.6625 35.2804 20.345 35.2804 20.9937 35.1082L22.9689 37.0828C24.3351 38.4256 26.0587 39.3472 27.9342 39.7377C29.8096 40.1283 31.7578 39.9713 33.5465 39.2855C35.3352 38.5997 36.889 37.414 38.0227 35.8697C39.1563 34.3255 39.822 32.4878 39.9403 30.5758V29.3484C39.7956 26.9469 38.7846 24.6794 37.095 22.9668L35.1027 20.9638C35.1813 20.6508 35.221 20.3294 35.221 20.0067C35.221 19.6802 35.1789 19.3551 35.0956 19.0395L37.0944 17.0424C38.7765 15.3375 39.7863 13.0824 39.9379 10.6922V9.39552C39.813 7.48725 39.1429 5.65496 38.0072 4.11633C36.8716 2.5777 35.3181 1.3974 33.5314 0.715656C31.7447 0.033914 29.7999 -0.120614 27.9279 0.270429C26.056 0.661473 24.3356 1.58165 22.9712 2.92165L21.225 4.66671C20.4336 4.39806 19.5765 4.39287 18.7819 4.65192L17.0528 2.9252C15.1791 1.052 12.6381 -0.000209017 9.98859 2.34538e-06Z" fill="white"/>
</mask>
<g mask="url(#mask0_1002_8324)">
<rect x="-0.479492" y="-0.450134" width="40.8575" height="40.8575" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB