diff --git a/packages/nodes-base/credentials/AdaloApi.credentials.ts b/packages/nodes-base/credentials/AdaloApi.credentials.ts new file mode 100644 index 0000000000000..8ab8588fa51f9 --- /dev/null +++ b/packages/nodes-base/credentials/AdaloApi.credentials.ts @@ -0,0 +1,32 @@ +import { + IAuthenticateBearer, + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class AdaloApi implements ICredentialType { + name = 'adaloApi'; + displayName = 'Adalo API'; + documentationUrl = 'adalo'; + properties: INodeProperties[] = [ + { + displayName: 'API Token', + name: 'apiToken', + type: 'string', + default: '', + }, + { + displayName: 'Application ID', + name: 'appId', + type: 'string', + default: '', + }, + ]; + + authenticate = { + type: 'bearer', + properties: { + tokenPropertyName: 'apiToken', + }, + } as IAuthenticateBearer; +} diff --git a/packages/nodes-base/nodes/Adalo/Adalo.node.json b/packages/nodes-base/nodes/Adalo/Adalo.node.json new file mode 100644 index 0000000000000..10b6ee6ffdab8 --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/Adalo.node.json @@ -0,0 +1,20 @@ +{ + "node": "n8n-nodes-base.adalo", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Data & Storage" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/adalo" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.adalo/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Adalo/Adalo.node.ts b/packages/nodes-base/nodes/Adalo/Adalo.node.ts new file mode 100644 index 0000000000000..af8f21a6af9f5 --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/Adalo.node.ts @@ -0,0 +1,198 @@ +import { + IDataObject, + IExecuteSingleFunctions, + IHttpRequestOptions, + IN8nHttpFullResponse, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import {operationFields} from './OperationDescription'; +import {FieldsUiValues} from './types'; + +export class Adalo implements INodeType { + async presendCreateUpdate (this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise { + const dataToSend = this.getNodeParameter('dataToSend', 0) as 'defineBelow' | 'autoMapInputData'; + + requestOptions.body = {}; + + if (dataToSend === 'autoMapInputData') { + const inputData = this.getInputData(); + const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore') as string; + + const inputKeysToIgnore = rawInputsToIgnore.split(',').map(c => c.trim()); + const inputKeys = Object.keys(inputData.json) + .filter((key) => !inputKeysToIgnore.includes(key)); + + for (const key of inputKeys) { + (requestOptions.body as IDataObject)[key] = inputData.json[key]; + } + } else { + const fields = this.getNodeParameter('fieldsUi.fieldValues') as FieldsUiValues; + + for (const field of fields) { + (requestOptions.body as IDataObject)[field.fieldId] = field.fieldValue; + } + } + + return requestOptions; + } + + description: INodeTypeDescription = { + displayName: 'Adalo', + name: 'adalo', + icon: 'file:adalo.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Adalo API', + defaults: { + name: 'Adalo', + color: '#4f44d7', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'adaloApi', + required: true, + + // @TODO FInd a proper way to test credentials + testedBy: { + request: { + method: 'GET', + url: '/', + }, + rules: [ + { + type: 'responseCode', + properties: { + value: 403, + message: 'Does not exist.', + }, + }, + ], + }, + }, + ], + requestDefaults: { + baseURL: '=https://api.adalo.com/v0/apps/{{$credentials.appId}}', + }, + requestOperations: { + pagination: { + type: 'offset', + properties: { + limitParameter: 'limit', + offsetParameter: 'offset', + pageSize: 100, + rootProperty: '', + type: 'query', + }, + }, + }, + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'string', + required: true, + default: '', + description: 'Your Adalo collection ID', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a row', + routing: { + send: { + preSend: [ + this.presendCreateUpdate, + ], + }, + request: { + method: 'POST', + url: '=/collections/{{$parameter["resource"]}}', + }, + }, + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a row', + routing: { + request: { + method: 'DELETE', + url: '=/collections/{{$parameter["resource"]}}/{{$parameter["rowId"]}}', + }, + output: { + postReceive: [ + { + type: 'set', + properties: { + value: '={{ { "success": true } }}', + }, + }, + ], + }, + }, + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a row', + routing: { + request: { + method: 'GET', + url: '=/collections/{{$parameter["resource"]}}/{{$parameter["rowId"]}}', + }, + }, + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all rows', + routing: { + request: { + method: 'GET', + url: '=/collections/{{$parameter["resource"]}}', + }, + output: { + postReceive: [ + async function (this: IExecuteSingleFunctions, items: INodeExecutionData[], response: IN8nHttpFullResponse,): Promise { + const { records } = response.body as { records: IDataObject[] }; + + return [...records.map((json) => ({ json }))]; + }, + ], + }, + }, + }, + { + name: 'Update', + value: 'update', + description: 'Update a row', + routing: { + send: { + preSend: [ + this.presendCreateUpdate, + ], + }, + request: { + method: 'PUT', + url: '=/collections/{{$parameter["resource"]}}/{{$parameter["rowId"]}}', + }, + }, + }, + ], + default: 'getAll', + }, + ...operationFields, + ], + }; + +} diff --git a/packages/nodes-base/nodes/Adalo/OperationDescription.ts b/packages/nodes-base/nodes/Adalo/OperationDescription.ts new file mode 100644 index 0000000000000..0f62e480bbacc --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/OperationDescription.ts @@ -0,0 +1,196 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const operationFields: INodeProperties[] = [ + /** + * get + */ + + { + displayName: 'Row ID', + name: 'rowId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'get', + ], + }, + }, + default: '', + required: true, + description: 'ID of the row to return', + }, + + /** + * update + */ + + { + displayName: 'Row ID', + name: 'rowId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'update', + ], + }, + }, + default: '', + required: true, + description: 'ID of the row to update', + }, + + /** + * create / update + */ + + { + displayName: 'Data to Send', + name: 'dataToSend', + type: 'options', + options: [ + { + name: 'Auto-map Input Data to Columns', + value: 'autoMapInputData', + description: 'Use when node input properties match destination column names', + }, + { + name: 'Define Below for Each Column', + value: 'defineBelow', + description: 'Set the value for each destination column', + }, + ], + displayOptions: { + show: { + operation: [ + 'create', + 'update', + ], + }, + }, + default: 'defineBelow', + description: 'Whether to insert the input data this node receives in the new row', + }, + { + displayName: 'Inputs to Ignore', + name: 'inputsToIgnore', + type: 'string', + displayOptions: { + show: { + operation: [ + 'create', + 'update', + ], + dataToSend: [ + 'autoMapInputData', + ], + }, + }, + default: '', + required: false, + description: 'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.', + placeholder: 'Enter properties...', + }, + { + displayName: 'Fields to Send', + name: 'fieldsUi', + placeholder: 'Add Field', + type: 'fixedCollection', + typeOptions: { + multipleValueButtonText: 'Add Field to Send', + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + 'update', + ], + dataToSend: [ + 'defineBelow', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Field', + name: 'fieldValues', + values: [ + { + displayName: 'Field ID', + name: 'fieldId', + type: 'string', + default: '', + }, + { + displayName: 'Field Value', + name: 'fieldValue', + type: 'string', + default: '', + }, + ], + }, + ], + }, + + /** + * delete + */ + + { + displayName: 'Row ID', + name: 'rowId', + type: 'string', + displayOptions: { + show: { + operation: [ + 'delete', + ], + }, + }, + default: '', + required: true, + description: 'ID of the row to delete', + }, + + /** + * getAll + */ + + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 100, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + }, + }, + description: 'You can request up to 100 records per page using the limit parameter.', + }, +]; diff --git a/packages/nodes-base/nodes/Adalo/adalo.svg b/packages/nodes-base/nodes/Adalo/adalo.svg new file mode 100644 index 0000000000000..2f1ff40fcd3c1 --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/adalo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/nodes-base/nodes/Adalo/types.d.ts b/packages/nodes-base/nodes/Adalo/types.d.ts new file mode 100644 index 0000000000000..d23cc3791b82d --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/types.d.ts @@ -0,0 +1,11 @@ +export type AdaloCredentials = { + apiKey: string; + appId: string; +}; + +export type FieldsUiValues = Array<{ + fieldId: string; + fieldValue: string; +}>; + +export type Operation = 'create' | 'delete' | 'update' | 'get' | 'getAll'; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c38da295ce2d2..dd7b0ed26bc3d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -34,6 +34,7 @@ "dist/credentials/ActiveCampaignApi.credentials.js", "dist/credentials/AcuitySchedulingApi.credentials.js", "dist/credentials/AcuitySchedulingOAuth2Api.credentials.js", + "dist/credentials/AdaloApi.credentials.js", "dist/credentials/AffinityApi.credentials.js", "dist/credentials/AgileCrmApi.credentials.js", "dist/credentials/AirtableApi.credentials.js", @@ -333,6 +334,7 @@ "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", + "dist/nodes/Adalo/Adalo.node.js", "dist/nodes/Affinity/Affinity.node.js", "dist/nodes/Affinity/AffinityTrigger.node.js", "dist/nodes/AgileCrm/AgileCrm.node.js",