diff --git a/packages/nodes-base/credentials/EventbriteApi.credentials.ts b/packages/nodes-base/credentials/EventbriteApi.credentials.ts new file mode 100644 index 000000000..9fa48753f --- /dev/null +++ b/packages/nodes-base/credentials/EventbriteApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class EventbriteApi implements ICredentialType { + name = 'eventbriteApi'; + displayName = 'Eventbrite API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts new file mode 100644 index 000000000..ecdb2978d --- /dev/null +++ b/packages/nodes-base/nodes/Eventbrite/EventbriteTrigger.node.ts @@ -0,0 +1,274 @@ +import { + IHookFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodePropertyOptions, + INodeTypeDescription, + INodeType, + IWebhookResponseData, +} from 'n8n-workflow'; + +import { + eventbriteApiRequest, + eventbriteApiRequestAllItems, +} from './GenericFunctions'; + +export class EventbriteTrigger implements INodeType { + description: INodeTypeDescription = { + displayName: 'Eventbrite Trigger', + name: 'eventbrite', + icon: 'file:eventbrite.png', + group: ['trigger'], + version: 1, + description: 'Handle Eventbrite events via webhooks', + defaults: { + name: 'Eventbrite Trigger', + color: '#dc5237', + }, + inputs: [], + outputs: ['main'], + credentials: [ + { + name: 'eventbriteApi', + required: true, + } + ], + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], + properties: [ + { + displayName: 'Organization', + name: 'organization', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getOrganizations' + }, + default: [], + description: '', + }, + { + displayName: 'Event', + name: 'event', + type: 'options', + required: true, + typeOptions: { + loadOptionsMethod: 'getEvents' + }, + default: [], + description: '', + }, + { + displayName: 'Actions', + name: 'actions', + type: 'multiOptions', + options: [ + { + name: 'attendee.updated', + value: 'attendee.updated' + }, + { + name: 'attendee.checked_in', + value: 'attendee.checked_in' + }, + { + name: 'attendee.checked_out', + value: 'attendee.checked_out' + }, + { + name: 'event.created', + value: 'event.created' + }, + { + name: 'event.published', + value: 'event.published' + }, + { + name: 'event.unpublished', + value: 'event.unpublished' + }, + { + name: 'event.updated', + value: 'event.updated' + }, + { + name: 'order.placed', + value: 'order.placed' + }, + { + name: 'order.refunded', + value: 'order.refunded' + }, + { + name: 'order.updated', + value: 'order.updated' + }, + { + name: 'organizer.updated', + value: 'organizer.updated' + }, + { + name: 'ticket_class.created', + value: 'ticket_class.created' + }, + { + name: 'ticket_class.deleted', + value: 'ticket_class.deleted' + }, + { + name: 'ticket_class.updated', + value: 'ticket_class.updated' + }, + { + name: 'venue.updated', + value: 'venue.updated' + }, + ], + required: true, + default: [], + description: '', + }, + { + displayName: 'Resolve Data', + name: 'resolveData', + type: 'boolean', + default: true, + description: 'By default does the webhook-data only contain the URL to receive
the object data manually. If this option gets activated it
will resolve the data automatically.', + }, + ], + + }; + + methods = { + loadOptions: { + // Get all the available organizations to display them to user so that he can + // select them easily + async getOrganizations(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const organizations = await eventbriteApiRequestAllItems.call(this, 'organizations', 'GET', '/users/me/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 events to display them to user so that he can + // select them easily + async getEvents(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const organization = this.getCurrentNodeParameter('organization'); + const events = await eventbriteApiRequestAllItems.call(this, 'events', 'GET', `/organizations/${organization}/events`); + for (const event of events) { + const eventName = event.name.text; + const eventId = event.id; + returnData.push({ + name: eventName, + value: eventId, + }); + } + return returnData; + }, + }, + }; + // @ts-ignore + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + if (webhookData.webhookId === undefined) { + return false; + } + const endpoint = `/webhooks/${webhookData.webhookId}/`; + try { + await eventbriteApiRequest.call(this, 'GET', endpoint); + } catch (e) { + return false; + } + return true; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const webhookData = this.getWorkflowStaticData('node'); + const event = this.getNodeParameter('event') as string; + const actions = this.getNodeParameter('actions') as string[]; + const endpoint = `/webhooks/`; + const body: IDataObject = { + endpoint_url: webhookUrl, + actions: actions.join(','), + event_id: event, + }; + + const responseData = await eventbriteApiRequest.call(this, 'POST', endpoint, body); + + webhookData.webhookId = responseData.id; + return true; + }, + async delete(this: IHookFunctions): Promise { + let responseData; + const webhookData = this.getWorkflowStaticData('node'); + const endpoint = `/webhooks/${webhookData.webhookId}/`; + try { + responseData = await eventbriteApiRequest.call(this, 'DELETE', endpoint); + } catch(error) { + return false; + } + if (!responseData.success) { + return false; + } + delete webhookData.webhookId; + return true; + }, + }, + }; + + async webhook(this: IWebhookFunctions): Promise { + const req = this.getRequestObject(); + + if (req.body.api_url === undefined) { + throw new Error('The received data does not contain required "api_url" property!'); + } + + const resolveData = this.getNodeParameter('resolveData', false) as boolean; + + if (resolveData === false) { + // Return the data as it got received + return { + workflowData: [ + this.helpers.returnJsonArray(req.body), + ], + }; + } + + if (req.body.api_url.includes('api-endpoint-to-fetch-object-details')) { + return { + workflowData: [ + this.helpers.returnJsonArray({ + placeholder: 'Test received. To display actual data of object get the webhook triggered by performing the action which triggers it.', + }) + ], + }; + } + + const responseData = await eventbriteApiRequest.call(this, 'GET', '', {}, undefined, req.body.api_url); + + return { + workflowData: [ + this.helpers.returnJsonArray(responseData), + ], + }; + } +} diff --git a/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts new file mode 100644 index 000000000..285c89b1f --- /dev/null +++ b/packages/nodes-base/nodes/Eventbrite/GenericFunctions.ts @@ -0,0 +1,63 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function eventbriteApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('eventbriteApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + let options: OptionsWithUri = { + headers: { 'Authorization': `Bearer ${credentials.apiKey}`}, + method, + qs, + body, + uri: uri ||`https://www.eventbriteapi.com/v3${resource}`, + json: true + }; + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + let errorMessage = error.message; + if (error.response.body && error.response.body.error_description) { + errorMessage = error.response.body.error_description; + } + + throw new Error('Eventbrite Error: ' + errorMessage); + } +} + +/** + * Make an API request to paginated flow endpoint + * and return all results + */ +export async function eventbriteApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + do { + responseData = await eventbriteApiRequest.call(this, method, resource, body, query); + query.continuation = responseData.pagination.continuation; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData.pagination !== undefined && + responseData.pagination.has_more_items !== undefined && + responseData.pagination.has_more_items !== false + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Eventbrite/eventbrite.png b/packages/nodes-base/nodes/Eventbrite/eventbrite.png new file mode 100644 index 000000000..11ef8cd47 Binary files /dev/null and b/packages/nodes-base/nodes/Eventbrite/eventbrite.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index dc5747eb3..c9ff287a7 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -34,6 +34,7 @@ "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/CodaApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", + "dist/credentials/EventbriteApi.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", "dist/credentials/FileMaker.credentials.js", "dist/credentials/FlowApi.credentials.js", @@ -98,6 +99,7 @@ "dist/nodes/ErrorTrigger.node.js", "dist/nodes/ExecuteCommand.node.js", "dist/nodes/ExecuteWorkflow.node.js", + "dist/nodes/Eventbrite/EventbriteTrigger.node.js", "dist/nodes/FileMaker/FileMaker.node.js", "dist/nodes/Freshdesk/Freshdesk.node.js", "dist/nodes/Flow/Flow.node.js",