Auth & get records management

This commit is contained in:
Romain Dunand 2019-11-10 01:54:25 +01:00
parent b013e09f4a
commit f18fc9d910
6 changed files with 647 additions and 1 deletions

View file

@ -0,0 +1,39 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class FileMaker implements ICredentialType {
name = 'FileMaker';
displayName = 'FileMaker';
properties = [
{
displayName: 'Host',
name: 'host',
type: 'string' as NodePropertyTypes,
default: '',
},
{
displayName: 'Database',
name: 'db',
type: 'string' as NodePropertyTypes,
default: '',
},
{
displayName: 'Login',
name: 'login',
type: 'string' as NodePropertyTypes,
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string' as NodePropertyTypes,
typeOptions: {
password: true,
},
default: '',
},
];
}

View file

@ -0,0 +1,421 @@
import {IExecuteFunctions} from 'n8n-core';
import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData, INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import {OptionsWithUri} from 'request';
import {layoutsApiRequest, getFields, getToken, logout} from "./GenericFunctions";
export class FileMaker implements INodeType {
description: INodeTypeDescription = {
displayName: 'FileMaker',
name: 'filemaker',
icon: 'file:filemaker.png',
group: ['input'],
version: 1,
description: 'Retrieve data from FileMaker data API.',
defaults: {
name: 'FileMaker',
color: '#665533',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'FileMaker',
required: true,
},
],
properties: [
{
displayName: 'Action',
name: 'action',
type: 'options',
options: [
/*{
name: 'Login',
value: 'login',
},
{
name: 'Logout',
value: 'logout',
},*/
{
name: 'Find Records',
value: 'find',
},
{
name: 'get Records',
value: 'records',
},
{
name: 'Get Records By Id',
value: 'record',
},
{
name: 'Perform Script',
value: 'performscript',
},
{
name: 'Create Record',
value: 'create',
},
{
name: 'Edit Record',
value: 'edit',
},
{
name: 'Duplicate Record',
value: 'duplicate',
},
{
name: 'Delete Record',
value: 'delete',
},
],
default: 'login',
description: 'Action to perform.',
},
// ----------------------------------
// shared
// ----------------------------------
{
displayName: 'Layout',
name: 'layout',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getLayouts',
},
options: [],
default: '',
required: true,
displayOptions: {
hide: {
action: [
'performscript'
],
},
},
placeholder: 'Layout Name',
description: 'FileMaker Layout Name.',
},
{
displayName: 'Record Id',
name: 'recid',
type: 'number',
default: '',
required: true,
displayOptions: {
show: {
action: [
'record',
'edit',
'delete',
'duplicate',
],
},
},
placeholder: 'Record ID',
description: 'Internal Record ID returned by get (recordid)',
},
// ----------------------------------
// find/records
// ----------------------------------
{
displayName: 'offset',
name: 'offset',
placeholder: '0',
description: 'The record number of the first record in the range of records.',
type: 'number',
default: '1',
displayOptions: {
show: {
action: [
'find',
'records',
],
},
}
},
{
displayName: 'limit',
name: 'limit',
placeholder: '100',
description: 'The maximum number of records that should be returned. If not specified, the default value is 100.',
type: 'number',
default: '100',
displayOptions: {
show: {
action: [
'find',
'records',
],
},
}
},
{
displayName: 'Sort',
name: 'sortParametersUi',
placeholder: 'Add Sort Rules',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
displayOptions: {
show: {
action: [
'find',
'records',
],
},
},
description: 'Sort rules',
default: {},
options: [
{
name: 'rules',
displayName: 'Rules',
values: [
{
displayName: 'Name',
name: 'name',
type: 'options',
default: '',
typeOptions: {
loadOptionsMethod: 'getFields',
},
options: [],
description: 'Field Name.',
},
{
displayName: 'Value',
name: 'value',
type: 'options',
default: 'ascend',
options: [
{
name: 'Ascend',
value: 'ascend'
},
{
name: 'Descend',
value: 'descend'
},
],
description: 'Sort order.',
},
]
},
],
},
// ----------------------------------
// create/edit
// ----------------------------------
{
displayName: 'fieldData',
name: 'fieldData',
placeholder: '{"field1": "value", "field2": "value", ...}',
description: 'Additional fields to add.',
type: 'string',
default: '{}',
displayOptions: {
show: {
action: [
'create',
'edit',
],
},
}
},
{
displayName: 'Fields',
name: 'Fields',
type: 'collection',
typeOptions: {
loadOptionsMethod: 'getFields',
},
options: [],
default: '',
required: true,
displayOptions: {
show: {
action: [
'create',
'edit',
],
},
},
placeholder: 'Layout Name',
description: 'FileMaker Layout Name.',
},
// ----------------------------------
// performscript
// ----------------------------------
{
displayName: 'Script Name',
name: 'script',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getScripts',
},
options: [],
default: '',
required: true,
displayOptions: {
show: {
action: [
'performscript'
],
},
},
placeholder: 'Layout Name',
description: 'FileMaker Layout Name.',
},
]
};
methods = {
loadOptions: {
// Get all the available topics to display them to user so that he can
// select them easily
async getLayouts(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
let layouts;
try {
layouts = await layoutsApiRequest.call(this);
} catch (err) {
throw new Error(`FileMaker Error: ${err}`);
}
for (const layout of layouts) {
returnData.push({
name: layout.name,
value: layout.name,
});
}
return returnData;
},
async getFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
let fields;
try {
fields = await getFields.call(this);
} catch (err) {
throw new Error(`FileMaker Error: ${err}`);
}
for (const field of fields) {
returnData.push({
name: field.name,
value: field.name,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const credentials = this.getCredentials('FileMaker');
const action = this.getNodeParameter('action', 0) as string;
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const staticData = this.getWorkflowStaticData('global');
// Operations which overwrite the returned data
const overwriteDataOperations = [];
// Operations which overwrite the returned data and return arrays
// and has so to be merged with the data of other items
const overwriteDataOperationsArray = [];
let requestOptions: OptionsWithUri;
const host = credentials.host as string;
const database = credentials.db as string;
//const layout = this.getNodeParameter('layout', 0, null) as string;
//const recid = this.getNodeParameter('recid', 0, null) as number;
const url = `https://${host}/fmi/data/v1`;
//const fullOperation = `${resource}:${operation}`;
for (let i = 0; i < items.length; i++) {
// Reset all values
requestOptions = {
uri: '',
headers: {},
method: 'GET',
json: true
//rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean,
};
const layout = this.getNodeParameter('layout', 0) as string;
const token = await getToken.call(this);
if (action === 'record') {
const recid = this.getNodeParameter('recid', 0) as string;
requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`;
requestOptions.method = 'GET';
requestOptions.headers = {
'Authorization': `Bearer ${token}`,
};
} else if (action === 'records') {
requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`;
requestOptions.method = 'GET';
requestOptions.headers = {
'Authorization': `Bearer ${token}`,
};
const sort = [];
const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject;
if (sortParametersUi.parameter !== undefined) {
// @ts-ignore
for (const parameterData of sortParametersUi!.rules as IDataObject[]) {
// @ts-ignore
sort.push({
'fieldName': parameterData!.name as string,
'sortOrder': parameterData!.value
});
}
}
requestOptions.qs = {
'_offset': this.getNodeParameter('offset', 0),
'_limit': this.getNodeParameter('limit', 0),
'_sort': JSON.stringify(sort),
};
} else {
throw new Error(`The action "${action}" is not implemented yet!`);
}
// Now that the options are all set make the actual http request
let response;
try {
response = await this.helpers.request(requestOptions);
} catch (error) {
response = error.response.body;
}
if (typeof response === 'string') {
throw new Error('Response body is not valid JSON. Change "Response Format" to "String"');
}
await logout.call(this, token);
returnData.push({json: response});
}
return this.prepareOutputData(returnData);
}
}

View file

@ -0,0 +1,185 @@
import {
IExecuteFunctions,
ILoadOptionsFunctions,
IExecuteSingleFunctions
} from 'n8n-core';
import {
ICredentialDataDecryptedObject,
IDataObject,
} from 'n8n-workflow';
import { OptionsWithUri } from 'request';
/**
* Make an API request to ActiveCampaign
*
* @param {IHookFunctions} this
* @param {string} method
* @returns {Promise<any>}
*/
export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise<any> { // tslint:disable-line:no-any
const token = await getToken.call(this);
const credentials = this.getCredentials('FileMaker');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const host = credentials.host as string;
const db = credentials.db as string;
const url = `https://${host}/fmi/data/v1/databases/${db}/layouts`;
const options: OptionsWithUri = {
headers: {
'Authorization': `Bearer ${token}`,
},
method: 'GET',
uri: url,
json: true
};
try {
const responseData = await this.helpers.request!(options);
return responseData.response.layouts;
} catch (error) {
// If that data does not exist for some reason return the actual error
throw error;
}
}
/**
* Make an API request to ActiveCampaign
*
* @returns {Promise<any>}
* @param layout
*/
export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise<any> { // tslint:disable-line:no-any
const token = await getToken.call(this);
const credentials = this.getCredentials('FileMaker');
const layout = this.getCurrentNodeParameter('layout') as string;
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const host = credentials.host as string;
const db = credentials.db as string;
const url = `https://${host}/fmi/data/v1/databases/${db}/layouts/${layout}`;
const options: OptionsWithUri = {
headers: {
'Authorization': `Bearer ${token}`,
},
method: 'GET',
uri: url,
json: true
};
try {
const responseData = await this.helpers.request!(options);
return responseData.response.fieldMetaData;
} catch (error) {
// If that data does not exist for some reason return the actual error
throw error;
}
}
export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('FileMaker');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const host = credentials.host as string;
const db = credentials.db as string;
const login = credentials.login as string;
const password = credentials.password as string;
const url = `https://${host}/fmi/data/v1/databases/${db}/sessions`;
let requestOptions: OptionsWithUri;
// Reset all values
requestOptions = {
uri: url,
headers: {},
method: 'POST',
json: true
//rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean,
};
requestOptions.auth = {
user: login as string,
pass: password as string,
};
requestOptions.body = {
"fmDataSource": [
{
"database": host,
"username": login as string,
"password": password as string
}
]
};
try {
const response = await this.helpers.request!(requestOptions);
if (typeof response === 'string') {
throw new Error('Response body is not valid JSON. Change "Response Format" to "String"');
}
return response.response.token;
} catch (error) {
console.error(error);
const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')';
if (errorMessage !== undefined) {
throw errorMessage;
}
throw error.response.body;
}
}
export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions, token: string): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('FileMaker');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
const host = credentials.host as string;
const db = credentials.db as string;
const url = `https://${host}/fmi/data/v1/databases/${db}/sessions/${token}`;
let requestOptions: OptionsWithUri;
// Reset all values
requestOptions = {
uri: url,
headers: {},
method: 'DELETE',
json: true
//rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean,
};
try {
const response = await this.helpers.request!(requestOptions);
if (typeof response === 'string') {
throw new Error('Response body is not valid JSON. Change "Response Format" to "String"');
}
return response;
} catch (error) {
console.error(error);
const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')';
if (errorMessage !== undefined) {
throw errorMessage;
}
throw error.response.body;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -360,7 +360,6 @@ export class HttpRequest implements INodeType {
},
],
},
// Body Parameter
{
displayName: 'Body Parameters',

View file

@ -33,6 +33,7 @@
"dist/credentials/Aws.credentials.js",
"dist/credentials/ChargebeeApi.credentials.js",
"dist/credentials/DropboxApi.credentials.js",
"dist/credentials/FileMaker.credentials.js",
"dist/credentials/GithubApi.credentials.js",
"dist/credentials/GitlabApi.credentials.js",
"dist/credentials/GoogleApi.credentials.js",
@ -78,6 +79,7 @@
"dist/nodes/EmailSend.node.js",
"dist/nodes/ErrorTrigger.node.js",
"dist/nodes/ExecuteCommand.node.js",
"dist/nodes/FileMaker/FileMaker.node.js",
"dist/nodes/Function.node.js",
"dist/nodes/FunctionItem.node.js",
"dist/nodes/Github/Github.node.js",