mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 14:44:05 -08:00
9ef339e525
* fix(Action Network Node): Pagination
* Fixed lint issue
* Added credential test
* ⚡ Move credentials verification and injection to the credentials file
Co-authored-by: Jonathan Bennetts <jonathan.bennetts@gmail.com>
Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
341 lines
7.8 KiB
TypeScript
341 lines
7.8 KiB
TypeScript
import {
|
|
IExecuteFunctions,
|
|
} from 'n8n-core';
|
|
|
|
import {
|
|
IDataObject,
|
|
ILoadOptionsFunctions,
|
|
NodeApiError,
|
|
} from 'n8n-workflow';
|
|
|
|
import {
|
|
OptionsWithUri,
|
|
} from 'request';
|
|
|
|
import {
|
|
flow,
|
|
omit,
|
|
} from 'lodash';
|
|
|
|
import {
|
|
AllFieldsUi,
|
|
FieldWithPrimaryField,
|
|
LinksFieldContainer,
|
|
PersonResponse,
|
|
PetitionResponse,
|
|
Resource,
|
|
Response,
|
|
} from './types';
|
|
|
|
export async function actionNetworkApiRequest(
|
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
|
method: string,
|
|
endpoint: string,
|
|
body: IDataObject = {},
|
|
qs: IDataObject = {},
|
|
) {
|
|
const options: OptionsWithUri = {
|
|
method,
|
|
body,
|
|
qs,
|
|
uri: `https://actionnetwork.org/api/v2${endpoint}`,
|
|
json: true,
|
|
};
|
|
|
|
if (!Object.keys(body).length) {
|
|
delete options.body;
|
|
}
|
|
|
|
if (!Object.keys(qs).length) {
|
|
delete options.qs;
|
|
}
|
|
|
|
try {
|
|
return await this.helpers.requestWithAuthentication.call(this, 'actionNetworkApi', options);
|
|
} catch (error) {
|
|
throw new NodeApiError(this.getNode(), error);
|
|
}
|
|
}
|
|
|
|
export async function handleListing(
|
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
|
method: string,
|
|
endpoint: string,
|
|
body: IDataObject = {},
|
|
qs: IDataObject = {},
|
|
options?: { returnAll: true },
|
|
) {
|
|
const returnData: IDataObject[] = [];
|
|
let responseData;
|
|
|
|
qs.perPage = 25; // max
|
|
qs.page = 1;
|
|
|
|
const returnAll = options?.returnAll ?? this.getNodeParameter('returnAll', 0, false) as boolean;
|
|
const limit = this.getNodeParameter('limit', 0, 0) as number;
|
|
|
|
const itemsKey = toItemsKey(endpoint);
|
|
|
|
do {
|
|
responseData = await actionNetworkApiRequest.call(this, method, endpoint, body, qs);
|
|
const items = responseData._embedded[itemsKey];
|
|
returnData.push(...items);
|
|
|
|
if (!returnAll && returnData.length >= limit) {
|
|
return returnData.slice(0, limit);
|
|
}
|
|
|
|
if (responseData._links && responseData._links.next && responseData._links.next.href) {
|
|
const queryString = new URLSearchParams(responseData._links.next.href.split('?')[1]);
|
|
qs.page = queryString.get('page') as string;
|
|
}
|
|
} while (responseData._links && responseData._links.next);
|
|
|
|
return returnData;
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// helpers
|
|
// ----------------------------------------
|
|
|
|
/**
|
|
* Convert an endpoint to the key needed to access data in the response.
|
|
*/
|
|
const toItemsKey = (endpoint: string) => {
|
|
|
|
// handle two-resource endpoint
|
|
if (
|
|
endpoint.includes('/signatures') ||
|
|
endpoint.includes('/attendances') ||
|
|
endpoint.includes('/taggings')
|
|
) {
|
|
endpoint = endpoint.split('/').pop()!;
|
|
}
|
|
|
|
return `osdi:${endpoint.replace(/\//g, '')}`;
|
|
};
|
|
|
|
export const extractId = (response: LinksFieldContainer) => {
|
|
return response._links.self.href.split('/').pop() ?? 'No ID';
|
|
};
|
|
|
|
export const makeOsdiLink = (personId: string) => {
|
|
return {
|
|
_links: {
|
|
'osdi:person': {
|
|
href: `https://actionnetwork.org/api/v2/people/${personId}`,
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
export const isPrimary = (field: FieldWithPrimaryField) => field.primary;
|
|
|
|
|
|
// ----------------------------------------
|
|
// field adjusters
|
|
// ----------------------------------------
|
|
|
|
function adjustLanguagesSpoken(allFields: AllFieldsUi) {
|
|
if (!allFields.languages_spoken) return allFields;
|
|
|
|
return {
|
|
...omit(allFields, ['languages_spoken']),
|
|
languages_spoken: [allFields.languages_spoken],
|
|
};
|
|
}
|
|
|
|
function adjustPhoneNumbers(allFields: AllFieldsUi) {
|
|
if (!allFields.phone_numbers) return allFields;
|
|
|
|
return {
|
|
...omit(allFields, ['phone_numbers']),
|
|
phone_numbers: [
|
|
allFields.phone_numbers.phone_numbers_fields,
|
|
],
|
|
};
|
|
}
|
|
|
|
function adjustPostalAddresses(allFields: AllFieldsUi) {
|
|
if (!allFields.postal_addresses) return allFields;
|
|
|
|
if (allFields.postal_addresses.postal_addresses_fields.length) {
|
|
const adjusted = allFields.postal_addresses.postal_addresses_fields.map((field) => {
|
|
const copy: IDataObject = {
|
|
...omit(field, ['address_lines', 'location']),
|
|
};
|
|
|
|
if (field.address_lines) {
|
|
copy.address_lines = [field.address_lines];
|
|
}
|
|
|
|
if (field.location) {
|
|
copy.location = field.location.location_fields;
|
|
}
|
|
|
|
return copy;
|
|
});
|
|
|
|
return {
|
|
...omit(allFields, ['postal_addresses']),
|
|
postal_addresses: adjusted,
|
|
};
|
|
}
|
|
}
|
|
|
|
function adjustLocation(allFields: AllFieldsUi) {
|
|
if (!allFields.location) return allFields;
|
|
|
|
const locationFields = allFields.location.postal_addresses_fields;
|
|
|
|
const adjusted: IDataObject = {
|
|
...omit(locationFields, ['address_lines', 'location']),
|
|
};
|
|
|
|
if (locationFields.address_lines) {
|
|
adjusted.address_lines = [locationFields.address_lines];
|
|
}
|
|
|
|
if (locationFields.location) {
|
|
adjusted.location = locationFields.location.location_fields;
|
|
}
|
|
|
|
return {
|
|
...omit(allFields, ['location']),
|
|
location: adjusted,
|
|
};
|
|
}
|
|
|
|
function adjustTargets(allFields: AllFieldsUi) {
|
|
if (!allFields.target) return allFields;
|
|
|
|
const adjusted = allFields.target.split(',').map(value => ({ name: value }));
|
|
|
|
return {
|
|
...omit(allFields, ['target']),
|
|
target: adjusted,
|
|
};
|
|
}
|
|
|
|
|
|
// ----------------------------------------
|
|
// payload adjusters
|
|
// ----------------------------------------
|
|
|
|
export const adjustPersonPayload = flow(
|
|
adjustLanguagesSpoken,
|
|
adjustPhoneNumbers,
|
|
adjustPostalAddresses,
|
|
);
|
|
|
|
export const adjustPetitionPayload = adjustTargets;
|
|
|
|
export const adjustEventPayload = adjustLocation;
|
|
|
|
|
|
// ----------------------------------------
|
|
// resource loaders
|
|
// ----------------------------------------
|
|
|
|
async function loadResource(this: ILoadOptionsFunctions, resource: string) {
|
|
return await handleListing.call(this, 'GET', `/${resource}`, {}, {}, { returnAll: true });
|
|
}
|
|
|
|
export const resourceLoaders = {
|
|
|
|
async getTags(this: ILoadOptionsFunctions) {
|
|
const tags = await loadResource.call(this, 'tags') as Array<{ name: string } & LinksFieldContainer>;
|
|
|
|
return tags.map((tag) => ({ name: tag.name, value: extractId(tag) }));
|
|
},
|
|
|
|
async getTaggings(this: ILoadOptionsFunctions) {
|
|
const tagId = this.getNodeParameter('tagId', 0);
|
|
const endpoint = `/tags/${tagId}/taggings`;
|
|
|
|
// two-resource endpoint, so direct call
|
|
const taggings = await handleListing.call(
|
|
this, 'GET', endpoint, {}, {}, { returnAll: true },
|
|
) as LinksFieldContainer[];
|
|
|
|
return taggings.map((tagging) => {
|
|
const taggingId = extractId(tagging);
|
|
|
|
return {
|
|
name: taggingId,
|
|
value: taggingId,
|
|
};
|
|
});
|
|
},
|
|
};
|
|
|
|
|
|
// ----------------------------------------
|
|
// response simplifiers
|
|
// ----------------------------------------
|
|
|
|
export const simplifyResponse = (response: Response, resource: Resource) => {
|
|
if (resource === 'person') {
|
|
return simplifyPersonResponse(response as PersonResponse);
|
|
} else if (resource === 'petition') {
|
|
return simplifyPetitionResponse(response as PetitionResponse);
|
|
}
|
|
|
|
const fieldsToSimplify = [
|
|
'identifiers',
|
|
'_links',
|
|
'action_network:sponsor',
|
|
'reminders',
|
|
];
|
|
|
|
return {
|
|
id: extractId(response),
|
|
...omit(response, fieldsToSimplify),
|
|
};
|
|
};
|
|
|
|
|
|
const simplifyPetitionResponse = (response: PetitionResponse) => {
|
|
const fieldsToSimplify = [
|
|
'identifiers',
|
|
'_links',
|
|
'action_network:hidden',
|
|
'_embedded',
|
|
];
|
|
|
|
return {
|
|
id: extractId(response),
|
|
...omit(response, fieldsToSimplify),
|
|
creator: simplifyPersonResponse(response._embedded['osdi:creator']),
|
|
};
|
|
};
|
|
|
|
const simplifyPersonResponse = (response: PersonResponse) => {
|
|
const emailAddress = response.email_addresses.filter(isPrimary);
|
|
const phoneNumber = response.phone_numbers.filter(isPrimary);
|
|
const postalAddress = response.postal_addresses.filter(isPrimary);
|
|
|
|
const fieldsToSimplify = [
|
|
'identifiers',
|
|
'email_addresses',
|
|
'phone_numbers',
|
|
'postal_addresses',
|
|
'languages_spoken',
|
|
'_links',
|
|
];
|
|
|
|
return {
|
|
id: extractId(response),
|
|
...omit(response, fieldsToSimplify),
|
|
...{ email_address: emailAddress[0].address || '' },
|
|
...{ phone_number: phoneNumber[0].number || '' },
|
|
...{
|
|
postal_address: {
|
|
...postalAddress && omit(postalAddress[0], 'address_lines'),
|
|
address_lines: postalAddress[0].address_lines ?? '',
|
|
},
|
|
},
|
|
language_spoken: response.languages_spoken[0],
|
|
};
|
|
};
|