2019-11-09 16:54:25 -08:00
import { IExecuteFunctions } from 'n8n-core' ;
import {
2019-11-21 15:42:26 -08:00
ILoadOptionsFunctions ,
INodeExecutionData , INodePropertyOptions ,
INodeType ,
INodeTypeDescription ,
2019-11-09 16:54:25 -08:00
} from 'n8n-workflow' ;
import { OptionsWithUri } from 'request' ;
2019-11-11 17:01:43 -08:00
import {
2019-11-21 15:42:26 -08:00
layoutsApiRequest ,
getFields ,
getPortals ,
getScripts ,
getToken ,
parseSort ,
parsePortals ,
parseQuery ,
parseScripts ,
parseFields ,
logout
2019-11-11 17:01:43 -08:00
} from "./GenericFunctions" ;
2019-11-09 16:54:25 -08:00
export class FileMaker implements INodeType {
2019-11-21 15:42:26 -08:00
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 : [
{
2019-11-29 15:13:06 -08:00
name : 'fileMaker' ,
2019-11-21 15:42:26 -08:00
required : true ,
} ,
] ,
properties : [
{
displayName : 'Action' ,
name : 'action' ,
type : 'options' ,
default : 'record' ,
options : [
/ * {
2019-11-21 22:08:53 -08:00
name : 'Login' ,
value : 'login' ,
} ,
{
name : 'Logout' ,
value : 'logout' ,
} , * /
2019-11-21 15:42:26 -08:00
{
name : 'Find Records' ,
value : 'find' ,
} ,
{
2019-11-21 22:08:53 -08:00
name : 'Get Records' ,
2019-11-21 15:42:26 -08:00
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' ,
} ,
] ,
description : 'Action to perform.' ,
} ,
// ----------------------------------
// shared
// ----------------------------------
{
displayName : 'Layout' ,
name : 'layout' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getLayouts' ,
} ,
options : [ ] ,
default : '' ,
required : true ,
displayOptions : { } ,
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)' ,
} ,
{
2019-11-21 22:08:53 -08:00
displayName : 'Offset' ,
2019-11-21 15:42:26 -08:00
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' ,
] ,
} ,
}
} ,
{
2019-11-21 22:08:53 -08:00
displayName : 'Limit' ,
2019-11-21 15:42:26 -08:00
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 : 'Get portals' ,
name : 'getPortals' ,
type : 'boolean' ,
default : false ,
description : 'Should we get portal data as well ?' ,
displayOptions : {
show : {
action : [
'record' ,
'records' ,
'find' ,
] ,
} ,
} ,
} ,
{
displayName : 'Portals' ,
name : 'portals' ,
type : 'options' ,
typeOptions : {
multipleValues : true ,
multipleValueButtonText : 'Add portal' ,
loadOptionsMethod : 'getPortals' ,
} ,
options : [ ] ,
default : [ ] ,
displayOptions : {
show : {
action : [
'record' ,
'records' ,
'find' ,
] ,
getPortals : [
true ,
] ,
} ,
} ,
placeholder : 'Portals' ,
2019-11-21 22:08:53 -08:00
description : 'The portal result set to return. Use the portal object name or portal<br />table name. If this parameter is omitted, the API will return all<br />portal objects and records in the layout. For best performance<br />, pass the portal object name or portal table name.' ,
2019-11-21 15:42:26 -08:00
} ,
// ----------------------------------
// find/records
// ----------------------------------
{
displayName : 'Response Layout' ,
name : 'responseLayout' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getResponseLayouts' ,
} ,
options : [ ] ,
default : '' ,
required : false ,
displayOptions : {
show : {
action : [
'find'
] ,
} ,
} ,
} ,
{
displayName : 'Queries' ,
name : 'queries' ,
placeholder : 'Add query' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
} ,
displayOptions : {
show : {
action : [
'find' ,
] ,
} ,
} ,
description : 'Queries ' ,
default : { } ,
options : [
{
name : 'query' ,
displayName : 'Query' ,
values : [
{
displayName : 'Fields' ,
name : 'fields' ,
placeholder : 'Add field' ,
type : 'fixedCollection' ,
default : { } ,
typeOptions : {
multipleValues : true ,
} ,
options : [ {
name : 'field' ,
displayName : 'Field' ,
values : [
{
displayName : 'Field' ,
name : 'name' ,
type : 'options' ,
default : '' ,
typeOptions : {
loadOptionsMethod : 'getFields' ,
} ,
options : [ ] ,
description : 'Search Field' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
description : 'Value to search' ,
} ,
]
}
] ,
description : 'Field Name' ,
} ,
{
displayName : 'Omit' ,
name : 'omit' ,
type : 'boolean' ,
default : false
} ,
]
} ,
] ,
} ,
{
2019-11-21 22:08:53 -08:00
displayName : 'Sort data?' ,
2019-11-21 15:42:26 -08:00
name : 'setSort' ,
type : 'boolean' ,
default : false ,
description : 'Should we sort data ?' ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
} ,
} ,
} ,
{
displayName : 'Sort' ,
name : 'sortParametersUi' ,
placeholder : 'Add Sort Rules' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
} ,
displayOptions : {
show : {
setSort : [
true ,
] ,
action : [
'find' ,
'records' ,
] ,
} ,
} ,
description : 'Sort rules' ,
default : { } ,
options : [
{
name : 'rules' ,
displayName : 'Rules' ,
values : [
{
displayName : 'Field' ,
name : 'name' ,
type : 'options' ,
default : '' ,
typeOptions : {
loadOptionsMethod : 'getFields' ,
} ,
options : [ ] ,
description : 'Field Name.' ,
} ,
{
displayName : 'Order' ,
name : 'value' ,
type : 'options' ,
default : 'ascend' ,
options : [
{
name : 'Ascend' ,
value : 'ascend'
} ,
{
name : 'Descend' ,
value : 'descend'
} ,
] ,
description : 'Sort order.' ,
} ,
]
} ,
] ,
} ,
{
displayName : 'Before find script' ,
name : 'setScriptBefore' ,
type : 'boolean' ,
default : false ,
description : 'Define a script to be run before the action specified by the API call and after the subsequent sort.' ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
}
} ,
} ,
{
displayName : 'Script Name' ,
name : 'scriptBefore' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getScripts' ,
} ,
options : [ ] ,
default : '' ,
required : true ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
setScriptBefore : [
true
] ,
} ,
} ,
placeholder : 'Script Name' ,
description : 'The name of the FileMaker script to be run after the action specified by the API call and after the subsequent sort.' ,
} ,
{
displayName : 'Script Parameter' ,
name : 'scriptBeforeParam' ,
type : 'string' ,
default : '' ,
required : false ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
setScriptBefore : [
true
] ,
} ,
} ,
placeholder : 'Script Parameters' ,
description : 'A parameter for the FileMaker script.' ,
} ,
{
displayName : 'Before sort script' ,
name : 'setScriptSort' ,
type : 'boolean' ,
default : false ,
description : 'Define a script to be run after the action specified by the API call but before the subsequent sort.' ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
}
} ,
} ,
{
displayName : 'Script Name' ,
name : 'scriptSort' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getScripts' ,
} ,
options : [ ] ,
default : '' ,
required : true ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
setScriptSort : [
true
] ,
} ,
} ,
placeholder : 'Script Name' ,
description : 'The name of the FileMaker script to be run after the action specified by the API call but before the subsequent sort.' ,
} ,
{
displayName : 'Script Parameter' ,
name : 'scriptSortParam' ,
type : 'string' ,
default : '' ,
required : false ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
setScriptSort : [
true
] ,
} ,
} ,
placeholder : 'Script Parameters' ,
description : 'A parameter for the FileMaker script.' ,
} ,
{
displayName : 'After sort script' ,
name : 'setScriptAfter' ,
type : 'boolean' ,
default : false ,
description : 'Define a script to be run after the action specified by the API call but before the subsequent sort.' ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
}
} ,
} ,
{
displayName : 'Script Name' ,
name : 'scriptAfter' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getScripts' ,
} ,
options : [ ] ,
default : '' ,
required : true ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
setScriptAfter : [
true
] ,
} ,
} ,
placeholder : 'Script Name' ,
description : 'The name of the FileMaker script to be run after the action specified by the API call and after the subsequent sort.' ,
} ,
{
displayName : 'Script Parameter' ,
name : 'scriptAfterParam' ,
type : 'string' ,
default : '' ,
required : false ,
displayOptions : {
show : {
action : [
'find' ,
'record' ,
'records' ,
] ,
setScriptAfter : [
true
] ,
} ,
} ,
placeholder : 'Script Parameters' ,
description : 'A parameter for the FileMaker script.' ,
} ,
// ----------------------------------
// create/edit
// ----------------------------------
/ * {
2019-11-21 22:08:53 -08:00
displayName : 'fieldData' ,
name : 'fieldData' ,
placeholder : '{"field1": "value", "field2": "value", ...}' ,
description : 'Additional fields to add.' ,
type : 'string' ,
default : '{}' ,
displayOptions : {
show : {
action : [
'create' ,
'edit' ,
] ,
} ,
}
} , * /
2019-11-21 15:42:26 -08:00
{
displayName : 'Mod Id' ,
name : 'modId' ,
description : 'The last modification ID. When you use modId, a record is edited only when the modId matches.' ,
type : 'number' ,
default : '' ,
displayOptions : {
show : {
action : [
'edit' ,
] ,
} ,
}
} ,
{
displayName : 'Fields' ,
name : 'fieldsParametersUi' ,
placeholder : 'Add field' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
} ,
displayOptions : {
show : {
action : [
'create' ,
'edit' ,
] ,
} ,
} ,
description : 'Fields to define' ,
default : { } ,
options : [
{
name : 'fields' ,
displayName : 'Fields' ,
values : [
{
displayName : 'Field' ,
name : 'name' ,
type : 'options' ,
default : '' ,
typeOptions : {
loadOptionsMethod : 'getFields' ,
} ,
options : [ ] ,
description : 'Field Name.' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
default : '' ,
} ,
]
} ,
] ,
} ,
// ----------------------------------
// performscript
// ----------------------------------
{
displayName : 'Script Name' ,
name : 'script' ,
type : 'options' ,
typeOptions : {
loadOptionsMethod : 'getScripts' ,
} ,
options : [ ] ,
default : '' ,
required : true ,
displayOptions : {
show : {
action : [
'performscript'
] ,
} ,
} ,
placeholder : 'Script Name' ,
description : 'The name of the FileMaker script to be run.' ,
} ,
{
displayName : 'Script Parameter' ,
name : 'scriptParam' ,
type : 'string' ,
default : '' ,
required : false ,
displayOptions : {
show : {
action : [
'performscript'
] ,
} ,
} ,
placeholder : 'Script Parameters' ,
description : 'A parameter for the FileMaker script.' ,
} ,
]
} ;
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 [ ] > {
2019-12-01 04:00:21 -08:00
let returnData : INodePropertyOptions [ ] ;
2019-11-21 15:42:26 -08:00
try {
2019-12-01 04:00:21 -08:00
returnData = await layoutsApiRequest . call ( this ) ;
2019-11-21 15:42:26 -08:00
} catch ( err ) {
throw new Error ( ` FileMaker Error: ${ err } ` ) ;
}
2019-12-01 04:00:21 -08:00
2019-11-21 15:42:26 -08:00
return returnData ;
} ,
async getResponseLayouts ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
const returnData : INodePropertyOptions [ ] = [ ] ;
returnData . push ( {
name : 'Use main layout' ,
value : '' ,
} ) ;
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 getScripts ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
const returnData : INodePropertyOptions [ ] = [ ] ;
let scripts ;
try {
scripts = await getScripts . call ( this ) ;
} catch ( err ) {
throw new Error ( ` FileMaker Error: ${ err } ` ) ;
}
for ( const script of scripts ) {
if ( ! script . isFolder ) {
returnData . push ( {
name : script.name ,
value : script.name ,
} ) ;
}
}
return returnData ;
} ,
async getPortals ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
const returnData : INodePropertyOptions [ ] = [ ] ;
let portals ;
try {
portals = await getPortals . call ( this ) ;
} catch ( err ) {
throw new Error ( ` FileMaker Error: ${ err } ` ) ;
}
Object . keys ( portals ) . forEach ( ( portal ) = > {
returnData . push ( {
name : portal ,
value : portal ,
} ) ;
} ) ;
return returnData ;
} ,
} ,
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
const returnData : INodeExecutionData [ ] = [ ] ;
2019-11-21 22:08:53 -08:00
const credentials = this . getCredentials ( 'fileMaker' ) ;
2019-11-21 15:42:26 -08:00
if ( credentials === undefined ) {
throw new Error ( 'No credentials got returned!' ) ;
}
let token ;
try {
token = await getToken . call ( this ) ;
} catch ( e ) {
throw new Error ( ` Login fail: ${ e } ` ) ;
}
let requestOptions : OptionsWithUri ;
const host = credentials . host as string ;
const database = credentials . db as string ;
const url = ` https:// ${ host } /fmi/data/v1 ` ;
const action = this . getNodeParameter ( 'action' , 0 ) as string ;
for ( let i = 0 ; i < items . length ; i ++ ) {
// Reset all values
requestOptions = {
uri : '' ,
headers : {
'Authorization' : ` Bearer ${ token } ` ,
} ,
method : 'GET' ,
json : true
} ;
const layout = this . getNodeParameter ( 'layout' , 0 ) as string ;
if ( action === 'record' ) {
const recid = this . getNodeParameter ( 'recid' , 0 ) as string ;
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /records/ ${ recid } ` ;
requestOptions . qs = {
'portal' : JSON . stringify ( parsePortals . call ( this ) ) ,
. . . parseScripts . call ( this )
} ;
} else if ( action === 'records' ) {
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /records ` ;
requestOptions . qs = {
'_offset' : this . getNodeParameter ( 'offset' , 0 ) ,
'_limit' : this . getNodeParameter ( 'limit' , 0 ) ,
'portal' : JSON . stringify ( parsePortals . call ( this ) ) ,
. . . parseScripts . call ( this )
} ;
const sort = parseSort . call ( this ) ;
if ( sort ) {
requestOptions . body . sort = sort ;
}
} else if ( action === 'find' ) {
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /_find ` ;
requestOptions . method = 'POST' ;
requestOptions . body = {
'query' : parseQuery . call ( this ) ,
'offset' : this . getNodeParameter ( 'offset' , 0 ) ,
'limit' : this . getNodeParameter ( 'limit' , 0 ) ,
'layout.response' : this . getNodeParameter ( 'responseLayout' , 0 ) ,
. . . parseScripts . call ( this )
} ;
const sort = parseSort . call ( this ) ;
if ( sort ) {
requestOptions . body . sort = sort ;
}
} else if ( action === 'create' ) {
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /records ` ;
requestOptions . method = 'POST' ;
requestOptions . headers ! [ 'Content-Type' ] = 'application/json' ;
//TODO: handle portalData
requestOptions . body = {
fieldData : { . . . parseFields . call ( this ) } ,
portalData : { } ,
. . . parseScripts . call ( this )
} ;
} else if ( action === 'edit' ) {
const recid = this . getNodeParameter ( 'recid' , 0 ) as string ;
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /records/ ${ recid } ` ;
requestOptions . method = 'PATCH' ;
requestOptions . headers ! [ 'Content-Type' ] = 'application/json' ;
//TODO: handle portalData
requestOptions . body = {
fieldData : { . . . parseFields . call ( this ) } ,
portalData : { } ,
. . . parseScripts . call ( this )
} ;
} else if ( action === 'performscript' ) {
const scriptName = this . getNodeParameter ( 'script' , 0 ) as string ;
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /script/ ${ scriptName } ` ;
requestOptions . qs = {
'script.param' : this . getNodeParameter ( 'scriptParam' , 0 ) ,
} ;
} else if ( action === 'duplicate' ) {
const recid = this . getNodeParameter ( 'recid' , 0 ) as string ;
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /records/ ${ recid } ` ;
requestOptions . method = 'POST' ;
requestOptions . headers ! [ 'Content-Type' ] = 'application/json' ;
requestOptions . qs = {
. . . parseScripts . call ( this )
} ;
} else if ( action === 'delete' ) {
const recid = this . getNodeParameter ( 'recid' , 0 ) as string ;
requestOptions . uri = url + ` /databases/ ${ database } /layouts/ ${ layout } /records/ ${ recid } ` ;
requestOptions . method = 'DELETE' ;
2019-11-14 04:02:57 -08:00
requestOptions . qs = {
2019-11-21 15:42:26 -08:00
. . . parseScripts . call ( this )
} ;
} 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 ) ;
}
2019-11-14 04:02:57 -08:00
}