n8n-rdstation-crm
Version:
Nós personalizados do n8n para integração com a API do RD Station CRM (v1)
686 lines (638 loc) • 17.3 kB
text/typescript
import {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow';
import { OptionsWithUri } from 'request-promise-native';
/**
* Classe para o nó de Contatos do RD Station CRM
* Este nó permite interações com a API de Contatos do RD Station CRM.
*/
export class RdStationCrmContacts implements INodeType {
description: INodeTypeDescription = {
// Informações básicas do nó
displayName: 'RD Station CRM Contatos',
name: 'rdStationCrmContacts',
icon: 'file:rdstation.svg', // Ícone a ser exibido no n8n
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Operações com contatos no RD Station CRM',
defaults: {
name: 'RD Station CRM Contatos',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'rdStationCrmApi',
required: true,
},
],
properties: [
// Seleção do recurso (neste caso, só temos contatos)
{
displayName: 'Recurso',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Contato',
value: 'contact',
},
],
default: 'contact',
required: true,
},
// Seleção da operação a ser realizada
{
displayName: 'Operação',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
'contact',
],
},
},
options: [
{
name: 'Criar',
value: 'create',
description: 'Criar um novo contato',
action: 'Criar um contato',
},
{
name: 'Atualizar',
value: 'update',
description: 'Atualizar um contato existente',
action: 'Atualizar um contato',
},
{
name: 'Listar',
value: 'getAll',
description: 'Listar contatos',
action: 'Listar contatos',
},
{
name: 'Obter',
value: 'get',
description: 'Obter um contato pelo UUID ou e-mail',
action: 'Obter um contato',
},
],
default: 'getAll',
},
// Campos para a operação LISTAR CONTATOS
{
displayName: 'Parâmetros de Listagem',
name: 'listParameters',
type: 'collection',
placeholder: 'Adicionar Parâmetros',
default: {},
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Limite',
name: 'limit',
type: 'number',
default: 50,
description: 'Número máximo de contatos a retornar',
},
{
displayName: 'Página',
name: 'page',
type: 'number',
default: 1,
description: 'Número da página para retornar',
},
{
displayName: 'Busca por Nome',
name: 'q',
type: 'string',
default: '',
description: 'Buscar contatos por nome',
},
{
displayName: 'Ordenação',
name: 'order',
type: 'options',
options: [
{
name: 'Nome',
value: 'name',
},
{
name: 'Data de Criação',
value: 'created_at',
},
],
default: 'name',
description: 'Campo pelo qual ordenar os resultados',
},
{
displayName: 'Direção da Ordenação',
name: 'direction',
type: 'options',
options: [
{
name: 'Ascendente',
value: 'asc',
},
{
name: 'Descendente',
value: 'desc',
},
],
default: 'asc',
description: 'Direção da ordenação (crescente ou decrescente)',
},
{
displayName: 'Filtrar por E-mail',
name: 'email',
type: 'string',
default: '',
description: 'Filtrar contatos por e-mail',
},
{
displayName: 'Filtrar por Telefone',
name: 'phone',
type: 'string',
default: '',
description: 'Filtrar contatos por telefone',
},
{
displayName: 'Filtrar por Cargo',
name: 'title',
type: 'string',
default: '',
description: 'Filtrar contatos por cargo',
},
],
},
// Campos para a operação OBTER CONTATO
{
displayName: 'Tipo de Identificador',
name: 'identifierType',
type: 'options',
options: [
{
name: 'UUID',
value: 'uuid',
},
{
name: 'E-mail',
value: 'email',
},
],
default: 'uuid',
required: true,
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'get',
],
},
},
description: 'Tipo de identificador para buscar o contato',
},
{
displayName: 'Valor do Identificador',
name: 'identifierValue',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'get',
],
},
},
description: 'UUID ou e-mail do contato a ser obtido',
},
// Campos para a operação ATUALIZAR CONTATO
{
displayName: 'Tipo de Identificador',
name: 'updateIdentifierType',
type: 'options',
options: [
{
name: 'UUID',
value: 'uuid',
},
{
name: 'E-mail',
value: 'email',
},
],
default: 'uuid',
required: true,
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'update',
],
},
},
description: 'Tipo de identificador do contato a ser atualizado',
},
{
displayName: 'Valor do Identificador',
name: 'updateIdentifierValue',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'update',
],
},
},
description: 'UUID ou e-mail do contato a ser atualizado',
},
// Campos comuns para CRIAR e ATUALIZAR contatos
{
displayName: 'Dados do Contato',
name: 'contactData',
type: 'collection',
placeholder: 'Adicionar Dados',
default: {},
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'create',
'update',
],
},
},
options: [
{
displayName: 'Nome',
name: 'name',
type: 'string',
default: '',
description: 'Nome do contato (obrigatório para criação, mínimo 2 caracteres)',
},
{
displayName: 'Cargo',
name: 'title',
type: 'string',
default: '',
description: 'Cargo ou título profissional do contato',
},
{
displayName: 'Data de Nascimento',
name: 'birthday',
type: 'string',
default: '',
description: 'Data de nascimento do contato (formato YYYY-MM-DD)',
},
{
displayName: 'ID da Empresa',
name: 'organization_id',
type: 'string',
default: '',
description: 'ID da empresa à qual o contato está vinculado',
},
],
},
// E-mails do contato (para CRIAR e ATUALIZAR)
{
displayName: 'E-mails',
name: 'emails',
placeholder: 'Adicionar E-mail',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'create',
'update',
],
},
},
default: {},
options: [
{
name: 'emailsValues',
displayName: 'E-mail',
values: [
{
displayName: 'E-mail',
name: 'email',
type: 'string',
default: '',
description: 'Endereço de e-mail do contato',
required: true,
},
{
displayName: 'Tipo',
name: 'type',
type: 'options',
options: [
{
name: 'Trabalho',
value: 'work',
},
{
name: 'Pessoal',
value: 'home',
},
{
name: 'Outro',
value: 'other',
},
],
default: 'work',
description: 'Tipo de e-mail',
},
],
},
],
},
// Telefones do contato (para CRIAR e ATUALIZAR)
{
displayName: 'Telefones',
name: 'phones',
placeholder: 'Adicionar Telefone',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
resource: [
'contact',
],
operation: [
'create',
'update',
],
},
},
default: {},
options: [
{
name: 'phonesValues',
displayName: 'Telefone',
values: [
{
displayName: 'Número',
name: 'phone',
type: 'string',
default: '',
description: 'Número de telefone do contato',
required: true,
},
{
displayName: 'Tipo',
name: 'type',
type: 'options',
options: [
{
name: 'Móvel',
value: 'mobile',
},
{
name: 'Trabalho',
value: 'work',
},
{
name: 'Residencial',
value: 'home',
},
{
name: 'Fax',
value: 'fax',
},
],
default: 'mobile',
description: 'Tipo de telefone',
},
],
},
],
},
],
};
/**
* Método principal que executa o nó com base na operação selecionada
*/
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
// URL base da API do RD Station CRM
const baseUrl = 'https://crm.rdstation.com/api/v1';
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
// Para cada item de entrada, processa a operação solicitada
for (let i = 0; i < items.length; i++) {
try {
// Configuração comum para todas as requisições
const options: OptionsWithUri = {
uri: '', // Uri inicial vazio, será definido conforme a operação
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
json: true,
resolveWithFullResponse: true,
};
// Vamos determinar qual operação executar com base no recurso e operação selecionados
if (resource === 'contact') {
if (operation === 'getAll') {
// Operação: LISTAR CONTATOS
// Preparando a requisição para listar contatos
options.method = 'GET';
options.uri = `${baseUrl}/contacts`;
// Obtendo e aplicando parâmetros de filtro/paginação
const listParameters = this.getNodeParameter('listParameters', i) as {
limit?: number;
page?: number;
q?: string;
order?: string;
direction?: string;
email?: string;
phone?: string;
title?: string;
};
const qs: Record<string, any> = {};
// Adicionando os parâmetros à query string
if (listParameters.limit) qs.limit = listParameters.limit;
if (listParameters.page) qs.page = listParameters.page;
if (listParameters.q) qs.q = listParameters.q;
if (listParameters.order) qs.order = listParameters.order;
if (listParameters.direction) qs.direction = listParameters.direction;
if (listParameters.email) qs.email = listParameters.email;
if (listParameters.phone) qs.phone = listParameters.phone;
if (listParameters.title) qs.title = listParameters.title;
options.qs = qs;
} else if (operation === 'get') {
// Operação: OBTER CONTATO ESPECÍFICO
// Determina o tipo de identificador (UUID ou email)
const identifierType = this.getNodeParameter('identifierType', i) as string;
const identifierValue = this.getNodeParameter('identifierValue', i) as string;
// Verifica se o valor do identificador foi fornecido
if (!identifierValue) {
throw new NodeOperationError(this.getNode(), 'É necessário fornecer um UUID ou e-mail válido!', { itemIndex: i });
}
// Configurando a requisição para buscar um contato específico
options.method = 'GET';
options.uri = `${baseUrl}/contacts/${identifierValue}`;
} else if (operation === 'create') {
// Operação: CRIAR CONTATO
// Configurando requisição para criar contato
options.method = 'POST';
options.uri = `${baseUrl}/contacts`;
// Obtendo os dados do contato
const contactData = this.getNodeParameter('contactData', i) as {
name?: string;
title?: string;
birthday?: string;
organization_id?: string;
};
// Verificando se o nome foi fornecido (obrigatório para criação)
if (!contactData.name || contactData.name.length < 2) {
throw new NodeOperationError(
this.getNode(),
'O nome do contato é obrigatório e deve ter pelo menos 2 caracteres!',
{ itemIndex: i },
);
}
// Preparando o corpo da requisição
const body: any = { ...contactData };
// Adicionando e-mails ao corpo
const emailsCollection = this.getNodeParameter('emails', i) as {
emailsValues?: Array<{ email: string; type: string }>;
};
if (emailsCollection.emailsValues && emailsCollection.emailsValues.length > 0) {
body.emails = emailsCollection.emailsValues;
}
// Adicionando telefones ao corpo
const phonesCollection = this.getNodeParameter('phones', i) as {
phonesValues?: Array<{ phone: string; type: string }>;
};
if (phonesCollection.phonesValues && phonesCollection.phonesValues.length > 0) {
body.phones = phonesCollection.phonesValues;
}
options.body = body;
} else if (operation === 'update') {
// Operação: ATUALIZAR CONTATO
// Obtém o tipo e valor do identificador para atualização
const identifierType = this.getNodeParameter('updateIdentifierType', i) as string;
const identifierValue = this.getNodeParameter('updateIdentifierValue', i) as string;
// Verifica se o valor do identificador foi fornecido
if (!identifierValue) {
throw new NodeOperationError(
this.getNode(),
'É necessário fornecer um UUID ou e-mail válido para atualizar o contato!',
{ itemIndex: i },
);
}
// Configurando a requisição para atualizar o contato
options.method = 'PATCH';
options.uri = `${baseUrl}/contacts/${identifierValue}`;
// Obtendo os dados a serem atualizados
const contactData = this.getNodeParameter('contactData', i) as {
name?: string;
title?: string;
birthday?: string;
organization_id?: string;
};
// Preparando o corpo da requisição
const body: any = {};
// Adicionando apenas campos que não estão vazios
if (contactData.name) body.name = contactData.name;
if (contactData.title) body.title = contactData.title;
if (contactData.birthday) body.birthday = contactData.birthday;
if (contactData.organization_id) body.organization_id = contactData.organization_id;
// Adicionando e-mails ao corpo, se fornecidos
const emailsCollection = this.getNodeParameter('emails', i) as {
emailsValues?: Array<{ email: string; type: string }>;
};
if (emailsCollection.emailsValues && emailsCollection.emailsValues.length > 0) {
body.emails = emailsCollection.emailsValues;
}
// Adicionando telefones ao corpo, se fornecidos
const phonesCollection = this.getNodeParameter('phones', i) as {
phonesValues?: Array<{ phone: string; type: string }>;
};
if (phonesCollection.phonesValues && phonesCollection.phonesValues.length > 0) {
body.phones = phonesCollection.phonesValues;
}
// Verifica se há campos para atualizar
if (Object.keys(body).length === 0) {
throw new NodeOperationError(
this.getNode(),
'É necessário fornecer pelo menos um campo para atualizar o contato!',
{ itemIndex: i },
);
}
options.body = body;
}
}
// Executa a requisição HTTP com as opções configuradas
const response = await this.helpers.requestWithAuthentication.call(
this,
'rdStationCrmApi',
options,
);
// Extrai os dados da resposta e adiciona ao retorno
const responseData = response.body;
returnData.push({
json: responseData,
pairedItem: { item: i },
});
} catch (error) {
// Trata e reporta erros ocorridos durante a execução
if (this.continueOnFail()) {
returnData.push({
json: {
error: error instanceof Error ? error.message : String(error),
},
pairedItem: { item: i },
});
continue;
}
throw error;
}
}
return [returnData];
}
}