diff --git a/packages/nodes-base/credentials/AdaloApi.credentials.ts b/packages/nodes-base/credentials/AdaloApi.credentials.ts new file mode 100644 index 0000000000..bbde6e2446 --- /dev/null +++ b/packages/nodes-base/credentials/AdaloApi.credentials.ts @@ -0,0 +1,34 @@ +import { IAuthenticateGeneric, ICredentialType, INodeProperties } from 'n8n-workflow'; + +export class AdaloApi implements ICredentialType { + name = 'adaloApi'; + displayName = 'Adalo API'; + documentationUrl = 'adalo'; + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + default: '', + description: + 'The Adalo API is available on paid Adalo plans, find more information here', + }, + { + displayName: 'App ID', + name: 'appId', + type: 'string', + default: '', + description: + 'You can get App ID from the URL of your app. For example, if your app URL is https://app.adalo.com/apps/1234567890/screens, then your App ID is 1234567890.', + }, + ]; + + authenticate: IAuthenticateGeneric = { + type: 'generic', + properties: { + headers: { + Authorization: '=Bearer {{$credentials.apiKey}}', + }, + }, + }; +} 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 0000000000..10b6ee6ffd --- /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 0000000000..3f35023bbe --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/Adalo.node.ts @@ -0,0 +1,212 @@ +import { + IDataObject, + IExecuteSingleFunctions, + IHttpRequestOptions, + IN8nHttpFullResponse, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { collectionFields } from './CollectionDescription'; +import { FieldsUiValues } from './types'; + +export class Adalo implements INodeType { + description: INodeTypeDescription = { + displayName: 'Adalo', + name: 'adalo', + icon: 'file:adalo.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["collectionId"]}}', + description: 'Consume Adalo API', + defaults: { + name: 'Adalo', + color: '#4f44d7', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'adaloApi', + required: true, + }, + ], + requestDefaults: { + baseURL: '=https://api.adalo.com/v0/apps/{{$credentials.appId}}', + }, + requestOperations: { + pagination: { + type: 'offset', + properties: { + limitParameter: 'limit', + offsetParameter: 'offset', + pageSize: 100, + type: 'query', + }, + }, + }, + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + default: 'collection', + options: [ + { + name: 'Collection', + value: 'collection', + }, + ], + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a row', + routing: { + send: { + preSend: [this.presendCreateUpdate], + }, + request: { + method: 'POST', + url: '=/collections/{{$parameter["collectionId"]}}', + }, + }, + action: 'Create a row', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a row', + routing: { + request: { + method: 'DELETE', + url: '=/collections/{{$parameter["collectionId"]}}/{{$parameter["rowId"]}}', + }, + output: { + postReceive: [ + { + type: 'set', + properties: { + value: '={{ { "success": true } }}', + }, + }, + ], + }, + }, + action: 'Delete a row', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve a row', + routing: { + request: { + method: 'GET', + url: '=/collections/{{$parameter["collectionId"]}}/{{$parameter["rowId"]}}', + }, + }, + action: 'Retrieve a row', + }, + { + name: 'Get Many', + value: 'getAll', + description: 'Retrieve many rows', + routing: { + request: { + method: 'GET', + url: '=/collections/{{$parameter["collectionId"]}}', + qs: { + limit: '={{$parameter["limit"]}}', + }, + }, + send: { + paginate: '={{$parameter["returnAll"]}}', + }, + output: { + postReceive: [ + { + type: 'rootProperty', + properties: { + property: 'records', + }, + }, + ], + }, + }, + action: 'Retrieve all rows', + }, + { + name: 'Update', + value: 'update', + description: 'Update a row', + routing: { + send: { + preSend: [this.presendCreateUpdate], + }, + request: { + method: 'PUT', + url: '=/collections/{{$parameter["collectionId"]}}/{{$parameter["rowId"]}}', + }, + }, + action: 'Update a row', + }, + ], + default: 'getAll', + }, + { + displayName: 'Collection ID', + name: 'collectionId', + type: 'string', + required: true, + default: '', + description: + 'Open your Adalo application and click on the three buttons beside the collection name, then select API Documentation', + hint: "You can find information about app's collections on https://app.adalo.com/apps/your-app-id/api-docs", + displayOptions: { + show: { + resource: ['collection'], + }, + }, + }, + ...collectionFields, + ], + }; + + 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; + } +} diff --git a/packages/nodes-base/nodes/Adalo/CollectionDescription.ts b/packages/nodes-base/nodes/Adalo/CollectionDescription.ts new file mode 100644 index 0000000000..20c85f2e86 --- /dev/null +++ b/packages/nodes-base/nodes/Adalo/CollectionDescription.ts @@ -0,0 +1,139 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const collectionFields: INodeProperties[] = [ + { + displayName: 'Row ID', + name: 'rowId', + type: 'string', + displayOptions: { + show: { + operation: ['get', 'delete', 'update'], + resource: ['collection'], + }, + }, + default: '', + required: true, + }, + + /** + * 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'], + resource: ['collection'], + }, + }, + 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'], + resource: ['collection'], + }, + }, + default: '', + 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', + description: + 'Field must be defined in the collection, otherwise it will be ignored. If field defined in the collection is not set here, it will be set to null.', + typeOptions: { + multipleValueButtonText: 'Add Field to Send', + multipleValues: true, + }, + displayOptions: { + show: { + operation: ['create', 'update'], + dataToSend: ['defineBelow'], + resource: ['collection'], + }, + }, + default: {}, + options: [ + { + displayName: 'Field', + name: 'fieldValues', + values: [ + { + displayName: 'Field ID', + name: 'fieldId', + type: 'string', + default: '', + }, + { + displayName: 'Field Value', + name: 'fieldValue', + type: 'string', + default: '', + }, + ], + }, + ], + }, + + /** + * 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'], + resource: ['collection'], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 100, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + displayOptions: { + show: { + operation: ['getAll'], + resource: ['collection'], + returnAll: [false], + }, + }, + description: 'Max number of results to return', + }, +]; diff --git a/packages/nodes-base/nodes/Adalo/adalo.svg b/packages/nodes-base/nodes/Adalo/adalo.svg new file mode 100644 index 0000000000..2f1ff40fcd --- /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 0000000000..d23cc3791b --- /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 f3c9f0d9bd..be33929eba 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -33,6 +33,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", @@ -338,6 +339,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",