From e55e8c4fd7f8b0a7f398a07ed8038d925c2a0a4f Mon Sep 17 00:00:00 2001 From: "David G. Simmons" Date: Tue, 7 Jul 2020 09:24:21 -0400 Subject: [PATCH 01/25] Added Node for QuestDB --- .../credentials/Questdb.credentials.ts | 73 ++++ .../nodes-base/nodes/QuestDB/QuestDB.node.ts | 337 ++++++++++++++++++ packages/nodes-base/nodes/QuestDB/questdb.png | Bin 0 -> 11244 bytes 3 files changed, 410 insertions(+) create mode 100644 packages/nodes-base/credentials/Questdb.credentials.ts create mode 100644 packages/nodes-base/nodes/QuestDB/QuestDB.node.ts create mode 100644 packages/nodes-base/nodes/QuestDB/questdb.png diff --git a/packages/nodes-base/credentials/Questdb.credentials.ts b/packages/nodes-base/credentials/Questdb.credentials.ts new file mode 100644 index 0000000000..5cd60f960a --- /dev/null +++ b/packages/nodes-base/credentials/Questdb.credentials.ts @@ -0,0 +1,73 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class QuestDB implements ICredentialType { + name = 'questdb'; + displayName = 'QuestDB'; + properties = [ + { + displayName: 'Host', + name: 'host', + type: 'string' as NodePropertyTypes, + default: 'localhost', + }, + { + displayName: 'Database', + name: 'database', + type: 'string' as NodePropertyTypes, + default: 'qdb', + }, + { + displayName: 'User', + name: 'user', + type: 'string' as NodePropertyTypes, + default: 'admin', + }, + { + displayName: 'Password', + name: 'password', + type: 'string' as NodePropertyTypes, + typeOptions: { + password: true, + }, + default: 'quest', + }, + { + displayName: 'SSL', + name: 'ssl', + type: 'options' as NodePropertyTypes, + options: [ + { + name: 'disable', + value: 'disable', + }, + { + name: 'allow', + value: 'allow', + }, + { + name: 'require', + value: 'require', + }, + { + name: 'verify (not implemented)', + value: 'verify', + }, + { + name: 'verify-full (not implemented)', + value: 'verify-full', + } + ], + default: 'disable', + }, + { + displayName: 'Port', + name: 'port', + type: 'number' as NodePropertyTypes, + default: 8812, + }, + ]; +} diff --git a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts b/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts new file mode 100644 index 0000000000..8e54e102f7 --- /dev/null +++ b/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts @@ -0,0 +1,337 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import * as pgPromise from 'pg-promise'; + + +/** + * Returns of copy of the items which only contains the json data and + * of that only the define properties + * + * @param {INodeExecutionData[]} items The items to copy + * @param {string[]} properties The properties it should include + * @returns + */ +function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { + // Prepare the data to insert and copy it to be returned + let newItem: IDataObject; + return items.map((item) => { + newItem = {}; + for (const property of properties) { + if (item.json[property] === undefined) { + newItem[property] = null; + } else { + newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + } + } + return newItem; + }); +} + + +export class QuestDB implements INodeType { + description: INodeTypeDescription = { + displayName: 'QuestDB', + name: 'questdb', + icon: 'file:questdb.png', + group: ['input'], + version: 1, + description: 'Gets, add and update data in QuestDB.', + defaults: { + name: 'QuestDB', + color: '#336791', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'questdb', + required: true, + } + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Execute Query', + value: 'executeQuery', + description: 'Executes a SQL query.', + }, + { + name: 'Insert', + value: 'insert', + description: 'Insert rows in database.', + }, + { + name: 'Update', + value: 'update', + description: 'Updates rows in database.', + }, + ], + default: 'insert', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // executeQuery + // ---------------------------------- + { + displayName: 'Query', + name: 'query', + type: 'string', + typeOptions: { + rows: 5, + }, + displayOptions: { + show: { + operation: [ + 'executeQuery' + ], + }, + }, + default: '', + placeholder: 'SELECT id, name FROM product WHERE id < 40', + required: true, + description: 'The SQL query to execute.', + }, + + + // ---------------------------------- + // insert + // ---------------------------------- + { + displayName: 'Schema', + name: 'schema', + type: 'string', + displayOptions: { + show: { + operation: [ + 'insert' + ], + }, + }, + default: 'public', + required: true, + description: 'Name of the schema the table belongs to', + }, + { + displayName: 'Table', + name: 'table', + type: 'string', + displayOptions: { + show: { + operation: [ + 'insert' + ], + }, + }, + default: '', + required: true, + description: 'Name of the table in which to insert data to.', + }, + { + displayName: 'Columns', + name: 'columns', + type: 'string', + displayOptions: { + show: { + operation: [ + 'insert' + ], + }, + }, + default: '', + placeholder: 'id,name,description', + description: 'Comma separated list of the properties which should used as columns for the new rows.', + }, + { + displayName: 'Return Fields', + name: 'returnFields', + type: 'string', + displayOptions: { + show: { + operation: [ + 'insert' + ], + }, + }, + default: '*', + description: 'Comma separated list of the fields that the operation will return', + }, + + + // ---------------------------------- + // update + // ---------------------------------- + { + displayName: 'Table', + name: 'table', + type: 'string', + displayOptions: { + show: { + operation: [ + 'update' + ], + }, + }, + default: '', + required: true, + description: 'Name of the table in which to update data in', + }, + { + displayName: 'Update Key', + name: 'updateKey', + type: 'string', + displayOptions: { + show: { + operation: [ + 'update' + ], + }, + }, + default: 'id', + required: true, + description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', + }, + { + displayName: 'Columns', + name: 'columns', + type: 'string', + displayOptions: { + show: { + operation: [ + 'update' + ], + }, + }, + default: '', + placeholder: 'name,description', + description: 'Comma separated list of the properties which should used as columns for rows to update.', + }, + + ] + }; + + + async execute(this: IExecuteFunctions): Promise { + + const credentials = this.getCredentials('questdb'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const pgp = pgPromise(); + + const config = { + host: credentials.host as string, + port: credentials.port as number, + database: credentials.database as string, + user: credentials.user as string, + password: credentials.password as string, + ssl: !['disable', undefined].includes(credentials.ssl as string | undefined), + sslmode: credentials.ssl as string || 'disable', + }; + + const db = pgp(config); + + let returnItems = []; + + const items = this.getInputData(); + const operation = this.getNodeParameter('operation', 0) as string; + + if (operation === 'executeQuery') { + // ---------------------------------- + // executeQuery + // ---------------------------------- + + const queries: string[] = []; + for (let i = 0; i < items.length; i++) { + queries.push(this.getNodeParameter('query', i) as string); + } + + const queryResult = await db.any(pgp.helpers.concat(queries)); + + returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); + + } else if (operation === 'insert') { + // ---------------------------------- + // insert + // ---------------------------------- + + const table = this.getNodeParameter('table', 0) as string; + const schema = this.getNodeParameter('schema', 0) as string; + let returnFields = (this.getNodeParameter('returnFields', 0) as string).split(',') as string[]; + const columnString = this.getNodeParameter('columns', 0) as string; + const columns = columnString.split(',').map(column => column.trim()); + + const cs = new pgp.helpers.ColumnSet(columns); + + const te = new pgp.helpers.TableName({ table, schema }); + + // Prepare the data to insert and copy it to be returned + const insertItems = getItemCopy(items, columns); + + // Generate the multi-row insert query and return the id of new row + returnFields = returnFields.map(value => value.trim()).filter(value => !!value); + const query = pgp.helpers.insert(insertItems, cs, te) + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); + + // Executing the query to insert the data + const insertData = await db.manyOrNone(query); + + // Add the id to the data + for (let i = 0; i < insertData.length; i++) { + returnItems.push({ + json: { + ...insertData[i], + ...insertItems[i], + } + }); + } + + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + const table = this.getNodeParameter('table', 0) as string; + const updateKey = this.getNodeParameter('updateKey', 0) as string; + const columnString = this.getNodeParameter('columns', 0) as string; + + const columns = columnString.split(',').map(column => column.trim()); + + // Make sure that the updateKey does also get queried + if (!columns.includes(updateKey)) { + columns.unshift(updateKey); + } + + // Prepare the data to update and copy it to be returned + const updateItems = getItemCopy(items, columns); + + // Generate the multi-row update query + const query = pgp.helpers.update(updateItems, columns, table) + ' WHERE v.' + updateKey + ' = t.' + updateKey; + + // Executing the query to update the data + await db.none(query); + + returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); + + } else { + await pgp.end(); + throw new Error(`The operation "${operation}" is not supported!`); + } + + // Close the connection + await pgp.end(); + + return this.prepareOutputData(returnItems); + } +} diff --git a/packages/nodes-base/nodes/QuestDB/questdb.png b/packages/nodes-base/nodes/QuestDB/questdb.png new file mode 100644 index 0000000000000000000000000000000000000000..90febff64f5d4facdd03c641e2cb6ffd0d2dd373 GIT binary patch literal 11244 zcmZ{K1yCKqvi8B<-A{0Lcemi~&cWT?B{&3vy9R;>m*DR1?(PoxlY8&G@78<&o2uFA z?f$;*o}H=PsjmH^q#%U^j}H$30FY#)#Z~@{(tkTF^q;HxdA$?>0KaD?CZ;4KCI(b; zb^uw~n*T9>NlArKQ(eXmpX@mPln+G;q_~T71(${sz!c$)l$91mmxZM%i_GpuBSfGj z&5JvVj0`RmORNPJEJ1gJKY*cOxe?M+!;iFek#Vz~b~J4m*c|#UFI(i+U-D9&{sJU? z)go1f{(&f5qC^{qc$eQ#MLy%G2*6JW!rx(zW$cl1@kr`W%t_447HcHb&0m7J>^+>VguP{RA`-Y&=h|`TW$O~mKvRM0n z4rd)n8-@~rb$!*MXF&lZaiN#)*LX=2sj&7mipB{?USWp{Ec;@^Y%7FTva;1vdLkOL zksCq+mSK&(N4Pk=0>0;xR!^E-@A1zs7Hz*nqANq&EdPGC{Ae-<4V5f1i)8iTz(^>t zxKatwiBI$-du`9sZ^<2sc|6tSrCEVK`(l))_ue2RPL8MH@Q!~E29&hNCF2Q<>wO8B zV~?&U-M1-@^23`QL!bOX_c=as@L8{<-Jem2*FM7qvi7z_JBb*J_3JKOe6)Gu&AUrj zkRaPB^pMYFvhQqlAWbgaz7))`kHz>|E}hxfTyo#&Yn)Up=@F=4;z>02jZ+&H`**>0T{_pAgUt(CsG7{2pmrs-m(h?HV`s0!GS)I zkc!6RyK4c@CjB&PZQx+)4DJxp-b|WkV=UJA#=-`6;Fni^H}MT+s!JO24pxj&d$6ts zRJa?E3B4FVK@ckN#+VZHbzwHz$D_(}*9QZQ8ianMm=EU~^obc_p^KOs!ea~R zO@tX%idO~@4IntC!Z6n6f*{JQBP^eW44FnI6zGS!0%y;}4I} z4yNf!HbOQzVZmW;Vu46Q(W3tr#nInt6jB4X#D3;{CV6IWOVkLp8g@R2yz9s$4qWVA-G#XN;`zy+p%XFyHvl>S;y0p@tP&+I#v1H4 zB(gD{G21pEKix5#Ss0h1998}cai66Lz7tYyvexHzVP9e2(!4yQysdI(Ly^21oqVu8u!7xQ~^(3g_)MW9bM9#!=X@;Vn##*&5h6P&e@+FYV+3P);HI4j+JM-nJ!4h zF5_?D>&1%3^2V;>=WsOm6$&3LGZt5V2|leS2m@XFR~RnvD^XJ0>WwQz;Gr}obA2JblcwzxN# znzKUGfv!wmkR+H{TtJygJ-(d1oFd_1=7?m`vQe_Qno6*g=91%BvtP32HY>8g1SMOj znYvD<8Od=S5L~j`bA4EvSfg9iZ4^!KwR{h!nPp%6IdB(*w>&mVzxre5>>JNQmVJ(W z=W6>bc%x(8mTt1HUQ;MTHUreRHiotz%o(Zcq%=t-ClOicIe%!+=yLSoMqgmarJAY)pGV(gRJ-RRQqV)MVT0Skl?7u0$^FFgX8ow4i zdXF)lkTxYWZhxq~M|+)n^}Tbu)!$y-iMT6kDb3Wb->I`&uy!_6yh~-8+wc1 zE5gm@tNYXoI~#cxX?SRQC^g2E`zC1Ba1eQEQE<^kSP4QZV|%-_ zjq*UjY;+2CJ$A120Qn#Vi2|`{SUs(FFU?qTRPv`rCLgL-iowkKY__5MEo_(EV(%jC zQo%#7tHx{SXM;EN54xc0Jh>}M25LKpmBG8KyYjp1nC6&PIbV?TflPimv;NYz;>-s2 ziqB>=w5a!>HuK9NV)Tee6j|V~sLWS2Bs7IZGf>^7nIBIM&pr>?XOq+{t@1jx=DQWv zXmf&Z0;5Pn?^ZnCJU(7FIsO|N?VVULSg#tQbgCMSRjhg`zpRbiwob%sB5h_C-x?R( z?XFiIPj?nL4o~;0CoX?8UbSU__k%l$OnN`w`}~p$V0!8_4e1TnC+Ng>hOfq4#!kVE zwKH>QzX@4KDHe)xkX@gvUG}=oVOV15VOZDgXe6f9pqXH}*u-wEH0HDO*?xHB!@g|R z+tI4?pmHj;wQ#Vqw3=ls)Yo;7O}QSmPkl{ZRJYW$EqiLttbZLKz7%NiaeKf%e}607 z8svKgf8;e9 zb3+bicCwZyym-EK4a*%ZADWql6$;0>v_h5s-nV!=>2GYi7A}*>O_4eV*}K`N4*F}} zzkT1Hb?t6fc^O0Xnq678cAniOUza}wAqKDg8Mi*S+Z zbxS>!&#uZI*LP`~Si5R&)vWf=#Q=3h&FOPB(6o zr|F0>ltK~&DRwDjzk&ek--M6P%STBpYg)%Rw@l-PVx5AxlY}HVwS7NiXaYqE7386| z?63+_F;LF~Evfc!|w<&b8U9&)p+1exg# zfV*SD>o57F6C)w@=+H9xv7x6YJ|ckQIKUSzK0f|}nB=!JVEIfRpi@`s`VnfJ*pzFc zDz9~Fd^jFpefQlX(82rd6_(O{3-&|s0|8c%v`kXx6!OopH`H891|%;Jp#3Am0-(X5 z0T6#AFaY?U3;Mrgus_J^n!hK!5t*_V;-G_x_(F zcpmt_5z=`O|1JN8LZHFC{~6#MrL|oE00fM`9So3>g$n?HH(IG_xoXMF@tQi=F&djW zn3ywq+ByDZ0r)+6|A=m(%(lR$imO`zjXf-FXmw9 z=xpxt&;0&{|4-fj#4A}kxH|kN3Ws zEMqcvm?ZA(TGh$eNQ|ud2GJ zs(Lwj{rz%UZCwx$9za?n1RMG8-iMuw#;VWMP!(Gr@mO``Y) z5n-9U7v;^06_;C_4p6(gl}9q$dJb7mf1l@gRfVDMj-8&^rq|WL?>UB7<7d84GnIqhpvog#0GPt^7TA4k!?v2(_K{q4jAR
lRYznIp>WfmY^DbN$nFzs}Y_a^oAZ(sQkQ7a$*Syk!vblM@vzxc&REh znxEDSZSNCDrugg!{FdGOZ7$!P)>mVjV@IJ`Nf(6cSW5V|T%fFff$fZ-(`G_}#uo<< z+l*h;*etUc#BQ3U`{+?ZkwyYZ84JkxrL{=Fh>N`fF`#jU;e>&ZfUki)jtRLMbIayb z**{lJN{^k$3W+mbwpu;vv%7X+@?Q8TG6VABUZJMCMk+gv#ASelZzYI$oA5ZeUwhF) zY!S{=z|*LULzBQoRKvBqVs-&}j#x)<YEtPT@sAWY3RaJ6pGBder z$$qbQOA(`PXj4wn34s%)iGq3CY+!xam#S;pU=+O8&gUL6HBbH`-#Ik)f*GJQY+%c9 zyGXkju_-iW-x`A)#6<{kN$fM6@u&&$5J4y;Y=8+c4Nm(};EI_RUO^6H<_z2+9v8#M zYh(!r(~)ym3H{gSH{sBr`Spq$Pw|@+rph+6HGVX7E}U;L2k`MM$RXhBv|y-k#6Ex+ zXetEMx}VA0WkJS{sM;v^A%!B~!b+1Nq=8P|)Ldd!nHsE?2-C~ypZK=dthgt7T9;lb z$4;P+DA{8^Z(Pcm^93c&g2lRkO}TkmWw#Z zkY709Ac{q%>37%zXS#snQbbr_Kv3##Q3_g@R+Fg|sN<{JCZF^1${-$AWF|iC+wDC?*}2 zy?~9W;Nb0ucm^4PZOAJ-b8}4Ls4$DqpEAz?&y0Q4ZC6P$>yM((MC%Vp?ZmB~ zJ+n5duLv`x;K}9_FgRdY;D_lu%H443gy|xD^blRJ2rOM^*Fn=`#!6Erl>t}qvG}73 zC_C7DI6L08r0MaW(Yj2%#nRTJ!yjPlJQ7ZAVT2``f1%=n(=SQ$@7W~aVj>|zK<42B z#i_!iW=6t>@QCo^7Yoo`SyUkdV?AY{zJiw50)#`D|wyRfmR`y<@C z=7J*(By>xyKOfaiOW05OYOz@WxK$uRSqQT)zhKds*Mz39)kjncB!aa?>7{!_*&HZx zNf^We5%QL>@k7IxD33^0Yhbi;oMQrE6D7ixwZxJyC`%m(TCRB-XP<+%T{{`pASWb{ zN1&>7YMp$XW|#`(#B0VV*z6KjF>5@xO`$u3RQtF}1&2%Ec09epU#uUNkJtMi4G~5|yjLU^;j>QQcUq?7FMvkIZH+ig&S5{&08{3+^kFxUd_Dnl2Zk&Xw%i;YQ zQs`*RlmoSUr(s2wL0EOfEkH%EJKM4}YOQc~QZ@>_e7Vf|g0;>~DC$cmmeZf}Vw#0Q z9uz90Pqw?GOcA5-v$tj*k33V%u|aQj{Iuzkq*_}mb^%|<7~yr4T#S%T=$<3y33d1Q z;Z0V+MWfk&INWjBf7lvx1r{ie8+g12#NZ*wvo-lio!Bi?*+!yC(nEa|vH}K$VMMcB zH@||J@=(hhl;oQ-&+Qe3)gm+CQ@(6tGWH6M8qp->GZ*h^f`Bv(3*M^A;ezf3b4zk8Q&|G@vypM>i} zCIq5P&15oj$7UUa`rdvAhf9WOip!TvrQEZ-UQ&JoLT;==GHim^Fj|YSAS8|2+seA$ zGa#ozQI+>7*Pz%QH=k3_Pth_VAOlo;)FmCHN;F$t$rghOsF|V^B&fj3vxy3skbj5B z`udA-aNy2O^>v-Og@u>Lswe~cF(X?~(n&BMAKW*yH4l6_j9ptM;>LR}PO)ey@_m3Y zaKtxZruW>EgM-HeR2%gdFK3ox62J>peQ1bKg9<}6lux9tPX?hFPLsFd2soT<-#rkf z?q1c>*Ws5aLei-Zq&Vo>0z3{JTt>4UsK)FUkCz$saU*|ZvFv)pk`EJ+6fcy8^0hro zEHJ8x#(PmKDBo>6N9D9E@>)>|%N;|k<>{|@2rHzg6#V&~7M(skKT1e)Z=SJv6P7ZW zdICfU>_&$!s-XD5}er#z2B=qGb)~4p;=ekmzQ`6-F3NIIK1(yk&Ze0^lCW6}( z%B3mSz2ZzwTje;ju>96&t71!1>t1#Fbh4)t@i~4m0SP&Kem96dfPl+7hzPsQKVuNPm=Qlm1=0-n)Y#)E z$rU~0G9%&C!|uL6ayguxpR^?*#50njtxG|1>lHsx8nV&oI7C^x*u1)#78ES$5pp8Q z-W6REdW}j|Ke2(iIu$O2lv8`;6RHv)Y%A>hf!YtRx$$bMW9{ zN{ku6zY>{?R0MI?(4lH8U(3^jn7!_63kxGwcv}PTU#CxX38pfqsdY(Vc*Hxp^ZTBT zxcqcy9Na4vU87^X!~z&HmgIo&hL3w*&T_{zq=;gH1WIX|<~R`tC$Ic0rvYY*p>qRx z&aps0nzWGb;HcB2VFmKiylIe^P^A9cM9f93a^^o9+$uNjWFFBOnHNy`p0>6+j<}06 zw)(^ljnLb0^$9B9`djUI9X`>2ACQcw(lHR0;8`4%&8a`?beAv|{9eNof2!cd;6rF- z{&kbPH+irQ_SzHnex}+b5TOs==u@ugR5ERjvZsNx&tQrpv~hGx_;v1u@t+v zY~xsE%dfkY1>GkOUKDJ;QWxiWtbyionJaaXmij_CB>1ix#LU4PH{$g>r!*4B;5ocs zWla59#^||@a5{J{_=%<1zQ~;2V11weynZb&<wz~S%frGICUSDxsvpN*HI z87I{mF;}qqPYCA&zkRpjOP<>g;9rd{8>Tf;=T^M7dyswZU!QV zssWZ1b7*q!P+^+NXG#noIL0rP4#w6e(A8z>=$pVjerB8po)?cqK$BAYJ1Tg+j0=jp z9bj$Es%7hpE#OnGtnbLVCY9#nc{UCE>>cUsvX#he%_02$)xwYSNNQDNCa=yYQXP)T z-&+5NTjC%wc7s3kmy1qz^R=H%b<1mG01|A{g9iA58EV4T^Y1`k1uk6EkwD~EYnY`L zUoyf3Qp_a45Jv4c*4S*#fi1&vAglr&L9h`5Xo9{dXJfh`{3i&rYFm$T$I2baVar6L zA%zHZc8%%6o#XSJrD_KO(~)SOd#Zr{%VR|p*@6fVn;9&O_HoP8pP3e7f?ZfRxgP=S zB^pd*PjzDVPMETV5!-KFjbKxM5uMK$5GC1Qg1r9prd3FKNvn(x-qiA|g~ESp1w=*# zh4rEto&p{(;wZ(;N-WLSz3oDzEGy*LzC!A$P4~ zX0WuD@P;UxfUIh?c%FkY`OKyO-SL@5u3!qkF=7(@qvAhy&mMwPh=#b3=(Rr4$DH&U zU(e2*sW0Z4RIiuKr*^f& zTS&ugisJl8!Io8MilJ@j)C0;X%sv2BoV`d0oL*!YvT)e0sBdMIU&Wl|@)GtqnL`wo*iS6+ULbbO=2ni)R7mZaPpGX? z)E^CIbZsILTeiGgKV*vj%|w;vtkTgB8p-h5KVmuC;catuNpeOfL6_2Q(W(B-7}~rr z)&m3td8V?Jmvbv6eZ3W9d`YMsdVCG7@;4#GieN1EMKl~E4(t`atB1jQ)C%P%&!-C& z)I3(y|1ynSPYLQepG%i6PQ}_+>)L=bii-$q_Sdj}FAOqCNWzoSnTnk6mxx8U5PF~o zDZ5H^aN>nuMnm{RjcFGlU_otX?)9M+FLiHqkrZN;4dUl@)6d+&aO-)o%Z3z6klTQx z7;-U~v>OKY4m>oV@HolzZ=*?$C|I$$C~>Doqs64Kn>&U=h!kxHmow58)U^*WO}1D( zlpVc+Uh|@KT*Dsz%);jcoTu?lFELt#&XhpAqc`PXpAHFj1~D!U4=^;LsTT~*w6!S! zg_0*ZdF=mKJjqU#bgU_v+JmeXo(a{@XMKGFLt}1gNH`D#XF4kNE#mef+ij4ZE?lA` z1YE^6OlJDo{Yff*lBN76z&xg#Q-E0Is=7bwKJH>(KQ2RN3yx&y`ZLBSS*IFUUFd5QB=q{hc;6i9HpwY??ym!y+3b8-O^^62vJ|F)>DCj!G=BxcKq%Io?8Kufs;CmK1ik(-sNP zD409?Mxvfw-mPmA{V_N9HT^FW2*rF>A}xiF;05JC$=DE#lM=|Ty&ZL$Fz>vBwJ zec9D;82CMdOXEH?z^6^2L$dN<7aTOBR)9)5%BD{??*fscA`{4bTIgo3IaDt8T?e& z1P^y`)(4)N?85i4c5+%w$sf^{w%5*HKVjm)xt~afEMg~#v8!!~X)CcBCV}H+h>|R} zOalWmWW&^1KTa`Yk+moF+3KmId^FeUd?$XID!h!sjGoQgF{M3kqtkmBc@#|oJ2c|i z*@n6jBg95ovyx2-Yy_XkH>MYSRF^VtqWOOLQ7OXK?_UrJBEo0G4$R#)3AJj5YcL6B|71p2*o_wTQ&Tp!$H&FJnQ_ zGD1iWfECDfvm5+oW{v8%W(yaa!L`AH>c%dOo+b>-X5_Po>$L4`lot+&Lm_4njnIRr zx2|I~yyvyP-Sf;ZvhBVe>r*RJLynxAp0 zC*rr8dM+%rW8BWClTBk&p1ZA;4fKGcwc+*!`QQUS@R_>I*519`TpJL><8`5UNmxwK zj0kDaN&RtPeTkKZ{N<-X4k8@UJ{*G#Q{ z8t`;RpbV?l^Cncs>M$dOAsRN@$kGG zW%IVUKRoSP$W>>Zx<9ynanrAktWiEIJmQ$DS9s2D4XFXA$#x{C_Hi$=-t@ZZ_0cg| zk_iIWK3wwP@Flm%LPIb7e5mZj2#TY3KSC`DfJgeg+%AT&Wz;uKj|YeY!PfU-)CY~B zO|e1WFo;6{uX#{@1(#x%m6Y(~Ub+deViMZ?U<2ndf4cgSGcYeCFKyb%2ls^q|ZpH9m2 zCIfx5H)!zf%^V}8CQrp?=z$Tq)ffq!rKXjYb?x_38|j)38xL6XbdZ)LV@KUkKD=#N zU?G+k0pJ=jqpAWHWmcAc#G`OJx@yC))c+}*p%?abJtew@8e~9AcV9`{E((1eSp21c zV%P}Ab*+i*gAAZTNb=sQk|NG@zJaw-vpR=!Q?Ku9E+{-hBC^?A@I+WRzpd>WQvG!I zvno)NQIia}QM{dsrQslLhT=1m{zk;PWv~vjvw)v+HOKW{GknBkR|zH5K-X3e24WyK z6;t*+mk!NtM&JhvE+z;jFp8?i5MXqrIg9qW9Pe3#qmsFZat}t$txtu5rz8sU8Vb1& zhlD;@PLB?RMuO(%=QU5n+uL&pu5h52q4z6ABll{d6ufWTEFsU7g5#_AQ4UoOU0Swp zb%D_s4wZ)7=zCAuvwdLHT!g;EmmF8VrDOz-9=_K<|JCrw=9$D{HOhLoCOx!G+Im*y zBh%~jk*$!{4Qe|?tEqRCQCA0{n^tIfdk! z!)SL$2MBTkS#3H4jfJxNu^JgSC@>3gEfq#VGKKpAtJ!bvI=_q&DpxmFJHI33LQ|Y8 zxYC``h)Q^NjFp8Vp$9=B(XI5@*AOnzfw6y~+9;#$_@o%kC}?$}S(V9pGsI#+^lZm#e?~KrNnnV2Y40QRlWLJI zPYa3$RfO#*Ii7yv^B|vpaNVz}h<7bAL{WO?#x~Kp z+Ser7kZJrHV8chnOy)Vc*E$*tm^9L82ThlLW_bt_`L3(UfuFbcR^;G?) z%-W#!>+pt)6dV_iqAc0SQ^cCx&&g`P!QnNO7#k0+Q-iK2uw@_KGF~`0<6E?wHvP5S za#^l6@~9tZU*l65y^iBX4`TTIyPwKw3V9rS2nCimc6^)t8kUxBiDSkXw9I9qD|LB~ zLgEbskN2qs76aeIYQL2cKODyp4B}&0Z=$-*MyEf$4da!|Ki8z+AZe!TuGWiES*2Z3 zShRXuo)6gF8Td@<&L#R{h6zbKtlf1QrgrdU1^Dn#v|S4ofZn}QlF4|$@ z=c7NmZ@_rQLFPouZZtJIb09v_QY^gk*TW6p*fic-3pJH*NnZr%CS^t=vx^*Q;~WNF z%1R4omy>EVl|62F(jemJ)p!H+0}NdcCE@LHBhu^%OPhU>iP1boTj2$5H=36n=6$%1 zHo>~gT!h#{>Y%RBbRhOEM+thPkPrGF#|2(OGaRxs43t=YaaX&FG80lG9h!WGKUtez zRhaC2xZ7X!KVR6aAt|q|m3hSG5Wf{SIJQyK;tAsw3Uc6@j@HJZw|f+jV>JlPc|y1! z`88cfcaH;jk>?jE8;oRD3+ZS@yhX_2G?FMq4yOwl*y+TW`mjKT6<(gn%e<1&umu*j zf7Vnv=Wt<6Q^{cinHc05i2_m6YMgvd8*MH-%)tZ{oYo+vaJ^Frye7t_Op!SV7=tyI zkTQoRtcsWRJC;3;$R2-!x<#4ImU!0lq#hi5aLv*iBUX|3D2UJV3W5I6A)IE@o5e=1 zT$8)Zk2mz0B34`b#l{zSHx#eAJg)nXzt%)3#HYKyxBG{UCm}tJC}qNhT;MwP7_plw yfw+!>Oosz;-rmVkWDnooyRIiQK1@lkem@Y*#u5z_KK%V3Kt@7Ayh_w4`2PXdVy@T# literal 0 HcmV?d00001 From de707f039b3b1273d6ce3e16927cbb2d0dc26a52 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Tue, 7 Jul 2020 18:31:38 +0200 Subject: [PATCH 02/25] :tada: executeQuery function extracted --- .../nodes/Postgres/Postgres.node.functions.ts | 48 ++++++ .../nodes/Postgres/Postgres.node.ts | 147 +++++++++--------- 2 files changed, 120 insertions(+), 75 deletions(-) create mode 100644 packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts new file mode 100644 index 0000000000..b9aae05372 --- /dev/null +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -0,0 +1,48 @@ +import { IDataObject, INodeExecutionData } from 'n8n-workflow'; +import pgPromise = require('pg-promise'); +import pg = require('pg-promise/typescript/pg-subset'); + +/** + * Executes the given SQL query on the database. + * + * @param {Function} getNodeParam The getter of the Node + * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance + * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection + * @param {input[]} input The Node's input data + * @returns Promise + */ +export function executeQuery( + getNodeParam: Function, + pgp: pgPromise.IMain<{}, pg.IClient>, + db: pgPromise.IDatabase<{}, pg.IClient>, + input: INodeExecutionData[] +): Promise { + const queries: string[] = []; + for (let i = 0; i < input.length; i++) { + queries.push(getNodeParam('query', i) as string); + } + + return db.any(pgp.helpers.concat(queries)); +} + +// /** +// * Returns of copy of the items which only contains the json data and +// * of that only the define properties +// * +// * @param {items[]} items The items execute the query with +// * @param {string[]} properties The properties it should include +// * @returns +// */ +// export function insert( +// getNodeParam: Function, +// pgp: pgPromise.IMain<{}, pg.IClient>, +// db: pgPromise.IDatabase<{}, pg.IClient>, +// items: INodeExecutionData[] +// ): Promise { +// const queries: string[] = []; +// for (let i = 0; i < items.length; i++) { +// queries.push(getNodeParam('query', i) as string); +// } + +// return db.any(pgp.helpers.concat(queries)); +// } diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 2fa010576b..2e40dc8290 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -3,11 +3,12 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription, + INodeTypeDescription } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; +import { executeQuery } from './Postgres.node.functions'; /** * Returns of copy of the items which only contains the json data and @@ -17,10 +18,13 @@ import * as pgPromise from 'pg-promise'; * @param {string[]} properties The properties it should include * @returns */ -function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { +function getItemCopy( + items: INodeExecutionData[], + properties: string[] +): IDataObject[] { // Prepare the data to insert and copy it to be returned let newItem: IDataObject; - return items.map((item) => { + return items.map(item => { newItem = {}; for (const property of properties) { if (item.json[property] === undefined) { @@ -33,7 +37,6 @@ function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataOb }); } - export class Postgres implements INodeType { description: INodeTypeDescription = { displayName: 'Postgres', @@ -44,14 +47,14 @@ export class Postgres implements INodeType { description: 'Gets, add and update data in Postgres.', defaults: { name: 'Postgres', - color: '#336791', + color: '#336791' }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'postgres', - required: true, + required: true } ], properties: [ @@ -63,21 +66,21 @@ export class Postgres implements INodeType { { name: 'Execute Query', value: 'executeQuery', - description: 'Executes a SQL query.', + description: 'Executes a SQL query.' }, { name: 'Insert', value: 'insert', - description: 'Insert rows in database.', + description: 'Insert rows in database.' }, { name: 'Update', value: 'update', - description: 'Updates rows in database.', - }, + description: 'Updates rows in database.' + } ], default: 'insert', - description: 'The operation to perform.', + description: 'The operation to perform.' }, // ---------------------------------- @@ -88,22 +91,19 @@ export class Postgres implements INodeType { name: 'query', type: 'string', typeOptions: { - rows: 5, + rows: 5 }, displayOptions: { show: { - operation: [ - 'executeQuery' - ], - }, + operation: ['executeQuery'] + } }, default: '', placeholder: 'SELECT id, name FROM product WHERE id < 40', required: true, - description: 'The SQL query to execute.', + description: 'The SQL query to execute.' }, - // ---------------------------------- // insert // ---------------------------------- @@ -113,14 +113,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], - }, + operation: ['insert'] + } }, default: 'public', required: true, - description: 'Name of the schema the table belongs to', + description: 'Name of the schema the table belongs to' }, { displayName: 'Table', @@ -128,14 +126,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], - }, + operation: ['insert'] + } }, default: '', required: true, - description: 'Name of the table in which to insert data to.', + description: 'Name of the table in which to insert data to.' }, { displayName: 'Columns', @@ -143,14 +139,13 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], - }, + operation: ['insert'] + } }, default: '', placeholder: 'id,name,description', - description: 'Comma separated list of the properties which should used as columns for the new rows.', + description: + 'Comma separated list of the properties which should used as columns for the new rows.' }, { displayName: 'Return Fields', @@ -158,16 +153,14 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], - }, + operation: ['insert'] + } }, default: '*', - description: 'Comma separated list of the fields that the operation will return', + description: + 'Comma separated list of the fields that the operation will return' }, - // ---------------------------------- // update // ---------------------------------- @@ -177,14 +170,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'update' - ], - }, + operation: ['update'] + } }, default: '', required: true, - description: 'Name of the table in which to update data in', + description: 'Name of the table in which to update data in' }, { displayName: 'Update Key', @@ -192,14 +183,13 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'update' - ], - }, + operation: ['update'] + } }, default: 'id', required: true, - description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', + description: + 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".' }, { displayName: 'Columns', @@ -207,22 +197,18 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'update' - ], - }, + operation: ['update'] + } }, default: '', placeholder: 'name,description', - description: 'Comma separated list of the properties which should used as columns for rows to update.', - }, - + description: + 'Comma separated list of the properties which should used as columns for rows to update.' + } ] }; - async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials('postgres'); if (credentials === undefined) { @@ -237,8 +223,10 @@ export class Postgres implements INodeType { database: credentials.database as string, user: credentials.user as string, password: credentials.password as string, - ssl: !['disable', undefined].includes(credentials.ssl as string | undefined), - sslmode: credentials.ssl as string || 'disable', + ssl: !['disable', undefined].includes( + credentials.ssl as string | undefined + ), + sslmode: (credentials.ssl as string) || 'disable' }; const db = pgp(config); @@ -253,15 +241,14 @@ export class Postgres implements INodeType { // executeQuery // ---------------------------------- - const queries: string[] = []; - for (let i = 0; i < items.length; i++) { - queries.push(this.getNodeParameter('query', i) as string); - } - - const queryResult = await db.any(pgp.helpers.concat(queries)); + const queryResult = await executeQuery( + this.getNodeParameter, + pgp, + db, + items + ); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); - } else if (operation === 'insert') { // ---------------------------------- // insert @@ -269,7 +256,10 @@ export class Postgres implements INodeType { const table = this.getNodeParameter('table', 0) as string; const schema = this.getNodeParameter('schema', 0) as string; - let returnFields = (this.getNodeParameter('returnFields', 0) as string).split(',') as string[]; + let returnFields = (this.getNodeParameter( + 'returnFields', + 0 + ) as string).split(',') as string[]; const columnString = this.getNodeParameter('columns', 0) as string; const columns = columnString.split(',').map(column => column.trim()); @@ -281,8 +271,12 @@ export class Postgres implements INodeType { const insertItems = getItemCopy(items, columns); // Generate the multi-row insert query and return the id of new row - returnFields = returnFields.map(value => value.trim()).filter(value => !!value); - const query = pgp.helpers.insert(insertItems, cs, te) + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); + returnFields = returnFields + .map(value => value.trim()) + .filter(value => !!value); + const query = + pgp.helpers.insert(insertItems, cs, te) + + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); // Executing the query to insert the data const insertData = await db.manyOrNone(query); @@ -292,11 +286,10 @@ export class Postgres implements INodeType { returnItems.push({ json: { ...insertData[i], - ...insertItems[i], + ...insertItems[i] } }); } - } else if (operation === 'update') { // ---------------------------------- // update @@ -317,13 +310,17 @@ export class Postgres implements INodeType { const updateItems = getItemCopy(items, columns); // Generate the multi-row update query - const query = pgp.helpers.update(updateItems, columns, table) + ' WHERE v.' + updateKey + ' = t.' + updateKey; + const query = + pgp.helpers.update(updateItems, columns, table) + + ' WHERE v.' + + updateKey + + ' = t.' + + updateKey; // Executing the query to update the data await db.none(query); - returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); - + returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); } else { await pgp.end(); throw new Error(`The operation "${operation}" is not supported!`); From 80aa0bd5ddfd0d15b8f5d66acfb1da9a7b9b4d31 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 09:32:21 +0200 Subject: [PATCH 03/25] :construction: add prettierrc to gitignore --- .gitignore | 1 + packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b3eac39207..0441d445b4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ _START_PACKAGE .env .vscode .idea +.prettierrc.js diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts index b9aae05372..a6d986643d 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -15,7 +15,7 @@ export function executeQuery( getNodeParam: Function, pgp: pgPromise.IMain<{}, pg.IClient>, db: pgPromise.IDatabase<{}, pg.IClient>, - input: INodeExecutionData[] + input: INodeExecutionData[], ): Promise { const queries: string[] = []; for (let i = 0; i < input.length; i++) { From 0108538fcc94bfdc9edf14babe0789926a274843 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 12:01:16 +0200 Subject: [PATCH 04/25] :construction: insert function extracted --- .../nodes/Postgres/Postgres.node.functions.ts | 94 +++++++--- .../nodes/Postgres/Postgres.node.ts | 175 +++++++----------- 2 files changed, 136 insertions(+), 133 deletions(-) diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts index a6d986643d..83a5701bad 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -2,6 +2,33 @@ import { IDataObject, INodeExecutionData } from 'n8n-workflow'; import pgPromise = require('pg-promise'); import pg = require('pg-promise/typescript/pg-subset'); +/** + * Returns of copy of the items which only contains the json data and + * of that only the define properties + * + * @param {INodeExecutionData[]} items The items to copy + * @param {string[]} properties The properties it should include + * @returns + */ +function getItemCopy( + items: INodeExecutionData[], + properties: string[], +): IDataObject[] { + // Prepare the data to insert and copy it to be returned + let newItem: IDataObject; + return items.map(item => { + newItem = {}; + for (const property of properties) { + if (item.json[property] === undefined) { + newItem[property] = null; + } else { + newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + } + } + return newItem; + }); +} + /** * Executes the given SQL query on the database. * @@ -9,14 +36,14 @@ import pg = require('pg-promise/typescript/pg-subset'); * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection * @param {input[]} input The Node's input data - * @returns Promise + * @returns Promise> */ export function executeQuery( getNodeParam: Function, pgp: pgPromise.IMain<{}, pg.IClient>, db: pgPromise.IDatabase<{}, pg.IClient>, input: INodeExecutionData[], -): Promise { +): Promise> { const queries: string[] = []; for (let i = 0; i < input.length; i++) { queries.push(getNodeParam('query', i) as string); @@ -25,24 +52,47 @@ export function executeQuery( return db.any(pgp.helpers.concat(queries)); } -// /** -// * Returns of copy of the items which only contains the json data and -// * of that only the define properties -// * -// * @param {items[]} items The items execute the query with -// * @param {string[]} properties The properties it should include -// * @returns -// */ -// export function insert( -// getNodeParam: Function, -// pgp: pgPromise.IMain<{}, pg.IClient>, -// db: pgPromise.IDatabase<{}, pg.IClient>, -// items: INodeExecutionData[] -// ): Promise { -// const queries: string[] = []; -// for (let i = 0; i < items.length; i++) { -// queries.push(getNodeParam('query', i) as string); -// } +/** + * Returns of copy of the items which only contains the json data and + * of that only the define properties + * + * @param {Function} getNodeParam The getter of the Node + * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance + * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection + * @param {input[]} input The Node's input data + * @returns Promise + */ +export async function executeInsert( + getNodeParam: Function, + pgp: pgPromise.IMain<{}, pg.IClient>, + db: pgPromise.IDatabase<{}, pg.IClient>, + items: INodeExecutionData[], +): Promise> { + const table = getNodeParam('table', 0) as string; + const schema = getNodeParam('schema', 0) as string; + let returnFields = (getNodeParam('returnFields', 0) as string).split( + ',', + ) as string[]; + const columnString = getNodeParam('columns', 0) as string; + const columns = columnString.split(',').map(column => column.trim()); -// return db.any(pgp.helpers.concat(queries)); -// } + const cs = new pgp.helpers.ColumnSet(columns); + + const te = new pgp.helpers.TableName({ table, schema }); + + // Prepare the data to insert and copy it to be returned + const insertItems = getItemCopy(items, columns); + + // Generate the multi-row insert query and return the id of new row + returnFields = returnFields + .map(value => value.trim()) + .filter(value => !!value); + const query = + pgp.helpers.insert(insertItems, cs, te) + + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); + + // Executing the query to insert the data + const insertData = await db.manyOrNone(query); + + return [insertData, insertItems]; +} diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 2e40dc8290..a7fe2b72d8 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -3,39 +3,12 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription + INodeTypeDescription, } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; -import { executeQuery } from './Postgres.node.functions'; - -/** - * Returns of copy of the items which only contains the json data and - * of that only the define properties - * - * @param {INodeExecutionData[]} items The items to copy - * @param {string[]} properties The properties it should include - * @returns - */ -function getItemCopy( - items: INodeExecutionData[], - properties: string[] -): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map(item => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); - } - } - return newItem; - }); -} +import { executeInsert, executeQuery } from './Postgres.node.functions'; export class Postgres implements INodeType { description: INodeTypeDescription = { @@ -47,15 +20,15 @@ export class Postgres implements INodeType { description: 'Gets, add and update data in Postgres.', defaults: { name: 'Postgres', - color: '#336791' + color: '#336791', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'postgres', - required: true - } + required: true, + }, ], properties: [ { @@ -66,21 +39,21 @@ export class Postgres implements INodeType { { name: 'Execute Query', value: 'executeQuery', - description: 'Executes a SQL query.' + description: 'Executes a SQL query.', }, { name: 'Insert', value: 'insert', - description: 'Insert rows in database.' + description: 'Insert rows in database.', }, { name: 'Update', value: 'update', - description: 'Updates rows in database.' - } + description: 'Updates rows in database.', + }, ], default: 'insert', - description: 'The operation to perform.' + description: 'The operation to perform.', }, // ---------------------------------- @@ -91,17 +64,17 @@ export class Postgres implements INodeType { name: 'query', type: 'string', typeOptions: { - rows: 5 + rows: 5, }, displayOptions: { show: { - operation: ['executeQuery'] - } + operation: ['executeQuery'], + }, }, default: '', placeholder: 'SELECT id, name FROM product WHERE id < 40', required: true, - description: 'The SQL query to execute.' + description: 'The SQL query to execute.', }, // ---------------------------------- @@ -113,12 +86,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['insert'] - } + operation: ['insert'], + }, }, default: 'public', required: true, - description: 'Name of the schema the table belongs to' + description: 'Name of the schema the table belongs to', }, { displayName: 'Table', @@ -126,12 +99,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['insert'] - } + operation: ['insert'], + }, }, default: '', required: true, - description: 'Name of the table in which to insert data to.' + description: 'Name of the table in which to insert data to.', }, { displayName: 'Columns', @@ -139,13 +112,13 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['insert'] - } + operation: ['insert'], + }, }, default: '', placeholder: 'id,name,description', description: - 'Comma separated list of the properties which should used as columns for the new rows.' + 'Comma separated list of the properties which should used as columns for the new rows.', }, { displayName: 'Return Fields', @@ -153,12 +126,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['insert'] - } + operation: ['insert'], + }, }, default: '*', description: - 'Comma separated list of the fields that the operation will return' + 'Comma separated list of the fields that the operation will return', }, // ---------------------------------- @@ -170,12 +143,12 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['update'] - } + operation: ['update'], + }, }, default: '', required: true, - description: 'Name of the table in which to update data in' + description: 'Name of the table in which to update data in', }, { displayName: 'Update Key', @@ -183,13 +156,13 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['update'] - } + operation: ['update'], + }, }, default: 'id', required: true, description: - 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".' + 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', }, { displayName: 'Columns', @@ -197,15 +170,15 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: ['update'] - } + operation: ['update'], + }, }, default: '', placeholder: 'name,description', description: - 'Comma separated list of the properties which should used as columns for rows to update.' - } - ] + 'Comma separated list of the properties which should used as columns for rows to update.', + }, + ], }; async execute(this: IExecuteFunctions): Promise { @@ -224,9 +197,9 @@ export class Postgres implements INodeType { user: credentials.user as string, password: credentials.password as string, ssl: !['disable', undefined].includes( - credentials.ssl as string | undefined + credentials.ssl as string | undefined, ), - sslmode: (credentials.ssl as string) || 'disable' + sslmode: (credentials.ssl as string) || 'disable', }; const db = pgp(config); @@ -245,7 +218,7 @@ export class Postgres implements INodeType { this.getNodeParameter, pgp, db, - items + items, ); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); @@ -254,40 +227,20 @@ export class Postgres implements INodeType { // insert // ---------------------------------- - const table = this.getNodeParameter('table', 0) as string; - const schema = this.getNodeParameter('schema', 0) as string; - let returnFields = (this.getNodeParameter( - 'returnFields', - 0 - ) as string).split(',') as string[]; - const columnString = this.getNodeParameter('columns', 0) as string; - const columns = columnString.split(',').map(column => column.trim()); - - const cs = new pgp.helpers.ColumnSet(columns); - - const te = new pgp.helpers.TableName({ table, schema }); - - // Prepare the data to insert and copy it to be returned - const insertItems = getItemCopy(items, columns); - - // Generate the multi-row insert query and return the id of new row - returnFields = returnFields - .map(value => value.trim()) - .filter(value => !!value); - const query = - pgp.helpers.insert(insertItems, cs, te) + - (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); - - // Executing the query to insert the data - const insertData = await db.manyOrNone(query); + const [insertData, insertItems] = await executeInsert( + this.getNodeParameter, + pgp, + db, + items, + ); // Add the id to the data for (let i = 0; i < insertData.length; i++) { returnItems.push({ json: { ...insertData[i], - ...insertItems[i] - } + ...insertItems[i], + }, }); } } else if (operation === 'update') { @@ -301,26 +254,26 @@ export class Postgres implements INodeType { const columns = columnString.split(',').map(column => column.trim()); - // Make sure that the updateKey does also get queried - if (!columns.includes(updateKey)) { - columns.unshift(updateKey); - } + // // Make sure that the updateKey does also get queried + // if (!columns.includes(updateKey)) { + // columns.unshift(updateKey); + // } - // Prepare the data to update and copy it to be returned - const updateItems = getItemCopy(items, columns); + // // Prepare the data to update and copy it to be returned + // const updateItems = getItemCopy(items, columns); - // Generate the multi-row update query - const query = - pgp.helpers.update(updateItems, columns, table) + - ' WHERE v.' + - updateKey + - ' = t.' + - updateKey; + // // Generate the multi-row update query + // const query = + // pgp.helpers.update(updateItems, columns, table) + + // ' WHERE v.' + + // updateKey + + // ' = t.' + + // updateKey; - // Executing the query to update the data - await db.none(query); + // // Executing the query to update the data + // await db.none(query); - returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); + // returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); } else { await pgp.end(); throw new Error(`The operation "${operation}" is not supported!`); From 2fea79f5f11f8092aed1fe21d3bbc21567612d85 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 14:36:40 +0200 Subject: [PATCH 05/25] :zap: extract update function --- .../nodes/Postgres/Postgres.node.functions.ts | 52 +++++++++++++++++-- .../nodes/Postgres/Postgres.node.ts | 38 ++------------ 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts index 83a5701bad..8c46ef7e3a 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -38,7 +38,7 @@ function getItemCopy( * @param {input[]} input The Node's input data * @returns Promise> */ -export function executeQuery( +export function pgQuery( getNodeParam: Function, pgp: pgPromise.IMain<{}, pg.IClient>, db: pgPromise.IDatabase<{}, pg.IClient>, @@ -53,16 +53,15 @@ export function executeQuery( } /** - * Returns of copy of the items which only contains the json data and - * of that only the define properties + * Inserts the given items into the database. * * @param {Function} getNodeParam The getter of the Node * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection * @param {input[]} input The Node's input data - * @returns Promise + * @returns Promise> */ -export async function executeInsert( +export async function pgInsert( getNodeParam: Function, pgp: pgPromise.IMain<{}, pg.IClient>, db: pgPromise.IDatabase<{}, pg.IClient>, @@ -96,3 +95,46 @@ export async function executeInsert( return [insertData, insertItems]; } + +/** + * Updates the given items in the database. + * + * @param {Function} getNodeParam The getter of the Node + * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance + * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection + * @param {input[]} input The Node's input data + * @returns Promise> + */ +export async function pgUpdate( + getNodeParam: Function, + pgp: pgPromise.IMain<{}, pg.IClient>, + db: pgPromise.IDatabase<{}, pg.IClient>, + items: INodeExecutionData[], +): Promise> { + const table = getNodeParam('table', 0) as string; + const updateKey = getNodeParam('updateKey', 0) as string; + const columnString = getNodeParam('columns', 0) as string; + + const columns = columnString.split(',').map(column => column.trim()); + + // Make sure that the updateKey does also get queried + if (!columns.includes(updateKey)) { + columns.unshift(updateKey); + } + + // Prepare the data to update and copy it to be returned + const updateItems = getItemCopy(items, columns); + + // Generate the multi-row update query + const query = + pgp.helpers.update(updateItems, columns, table) + + ' WHERE v.' + + updateKey + + ' = t.' + + updateKey; + + // Executing the query to update the data + await db.none(query); + + return updateItems; +} diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index a7fe2b72d8..702799c5c2 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -8,7 +8,7 @@ import { import * as pgPromise from 'pg-promise'; -import { executeInsert, executeQuery } from './Postgres.node.functions'; +import { pgInsert, pgQuery, pgUpdate } from './Postgres.node.functions'; export class Postgres implements INodeType { description: INodeTypeDescription = { @@ -214,12 +214,7 @@ export class Postgres implements INodeType { // executeQuery // ---------------------------------- - const queryResult = await executeQuery( - this.getNodeParameter, - pgp, - db, - items, - ); + const queryResult = await pgQuery(this.getNodeParameter, pgp, db, items); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); } else if (operation === 'insert') { @@ -227,7 +222,7 @@ export class Postgres implements INodeType { // insert // ---------------------------------- - const [insertData, insertItems] = await executeInsert( + const [insertData, insertItems] = await pgInsert( this.getNodeParameter, pgp, db, @@ -248,32 +243,9 @@ export class Postgres implements INodeType { // update // ---------------------------------- - const table = this.getNodeParameter('table', 0) as string; - const updateKey = this.getNodeParameter('updateKey', 0) as string; - const columnString = this.getNodeParameter('columns', 0) as string; + const updateItems = await pgUpdate(this.getNodeParameter, pgp, db, items); - const columns = columnString.split(',').map(column => column.trim()); - - // // Make sure that the updateKey does also get queried - // if (!columns.includes(updateKey)) { - // columns.unshift(updateKey); - // } - - // // Prepare the data to update and copy it to be returned - // const updateItems = getItemCopy(items, columns); - - // // Generate the multi-row update query - // const query = - // pgp.helpers.update(updateItems, columns, table) + - // ' WHERE v.' + - // updateKey + - // ' = t.' + - // updateKey; - - // // Executing the query to update the data - // await db.none(query); - - // returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); + returnItems = this.helpers.returnJsonArray(updateItems); } else { await pgp.end(); throw new Error(`The operation "${operation}" is not supported!`); From 10a26ee75d3763a61926b7971ab5ff9713b2c00c Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 14:39:44 +0200 Subject: [PATCH 06/25] :bulb: fix function docs --- packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts index 8c46ef7e3a..6855217bd4 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -58,7 +58,7 @@ export function pgQuery( * @param {Function} getNodeParam The getter of the Node * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection - * @param {input[]} input The Node's input data + * @param {INodeExecutionData[]} items The items to be inserted * @returns Promise> */ export async function pgInsert( @@ -102,7 +102,7 @@ export async function pgInsert( * @param {Function} getNodeParam The getter of the Node * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection - * @param {input[]} input The Node's input data + * @param {INodeExecutionData[]} items The items to be updated * @returns Promise> */ export async function pgUpdate( From cdc42f5558b684c143b990aed3b35e2ec01c9e4d Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 15:12:54 +0200 Subject: [PATCH 07/25] :bulb: add in code documentation --- .../nodes-base/nodes/Postgres/Postgres.node.functions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts index 6855217bd4..b07835d1f6 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -32,7 +32,7 @@ function getItemCopy( /** * Executes the given SQL query on the database. * - * @param {Function} getNodeParam The getter of the Node + * @param {Function} getNodeParam The getter for the Node's parameters * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection * @param {input[]} input The Node's input data @@ -55,7 +55,7 @@ export function pgQuery( /** * Inserts the given items into the database. * - * @param {Function} getNodeParam The getter of the Node + * @param {Function} getNodeParam The getter for the Node's parameters * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection * @param {INodeExecutionData[]} items The items to be inserted @@ -99,7 +99,7 @@ export async function pgInsert( /** * Updates the given items in the database. * - * @param {Function} getNodeParam The getter of the Node + * @param {Function} getNodeParam The getter for the Node's parameters * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection * @param {INodeExecutionData[]} items The items to be updated From 51bd1b473fa21b3853d27fa78004e87c261dbb32 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 16:03:59 +0200 Subject: [PATCH 08/25] :racehorse: adjust icon dimensions and tinify it --- packages/nodes-base/nodes/QuestDB/questdb.png | Bin 11244 -> 2542 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/nodes-base/nodes/QuestDB/questdb.png b/packages/nodes-base/nodes/QuestDB/questdb.png index 90febff64f5d4facdd03c641e2cb6ffd0d2dd373..85543a9c55466d8cf2ff61ff882c0cea0e433688 100644 GIT binary patch literal 2542 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 Eg1%4-ApigX literal 11244 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 From 8405d48e0c20f72b1026050f67ec44dc621b2897 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 16:30:21 +0200 Subject: [PATCH 09/25] :zap: complete QuestDB Node --- .../nodes-base/nodes/QuestDB/QuestDB.node.ts | 150 +++++------------- packages/nodes-base/package.json | 2 + 2 files changed, 40 insertions(+), 112 deletions(-) diff --git a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts b/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts index 8e54e102f7..93dcdbe66d 100644 --- a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts +++ b/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts @@ -8,31 +8,11 @@ import { import * as pgPromise from 'pg-promise'; - -/** - * Returns of copy of the items which only contains the json data and - * of that only the define properties - * - * @param {INodeExecutionData[]} items The items to copy - * @param {string[]} properties The properties it should include - * @returns - */ -function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); - } - } - return newItem; - }); -} - +import { + pgInsert, + pgQuery, + pgUpdate, +} from '../Postgres/Postgres.node.functions'; export class QuestDB implements INodeType { description: INodeTypeDescription = { @@ -44,7 +24,7 @@ export class QuestDB implements INodeType { description: 'Gets, add and update data in QuestDB.', defaults: { name: 'QuestDB', - color: '#336791', + color: '#2C4A79', }, inputs: ['main'], outputs: ['main'], @@ -52,7 +32,7 @@ export class QuestDB implements INodeType { { name: 'questdb', required: true, - } + }, ], properties: [ { @@ -92,9 +72,7 @@ export class QuestDB implements INodeType { }, displayOptions: { show: { - operation: [ - 'executeQuery' - ], + operation: ['executeQuery'], }, }, default: '', @@ -103,7 +81,6 @@ export class QuestDB implements INodeType { description: 'The SQL query to execute.', }, - // ---------------------------------- // insert // ---------------------------------- @@ -113,9 +90,7 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], + operation: ['insert'], }, }, default: 'public', @@ -128,9 +103,7 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], + operation: ['insert'], }, }, default: '', @@ -143,14 +116,13 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], + operation: ['insert'], }, }, default: '', placeholder: 'id,name,description', - description: 'Comma separated list of the properties which should used as columns for the new rows.', + description: + 'Comma separated list of the properties which should used as columns for the new rows.', }, { displayName: 'Return Fields', @@ -158,16 +130,14 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], + operation: ['insert'], }, }, default: '*', - description: 'Comma separated list of the fields that the operation will return', + description: + 'Comma separated list of the fields that the operation will return', }, - // ---------------------------------- // update // ---------------------------------- @@ -177,9 +147,7 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'update' - ], + operation: ['update'], }, }, default: '', @@ -192,14 +160,13 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'update' - ], + operation: ['update'], }, }, default: 'id', required: true, - description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', + description: + 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', }, { displayName: 'Columns', @@ -207,22 +174,18 @@ export class QuestDB implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'update' - ], + operation: ['update'], }, }, default: '', placeholder: 'name,description', - description: 'Comma separated list of the properties which should used as columns for rows to update.', + description: + 'Comma separated list of the properties which should used as columns for rows to update.', }, - - ] + ], }; - async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials('questdb'); if (credentials === undefined) { @@ -237,8 +200,10 @@ export class QuestDB implements INodeType { database: credentials.database as string, user: credentials.user as string, password: credentials.password as string, - ssl: !['disable', undefined].includes(credentials.ssl as string | undefined), - sslmode: credentials.ssl as string || 'disable', + ssl: !['disable', undefined].includes( + credentials.ssl as string | undefined, + ), + sslmode: (credentials.ssl as string) || 'disable', }; const db = pgp(config); @@ -253,39 +218,20 @@ export class QuestDB implements INodeType { // executeQuery // ---------------------------------- - const queries: string[] = []; - for (let i = 0; i < items.length; i++) { - queries.push(this.getNodeParameter('query', i) as string); - } - - const queryResult = await db.any(pgp.helpers.concat(queries)); + const queryResult = await pgQuery(this.getNodeParameter, pgp, db, items); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); - } else if (operation === 'insert') { // ---------------------------------- // insert // ---------------------------------- - const table = this.getNodeParameter('table', 0) as string; - const schema = this.getNodeParameter('schema', 0) as string; - let returnFields = (this.getNodeParameter('returnFields', 0) as string).split(',') as string[]; - const columnString = this.getNodeParameter('columns', 0) as string; - const columns = columnString.split(',').map(column => column.trim()); - - const cs = new pgp.helpers.ColumnSet(columns); - - const te = new pgp.helpers.TableName({ table, schema }); - - // Prepare the data to insert and copy it to be returned - const insertItems = getItemCopy(items, columns); - - // Generate the multi-row insert query and return the id of new row - returnFields = returnFields.map(value => value.trim()).filter(value => !!value); - const query = pgp.helpers.insert(insertItems, cs, te) + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); - - // Executing the query to insert the data - const insertData = await db.manyOrNone(query); + const [insertData, insertItems] = await pgInsert( + this.getNodeParameter, + pgp, + db, + items, + ); // Add the id to the data for (let i = 0; i < insertData.length; i++) { @@ -293,37 +239,17 @@ export class QuestDB implements INodeType { json: { ...insertData[i], ...insertItems[i], - } + }, }); } - } else if (operation === 'update') { // ---------------------------------- // update // ---------------------------------- - const table = this.getNodeParameter('table', 0) as string; - const updateKey = this.getNodeParameter('updateKey', 0) as string; - const columnString = this.getNodeParameter('columns', 0) as string; - - const columns = columnString.split(',').map(column => column.trim()); - - // Make sure that the updateKey does also get queried - if (!columns.includes(updateKey)) { - columns.unshift(updateKey); - } - - // Prepare the data to update and copy it to be returned - const updateItems = getItemCopy(items, columns); - - // Generate the multi-row update query - const query = pgp.helpers.update(updateItems, columns, table) + ' WHERE v.' + updateKey + ' = t.' + updateKey; - - // Executing the query to update the data - await db.none(query); - - returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); + const updateItems = await pgUpdate(this.getNodeParameter, pgp, db, items); + returnItems = this.helpers.returnJsonArray(updateItems); } else { await pgp.end(); throw new Error(`The operation "${operation}" is not supported!`); diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3c51466ea3..99fc37c9b0 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -112,6 +112,7 @@ "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", "dist/credentials/PostmarkApi.credentials.js", + "dist/credentials/Questdb.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", "dist/credentials/RundeckApi.credentials.js", @@ -260,6 +261,7 @@ "dist/nodes/Pipedrive/PipedriveTrigger.node.js", "dist/nodes/Postgres/Postgres.node.js", "dist/nodes/Postmark/PostmarkTrigger.node.js", + "dist/nodes/QuestDB/QuestDB.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js", "dist/nodes/ReadPdf.node.js", From 6870b4dfd3b4602693f3c96bcf3194ae4c050a37 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 16:36:15 +0200 Subject: [PATCH 10/25] :rewind: Revert "adjust icon dimensions and tinify it" This reverts commit 51bd1b473fa21b3853d27fa78004e87c261dbb32. --- packages/nodes-base/nodes/QuestDB/questdb.png | Bin 2542 -> 11244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/nodes-base/nodes/QuestDB/questdb.png b/packages/nodes-base/nodes/QuestDB/questdb.png index 85543a9c55466d8cf2ff61ff882c0cea0e433688..90febff64f5d4facdd03c641e2cb6ffd0d2dd373 100644 GIT binary patch literal 11244 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 literal 2542 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 Eg1%4-ApigX From 9f39156994173c895edb9daf4bc3a61f2eed60fe Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Wed, 8 Jul 2020 16:44:05 +0200 Subject: [PATCH 11/25] :racehorse: tinify icon --- packages/nodes-base/nodes/QuestDB/questdb.png | Bin 11244 -> 2835 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/nodes-base/nodes/QuestDB/questdb.png b/packages/nodes-base/nodes/QuestDB/questdb.png index 90febff64f5d4facdd03c641e2cb6ffd0d2dd373..5be1906e5d9d408257f4d2831489dc310126ecaf 100644 GIT binary patch literal 2835 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 literal 11244 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 From e32ec5b206581f45ea2a55c231671c041a319536 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 8 Jul 2020 16:47:08 +0200 Subject: [PATCH 12/25] :bookmark: Release n8n-nodes-base@0.68.1 --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1141485b4c..b3ccc3e267 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.68.0", + "version": "0.68.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From b3671380e3642d276a557e7d57086d3c5cb7fd2e Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 8 Jul 2020 16:48:54 +0200 Subject: [PATCH 13/25] :arrow_up: Set n8n-nodes-base@0.68.1 on n8n --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 0bda408ec3..ab9d715f37 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -102,7 +102,7 @@ "mysql2": "^2.0.1", "n8n-core": "~0.38.0", "n8n-editor-ui": "~0.49.0", - "n8n-nodes-base": "~0.68.0", + "n8n-nodes-base": "~0.68.1", "n8n-workflow": "~0.34.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", From 16bd10fbd11f595862501ecac73f7985d74f853a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 8 Jul 2020 16:50:15 +0200 Subject: [PATCH 14/25] :bookmark: Release n8n@0.73.1 --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ab9d715f37..e9b16ad9e2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.73.0", + "version": "0.73.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", From e07da55a1a538a05f322b9d089008b65f235f0ea Mon Sep 17 00:00:00 2001 From: smamudhan <48641776+smamudhan@users.noreply.github.com> Date: Thu, 9 Jul 2020 18:03:05 +0530 Subject: [PATCH 15/25] Typographical changes to ensure consistency with docs (#740) * Updated Dropdown Descriptions to match documentation * Removed full stops and hyphens in field descriptions * Removed hyphen on description on line 267 * Fixed typographical errors for Github node * Fixed typo for Zoom Credentials * Changed Postgres node description to match Postgres docs (https://github.com/n8n-io/n8n-docs/pull/30) * Changed Rocketchat displayname to RocketChat * Fixed typographical errors (and matched with docs) for Trello node descriptions * Updated Jira node descriptions to match docs --- packages/nodes-base/credentials/ZoomApi.credentials.ts | 2 +- packages/nodes-base/nodes/Jira/IssueDescription.ts | 4 ++-- packages/nodes-base/nodes/Postgres/Postgres.node.ts | 6 +++--- packages/nodes-base/nodes/Rocketchat/Rocketchat.node.ts | 6 +++--- packages/nodes-base/nodes/Trello/AttachmentDescription.ts | 2 +- packages/nodes-base/nodes/Trello/ChecklistDescription.ts | 6 +++--- packages/nodes-base/nodes/Trello/LabelDescription.ts | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/nodes-base/credentials/ZoomApi.credentials.ts b/packages/nodes-base/credentials/ZoomApi.credentials.ts index dbef996429..6efd857568 100644 --- a/packages/nodes-base/credentials/ZoomApi.credentials.ts +++ b/packages/nodes-base/credentials/ZoomApi.credentials.ts @@ -5,7 +5,7 @@ export class ZoomApi implements ICredentialType { displayName = 'Zoom API'; properties = [ { - displayName: 'JTW Token', + displayName: 'JWT Token', name: 'accessToken', type: 'string' as NodePropertyTypes, default: '' diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index bdb06179df..d29c9a5350 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -41,12 +41,12 @@ export const issueOperations = [ { name: 'Notify', value: 'notify', - description: 'Creates an email notification for an issue and adds it to the mail queue.', + description: 'Create an email notification for an issue and add it to the mail queue', }, { name: 'Status', value: 'transitions', - description: `Returns either all transitions or a transition that can be performed by the user on an issue, based on the issue's status.`, + description: `Return either all transitions or a transition that can be performed by the user on an issue, based on the issue's status`, }, { name: 'Delete', diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 2fa010576b..ad074a5f51 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -63,17 +63,17 @@ export class Postgres implements INodeType { { name: 'Execute Query', value: 'executeQuery', - description: 'Executes a SQL query.', + description: 'Execute an SQL query', }, { name: 'Insert', value: 'insert', - description: 'Insert rows in database.', + description: 'Insert rows in database', }, { name: 'Update', value: 'update', - description: 'Updates rows in database.', + description: 'Update rows in database', }, ], default: 'insert', diff --git a/packages/nodes-base/nodes/Rocketchat/Rocketchat.node.ts b/packages/nodes-base/nodes/Rocketchat/Rocketchat.node.ts index e83a3afbfe..65dd1a6820 100644 --- a/packages/nodes-base/nodes/Rocketchat/Rocketchat.node.ts +++ b/packages/nodes-base/nodes/Rocketchat/Rocketchat.node.ts @@ -48,15 +48,15 @@ interface IPostMessageBody { export class Rocketchat implements INodeType { description: INodeTypeDescription = { - displayName: 'Rocketchat', + displayName: 'RocketChat', name: 'rocketchat', icon: 'file:rocketchat.png', group: ['output'], version: 1, subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', - description: 'Consume Rocketchat API', + description: 'Consume RocketChat API', defaults: { - name: 'Rocketchat', + name: 'RocketChat', color: '#c02428', }, inputs: ['main'], diff --git a/packages/nodes-base/nodes/Trello/AttachmentDescription.ts b/packages/nodes-base/nodes/Trello/AttachmentDescription.ts index a04f507518..4306b2ed11 100644 --- a/packages/nodes-base/nodes/Trello/AttachmentDescription.ts +++ b/packages/nodes-base/nodes/Trello/AttachmentDescription.ts @@ -29,7 +29,7 @@ export const attachmentOperations = [ { name: 'Get', value: 'get', - description: 'Get the data of an attachments', + description: 'Get the data of an attachment', }, { name: 'Get All', diff --git a/packages/nodes-base/nodes/Trello/ChecklistDescription.ts b/packages/nodes-base/nodes/Trello/ChecklistDescription.ts index 34036f83e2..be11ba6f17 100644 --- a/packages/nodes-base/nodes/Trello/ChecklistDescription.ts +++ b/packages/nodes-base/nodes/Trello/ChecklistDescription.ts @@ -44,17 +44,17 @@ export const checklistOperations = [ { name: 'Get Checklist Items', value: 'getCheckItem', - description: 'Get a specific Checklist on a card', + description: 'Get a specific checklist on a card', }, { name: 'Get Completed Checklist Items', value: 'completedCheckItems', - description: 'Get the completed Checklist items on a card', + description: 'Get the completed checklist items on a card', }, { name: 'Update Checklist Item', value: 'updateCheckItem', - description: 'Update an item in a checklist on a card.', + description: 'Update an item in a checklist on a card', }, ], default: 'getAll', diff --git a/packages/nodes-base/nodes/Trello/LabelDescription.ts b/packages/nodes-base/nodes/Trello/LabelDescription.ts index 9c23053282..2b938ae5de 100644 --- a/packages/nodes-base/nodes/Trello/LabelDescription.ts +++ b/packages/nodes-base/nodes/Trello/LabelDescription.ts @@ -39,7 +39,7 @@ export const labelOperations = [ { name: 'Get All', value: 'getAll', - description: 'Returns all label for the board', + description: 'Returns all labels for the board', }, { name: 'Remove From Card', From 3bf34cffa90a1810b265d5ce0872d25db13a658c Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck Date: Thu, 9 Jul 2020 17:16:45 +0200 Subject: [PATCH 16/25] :art: fix format --- .../credentials/Questdb.credentials.ts | 8 ++---- .../nodes/Postgres/Postgres.node.functions.ts | 19 +++---------- .../nodes/Postgres/Postgres.node.ts | 21 +++------------ .../nodes-base/nodes/QuestDB/QuestDB.node.ts | 27 ++++--------------- 4 files changed, 15 insertions(+), 60 deletions(-) diff --git a/packages/nodes-base/credentials/Questdb.credentials.ts b/packages/nodes-base/credentials/Questdb.credentials.ts index 5cd60f960a..3672f6204d 100644 --- a/packages/nodes-base/credentials/Questdb.credentials.ts +++ b/packages/nodes-base/credentials/Questdb.credentials.ts @@ -1,8 +1,4 @@ -import { - ICredentialType, - NodePropertyTypes, -} from 'n8n-workflow'; - +import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; export class QuestDB implements ICredentialType { name = 'questdb'; @@ -59,7 +55,7 @@ export class QuestDB implements ICredentialType { { name: 'verify-full (not implemented)', value: 'verify-full', - } + }, ], default: 'disable', }, diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts index b07835d1f6..fb4c50051c 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -10,10 +10,7 @@ import pg = require('pg-promise/typescript/pg-subset'); * @param {string[]} properties The properties it should include * @returns */ -function getItemCopy( - items: INodeExecutionData[], - properties: string[], -): IDataObject[] { +function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { // Prepare the data to insert and copy it to be returned let newItem: IDataObject; return items.map(item => { @@ -69,9 +66,7 @@ export async function pgInsert( ): Promise> { const table = getNodeParam('table', 0) as string; const schema = getNodeParam('schema', 0) as string; - let returnFields = (getNodeParam('returnFields', 0) as string).split( - ',', - ) as string[]; + let returnFields = (getNodeParam('returnFields', 0) as string).split(',') as string[]; const columnString = getNodeParam('columns', 0) as string; const columns = columnString.split(',').map(column => column.trim()); @@ -83,9 +78,7 @@ export async function pgInsert( const insertItems = getItemCopy(items, columns); // Generate the multi-row insert query and return the id of new row - returnFields = returnFields - .map(value => value.trim()) - .filter(value => !!value); + returnFields = returnFields.map(value => value.trim()).filter(value => !!value); const query = pgp.helpers.insert(insertItems, cs, te) + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); @@ -127,11 +120,7 @@ export async function pgUpdate( // Generate the multi-row update query const query = - pgp.helpers.update(updateItems, columns, table) + - ' WHERE v.' + - updateKey + - ' = t.' + - updateKey; + pgp.helpers.update(updateItems, columns, table) + ' WHERE v.' + updateKey + ' = t.' + updateKey; // Executing the query to update the data await db.none(query); diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 702799c5c2..847620a6fa 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -1,10 +1,5 @@ import { IExecuteFunctions } from 'n8n-core'; -import { - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; @@ -130,8 +125,7 @@ export class Postgres implements INodeType { }, }, default: '*', - description: - 'Comma separated list of the fields that the operation will return', + description: 'Comma separated list of the fields that the operation will return', }, // ---------------------------------- @@ -196,9 +190,7 @@ export class Postgres implements INodeType { database: credentials.database as string, user: credentials.user as string, password: credentials.password as string, - ssl: !['disable', undefined].includes( - credentials.ssl as string | undefined, - ), + ssl: !['disable', undefined].includes(credentials.ssl as string | undefined), sslmode: (credentials.ssl as string) || 'disable', }; @@ -222,12 +214,7 @@ export class Postgres implements INodeType { // insert // ---------------------------------- - const [insertData, insertItems] = await pgInsert( - this.getNodeParameter, - pgp, - db, - items, - ); + const [insertData, insertItems] = await pgInsert(this.getNodeParameter, pgp, db, items); // Add the id to the data for (let i = 0; i < insertData.length; i++) { diff --git a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts b/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts index 93dcdbe66d..bc8af9f17c 100644 --- a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts +++ b/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts @@ -1,18 +1,9 @@ import { IExecuteFunctions } from 'n8n-core'; -import { - IDataObject, - INodeExecutionData, - INodeType, - INodeTypeDescription, -} from 'n8n-workflow'; +import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; -import { - pgInsert, - pgQuery, - pgUpdate, -} from '../Postgres/Postgres.node.functions'; +import { pgInsert, pgQuery, pgUpdate } from '../Postgres/Postgres.node.functions'; export class QuestDB implements INodeType { description: INodeTypeDescription = { @@ -134,8 +125,7 @@ export class QuestDB implements INodeType { }, }, default: '*', - description: - 'Comma separated list of the fields that the operation will return', + description: 'Comma separated list of the fields that the operation will return', }, // ---------------------------------- @@ -200,9 +190,7 @@ export class QuestDB implements INodeType { database: credentials.database as string, user: credentials.user as string, password: credentials.password as string, - ssl: !['disable', undefined].includes( - credentials.ssl as string | undefined, - ), + ssl: !['disable', undefined].includes(credentials.ssl as string | undefined), sslmode: (credentials.ssl as string) || 'disable', }; @@ -226,12 +214,7 @@ export class QuestDB implements INodeType { // insert // ---------------------------------- - const [insertData, insertItems] = await pgInsert( - this.getNodeParameter, - pgp, - db, - items, - ); + const [insertData, insertItems] = await pgInsert(this.getNodeParameter, pgp, db, items); // Add the id to the data for (let i = 0; i < insertData.length; i++) { From bf81b064e15c5ec0118399f5b04bbab81395fdf5 Mon Sep 17 00:00:00 2001 From: ricardo Date: Thu, 9 Jul 2020 13:37:35 -0400 Subject: [PATCH 17/25] :zap: Improvements to Mailchimp-Node --- .../nodes/Mailchimp/GenericFunctions.ts | 1 - .../nodes/Mailchimp/Mailchimp.node.ts | 348 +++++++++++++++++- 2 files changed, 347 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index 59362b50d6..99c0af67fd 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -49,7 +49,6 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio const datacenter = (credentials.apiKey as string).split('-').pop(); options.url = `https://${datacenter}.${host}${endpoint}`; - return await this.helpers.request!(options); } else { const credentials = this.getCredentials('mailchimpOAuth2Api') as IDataObject; diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index 25dbeb9984..443fd5d20c 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -47,6 +47,7 @@ interface ICreateMemberBody { timestamp_opt?: string; tags?: string[]; merge_fields?: IDataObject; + interests?: IDataObject; } export class Mailchimp implements INodeType { @@ -112,6 +113,10 @@ export class Mailchimp implements INodeType { name: 'resource', type: 'options', options: [ + { + name: 'List Group', + value: 'listGroup', + }, { name: 'Member', value: 'member', @@ -194,6 +199,28 @@ export class Mailchimp implements INodeType { default: 'create', description: 'The operation to perform.', }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'listGroup', + ], + }, + }, + options: [ + { + name: 'Get All', + value: 'getAll', + description: 'Get all groups', + }, + ], + default: 'getAll', + description: 'The operation to perform.', + }, /* -------------------------------------------------------------------------- */ /* member:create */ /* -------------------------------------------------------------------------- */ @@ -534,6 +561,89 @@ export class Mailchimp implements INodeType { }, }, }, + { + displayName: 'Interest Groups', + name: 'groupsUi', + placeholder: 'Add Interest Group', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + name: 'groupsValues', + displayName: 'Group', + typeOptions: { + multipleValueButtonText: 'Add Interest Group', + }, + values: [ + { + displayName: 'Category ID', + name: 'categoryId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroupCategories', + loadOptionsDependsOn: [ + 'list', + ], + }, + default: '', + }, + { + displayName: 'Category Field ID', + name: 'categoryFieldId', + type: 'string', + default: '', + description: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'boolean', + default: false, + description: '', + }, + ], + }, + ], + }, + { + displayName: 'Interest Groups', + name: 'groupJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: '', + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'create', + ], + jsonParameters: [ + true, + ], + }, + }, + }, /* -------------------------------------------------------------------------- */ /* member:delete */ /* -------------------------------------------------------------------------- */ @@ -922,6 +1032,66 @@ export class Mailchimp implements INodeType { default: '', description: 'Type of email this member asked to get', }, + { + displayName: 'Interest Groups', + name: 'groupsUi', + placeholder: 'Add Interest Group', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + '/resource':[ + 'member' + ], + '/operation':[ + 'update', + ], + '/jsonParameters': [ + false, + ], + }, + }, + options: [ + { + name: 'groupsValues', + displayName: 'Group', + typeOptions: { + multipleValueButtonText: 'Add Interest Group', + }, + values: [ + { + displayName: 'Category ID', + name: 'categoryId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroupCategories', + loadOptionsDependsOn: [ + 'list', + ], + }, + default: '', + }, + { + displayName: 'Category Field ID', + name: 'categoryFieldId', + type: 'string', + default: '', + description: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'boolean', + default: false, + description: '', + }, + ], + }, + ], + }, { displayName: 'Language', name: 'language', @@ -1157,6 +1327,29 @@ export class Mailchimp implements INodeType { }, }, }, + { + displayName: 'Interest Groups', + name: 'groupJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: '', + displayOptions: { + show: { + resource:[ + 'member', + ], + operation: [ + 'update', + ], + jsonParameters: [ + true, + ], + }, + }, + }, /* -------------------------------------------------------------------------- */ /* memberTag:create */ /* -------------------------------------------------------------------------- */ @@ -1250,6 +1443,96 @@ export class Mailchimp implements INodeType { }, ], }, +/* -------------------------------------------------------------------------- */ +/* member:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'listGroup', + ], + operation: [ + 'getAll', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists', + }, + { + displayName: 'Group Category', + name: 'groupCategory', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getGroupCategories', + loadOptionsDependsOn: [ + 'list', + ], + }, + displayOptions: { + show: { + resource: [ + 'listGroup', + ], + operation: [ + 'getAll', + ], + }, + }, + default: '', + options: [], + required: true, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'listGroup', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'listGroup', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + default: 500, + description: 'How many results to return.', + }, ], }; @@ -1261,7 +1544,7 @@ export class Mailchimp implements INodeType { // select them easily async getLists(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const { lists } = await mailchimpApiRequest.call(this, '/lists', 'GET'); + const lists = await mailchimpApiRequestAllItems.call(this, '/lists', 'GET', 'lists'); for (const list of lists) { const listName = list.name; const listId = list.id; @@ -1289,6 +1572,23 @@ export class Mailchimp implements INodeType { } return returnData; }, + + // Get all the interest fields to display them to user so that he can + // select them easily + async getGroupCategories(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const listId = this.getCurrentNodeParameter('list'); + const { categories } = await mailchimpApiRequest.call(this, `/lists/${listId}/interest-categories`, 'GET'); + for (const category of categories) { + const categoryName = category.title; + const categoryId = category.id; + returnData.push({ + name: categoryName, + value: categoryId, + }); + } + return returnData; + }, } }; @@ -1302,6 +1602,22 @@ export class Mailchimp implements INodeType { const operation = this.getNodeParameter('operation', 0) as string; for (let i = 0; i < length; i++) { + if (resource === 'listGroup') { + //https://mailchimp.com/developer/reference/lists/interest-categories/#get_/lists/-list_id-/interest-categories/-interest_category_id- + if (operation === 'getAll') { + const listId = this.getNodeParameter('list', i) as string; + const categoryId = this.getNodeParameter('groupCategory', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + if (returnAll === true) { + responseData = await mailchimpApiRequestAllItems.call(this, `/lists/${listId}/interest-categories/${categoryId}/interests`, 'GET', 'interests', {}, qs); + } else { + qs.count = this.getNodeParameter('limit', i) as number; + responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/interest-categories/${categoryId}/interests`, 'GET', {}, qs); + responseData = responseData.interests; + } + } + } if (resource === 'member') { //https://mailchimp.com/developer/reference/lists/list-members/#post_/lists/-list_id-/members if (operation === 'create') { @@ -1363,15 +1679,29 @@ export class Mailchimp implements INodeType { } body.merge_fields = mergeFields; } + + const groupsValues = (this.getNodeParameter('groupsUi', i) as IDataObject).groupsValues as IDataObject[]; + if (groupsValues) { + const groups = {}; + for (let i = 0; i < groupsValues.length; i++) { + // @ts-ignore + groups[groupsValues[i].categoryFieldId] = groupsValues[i].value; + } + body.interests = groups; + } } else { const locationJson = validateJSON(this.getNodeParameter('locationJson', i) as string); const mergeFieldsJson = validateJSON(this.getNodeParameter('mergeFieldsJson', i) as string); + const groupJson = validateJSON(this.getNodeParameter('groupJson', i) as string); if (locationJson) { body.location = locationJson; } if (mergeFieldsJson) { body.merge_fields = mergeFieldsJson; } + if (groupJson) { + body.interests = groupJson; + } } responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members`, 'POST', body); } @@ -1504,15 +1834,31 @@ export class Mailchimp implements INodeType { body.merge_fields = mergeFields; } } + if (updateFields.groupsUi) { + const groupsValues = (updateFields.groupsUi as IDataObject).groupsValues as IDataObject[]; + if (groupsValues) { + const groups = {}; + for (let i = 0; i < groupsValues.length; i++) { + // @ts-ignore + groups[groupsValues[i].categoryFieldId] = groupsValues[i].value; + } + body.interests = groups; + } + } } else { const locationJson = validateJSON(this.getNodeParameter('locationJson', i) as string); const mergeFieldsJson = validateJSON(this.getNodeParameter('mergeFieldsJson', i) as string); + const groupJson = validateJSON(this.getNodeParameter('groupJson', i) as string); + if (locationJson) { body.location = locationJson; } if (mergeFieldsJson) { body.merge_fields = mergeFieldsJson; } + if (groupJson) { + body.interests = groupJson; + } } responseData = await mailchimpApiRequest.call(this, `/lists/${listId}/members/${email}`, 'PUT', body); } From 5adec0a299a9e1f23e1a6fd039a3765307cd6439 Mon Sep 17 00:00:00 2001 From: ricardo Date: Thu, 9 Jul 2020 16:23:17 -0400 Subject: [PATCH 18/25] :zap: Add search operation to resource person --- .../nodes/Pipedrive/GenericFunctions.ts | 14 ++- .../nodes/Pipedrive/Pipedrive.node.ts | 115 +++++++++++++++++- 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts index 33bf215b12..a263afa096 100644 --- a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts @@ -48,6 +48,9 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio query.api_token = credentials.apiToken; const options: OptionsWithUri = { + headers: { + "Accept": "application/json", + }, method, qs: query, uri: `https://api.pipedrive.com/v1${endpoint}`, @@ -93,7 +96,7 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio if (error.response && error.response.body && error.response.body.error) { // Try to return the error prettier - let errorMessage = `Pipedrive error response [${error.statusCode}]: ${error.response.body.error}`; + let errorMessage = `Pipedrive error response [${error.statusCode}]: ${error.response.body.error.message}`; if (error.response.body.error_info) { errorMessage += ` - ${error.response.body.error_info}`; } @@ -124,7 +127,7 @@ export async function pipedriveApiRequestAllItems(this: IHookFunctions | IExecut if (query === undefined) { query = {}; } - query.limit = 500; + query.limit = 100; query.start = 0; const returnData: IDataObject[] = []; @@ -133,7 +136,12 @@ export async function pipedriveApiRequestAllItems(this: IHookFunctions | IExecut do { responseData = await pipedriveApiRequest.call(this, method, endpoint, body, query); - returnData.push.apply(returnData, responseData.data); + // the search path returns data diferently + if (responseData.data.items) { + returnData.push.apply(returnData, responseData.data.items); + } else { + returnData.push.apply(returnData, responseData.data); + } query.start = responseData.additionalData.pagination.next_start; } while ( diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index a5cd93ab9f..8157f94bde 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -25,7 +25,6 @@ interface CustomProperty { value: string; } - /** * Add the additional fields to the body * @@ -362,6 +361,11 @@ export class Pipedrive implements INodeType { value: 'getAll', description: 'Get data of all persons', }, + { + name: 'Search', + value: 'search', + description: 'Search all persons', + }, { name: 'Update', value: 'update', @@ -2021,6 +2025,7 @@ export class Pipedrive implements INodeType { show: { operation: [ 'getAll', + 'search', ], }, }, @@ -2035,6 +2040,7 @@ export class Pipedrive implements INodeType { show: { operation: [ 'getAll', + 'search', ], returnAll: [ false, @@ -2088,6 +2094,74 @@ export class Pipedrive implements INodeType { }, ], }, + + // ---------------------------------- + // person:search + // ---------------------------------- + { + displayName: 'Term', + name: 'term', + type: 'string', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'person', + ], + }, + }, + default: '', + description: 'The search term to look for. Minimum 2 characters (or 1 if using exact_match).', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'search', + ], + resource: [ + 'person', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Exact Match', + name: 'exactMatch', + type: 'boolean', + default: false, + description: 'When enabled, only full exact matches against the given term are returned. It is not case sensitive.', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: '', + description: 'A comma-separated string array. The fields to perform the search from. Defaults to all of them.', + }, + { + displayName: 'Include Fields', + name: 'includeFields', + type: 'string', + default: '', + description: 'Supports including optional fields in the results which are not provided by default.', + }, + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'string', + default: '', + description: 'Will filter Deals by the provided Organization ID.', + }, + ], + }, ], }; @@ -2526,6 +2600,39 @@ export class Pipedrive implements INodeType { endpoint = `/persons`; + } else if (operation === 'search') { + // ---------------------------------- + // persons:search + // ---------------------------------- + + requestMethod = 'GET'; + + qs.term = this.getNodeParameter('term', i) as string; + returnAll = this.getNodeParameter('returnAll', i) as boolean; + if (returnAll === false) { + qs.limit = this.getNodeParameter('limit', i) as number; + } + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (additionalFields.fields) { + qs.fields = additionalFields.fields as string; + } + + if (additionalFields.exactMatch) { + qs.exact_match = additionalFields.exactMatch as boolean; + } + + if (additionalFields.organizationId) { + qs.organization_id = parseInt(additionalFields.organizationId as string, 10); + } + + if (additionalFields.includeFields) { + qs.include_fields = additionalFields.includeFields as string; + } + + endpoint = `/persons/search`; + } else if (operation === 'update') { // ---------------------------------- // person:update @@ -2562,7 +2669,9 @@ export class Pipedrive implements INodeType { let responseData; if (returnAll === true) { + responseData = await pipedriveApiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + } else { if (customProperties !== undefined) { @@ -2597,6 +2706,10 @@ export class Pipedrive implements INodeType { responseData.data = []; } + if (operation === 'search' && responseData.data && responseData.data.items) { + responseData.data = responseData.data.items; + } + if (Array.isArray(responseData.data)) { returnData.push.apply(returnData, responseData.data as IDataObject[]); } else { From 305894d9b4de60d14fe7d9a8727610a3557f5c51 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 10 Jul 2020 10:12:30 +0200 Subject: [PATCH 19/25] :bug: Fix item display issue --- docker/images/n8n/README.md | 151 ++++++++---------- packages/editor-ui/src/components/RunData.vue | 8 +- 2 files changed, 77 insertions(+), 82 deletions(-) diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 7170dda595..977c53fef4 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -6,25 +6,23 @@ n8n is a free and open [fair-code](http://faircode.io) licensed node based Workf n8n.io - Screenshot - ## Contents -- [Demo](#demo) -- [Available integrations](#available-integrations) -- [Documentation](#documentation) -- [Start n8n in Docker](#start-n8n-in-docker) -- [Start with tunnel](#start-with-tunnel) -- [Securing n8n](#securing-n8n) -- [Persist data](#persist-data) -- [Passing Sensitive Data via File](#passing-sensitive-data-via-file) -- [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance) -- [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt) -- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) -- [Support](#support) -- [Jobs](#jobs) -- [Upgrading](#upgrading) -- [License](#license) - + - [Demo](#demo) + - [Available integrations](#available-integrations) + - [Documentation](#documentation) + - [Start n8n in Docker](#start-n8n-in-docker) + - [Start with tunnel](#start-with-tunnel) + - [Securing n8n](#securing-n8n) + - [Persist data](#persist-data) + - [Passing Sensitive Data via File](#passing-sensitive-data-via-file) + - [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance) + - [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt) + - [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) + - [Support](#support) + - [Jobs](#jobs) + - [Upgrading](#upgrading) + - [License](#license) ## Demo @@ -49,9 +47,9 @@ Additional information and example workflows on the n8n.io website: [https://n8n ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - n8nio/n8n + --name n8n \ + -p 5678:5678 \ + n8nio/n8n ``` You can then access n8n by opening: @@ -71,14 +69,13 @@ To use it simply start n8n with `--tunnel` ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start --tunnel + --name n8n \ + -p 5678:5678 \ + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start --tunnel ``` - ## Securing n8n By default n8n can be accessed by everybody. This is OK if you have it only running @@ -93,7 +90,6 @@ N8N_BASIC_AUTH_USER= N8N_BASIC_AUTH_PASSWORD= ``` - ## Persist data The workflow data gets by default saved in an SQLite database in the user @@ -102,10 +98,10 @@ settings like webhook URL and encryption key. ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n + --name n8n \ + -p 5678:5678 \ + -v ~/.n8n:/root/.n8n \ + n8nio/n8n ``` ### Start with other Database @@ -121,7 +117,6 @@ for the credentials. If none gets found n8n creates automatically one on startup. In case credentials are already saved with a different encryption key it can not be used anymore as encrypting it is not possible anymore. - #### Use with MongoDB > **WARNING**: Use Postgres if possible! Mongo has problems with saving large @@ -129,40 +124,39 @@ it can not be used anymore as encrypting it is not possible anymore. > may be dropped in the future. Replace the following placeholders with the actual data: - - - - - - - - - - + - MONGO_DATABASE + - MONGO_HOST + - MONGO_PORT + - MONGO_USER + - MONGO_PASSWORD ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e DB_TYPE=mongodb \ -e DB_MONGODB_CONNECTION_URL="mongodb://:@:/" \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start ``` A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withMongo/README.md) - #### Use with PostgresDB Replace the following placeholders with the actual data: - - - - - - - - - - - - + - POSTGRES_DATABASE + - POSTGRES_HOST + - POSTGRES_PASSWORD + - POSTGRES_PORT + - POSTGRES_USER + - POSTGRES_SCHEMA ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e DB_TYPE=postgresdb \ -e DB_POSTGRESDB_DATABASE= \ -e DB_POSTGRESDB_HOST= \ @@ -170,39 +164,37 @@ docker run -it --rm \ -e DB_POSTGRESDB_USER= \ -e DB_POSTGRESDB_SCHEMA= \ -e DB_POSTGRESDB_PASSWORD= \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start ``` A full working setup with docker-compose can be found [here](https://github.com/n8n-io/n8n/blob/master/docker/compose/withPostgres/README.md) - #### Use with MySQL Replace the following placeholders with the actual data: - - - - - - - - - - + - MYSQLDB_DATABASE + - MYSQLDB_HOST + - MYSQLDB_PASSWORD + - MYSQLDB_PORT + - MYSQLDB_USER ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e DB_TYPE=mysqldb \ -e DB_MYSQLDB_DATABASE= \ -e DB_MYSQLDB_HOST= \ -e DB_MYSQLDB_PORT= \ -e DB_MYSQLDB_USER= \ -e DB_MYSQLDB_PASSWORD= \ - -v ~/.n8n:/root/.n8n \ - n8nio/n8n \ - n8n start + -v ~/.n8n:/root/.n8n \ + n8nio/n8n \ + n8n start ``` - ## Passing Sensitive Data via File To avoid passing sensitive information via environment variables "_FILE" may be @@ -211,16 +203,15 @@ with the given name. That makes it possible to load data easily from Docker- and Kubernetes-Secrets. The following environment variables support file input: - - DB_MONGODB_CONNECTION_URL_FILE - - DB_POSTGRESDB_DATABASE_FILE - - DB_POSTGRESDB_HOST_FILE - - DB_POSTGRESDB_PASSWORD_FILE - - DB_POSTGRESDB_PORT_FILE - - DB_POSTGRESDB_USER_FILE - - DB_POSTGRESDB_SCHEMA_FILE - - N8N_BASIC_AUTH_PASSWORD_FILE - - N8N_BASIC_AUTH_USER_FILE - + - DB_MONGODB_CONNECTION_URL_FILE + - DB_POSTGRESDB_DATABASE_FILE + - DB_POSTGRESDB_HOST_FILE + - DB_POSTGRESDB_PASSWORD_FILE + - DB_POSTGRESDB_PORT_FILE + - DB_POSTGRESDB_USER_FILE + - DB_POSTGRESDB_SCHEMA_FILE + - N8N_BASIC_AUTH_PASSWORD_FILE + - N8N_BASIC_AUTH_USER_FILE ## Example Setup with Lets Encrypt @@ -235,7 +226,7 @@ docker pull n8nio/n8n # Stop current setup sudo docker-compose stop # Delete it (will only delete the docker-containers, data is stored separately) -sudo docker-compose rm +sudo docker-compose rm # Then start it again sudo docker-compose up -d ``` @@ -251,11 +242,11 @@ the environment variable `TZ`. Example to use the same timezone for both: ``` docker run -it --rm \ - --name n8n \ - -p 5678:5678 \ + --name n8n \ + -p 5678:5678 \ -e GENERIC_TIMEZONE="Europe/Berlin" \ -e TZ="Europe/Berlin" \ - n8nio/n8n + n8nio/n8n ``` diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index b3729e4f68..42fbaa5b19 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -19,7 +19,7 @@
- + Results: {{ dataCount }} Results: @@ -248,7 +248,11 @@ export default mixins( return executionData.resultData.runData; }, maxDisplayItemsOptions (): number[] { - return [25, 50, 100, 250, 500, 1000, this.dataCount].filter(option => option <= this.dataCount); + const options = [25, 50, 100, 250, 500, 1000].filter(option => option <= this.dataCount); + if (!options.includes(this.dataCount)) { + options.push(this.dataCount); + } + return options; }, node (): INodeUi | null { return this.$store.getters.activeNode; From e3e3f038d6bbc13c940453987c4236387a88b52f Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 10 Jul 2020 10:25:54 +0200 Subject: [PATCH 20/25] :zap: Minor improvement to Pipedrive-Node --- .../nodes/Pipedrive/GenericFunctions.ts | 2 +- .../nodes-base/nodes/Pipedrive/Pipedrive.node.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts index a263afa096..069ff5fe55 100644 --- a/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Pipedrive/GenericFunctions.ts @@ -49,7 +49,7 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio const options: OptionsWithUri = { headers: { - "Accept": "application/json", + Accept: 'application/json', }, method, qs: query, diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index 8157f94bde..13786ba94e 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -2160,6 +2160,13 @@ export class Pipedrive implements INodeType { default: '', description: 'Will filter Deals by the provided Organization ID.', }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + default: false, + description: `Returns the data exactly in the way it got received from the API.`, + }, ], }, ], @@ -2708,6 +2715,15 @@ export class Pipedrive implements INodeType { if (operation === 'search' && responseData.data && responseData.data.items) { responseData.data = responseData.data.items; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + if (additionalFields.rawData !== true) { + responseData.data = responseData.data.map((item: { result_score: number, item: object }) => { + return { + result_score: item.result_score, + ...item.item, + }; + }); + } } if (Array.isArray(responseData.data)) { From db972b384fc9d7c8efb4ca0b2dd6344af58b1ef2 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 10 Jul 2020 04:38:23 -0400 Subject: [PATCH 21/25] :bug: Fixes issue #735 (#743) --- packages/nodes-base/nodes/HttpRequest.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index 417d80eeea..08d3a0ac15 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -802,7 +802,7 @@ export class HttpRequest implements INodeType { if (oAuth2Api !== undefined) { //@ts-ignore - response = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions); + response = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions, 'Bearer'); } else { response = await this.helpers.request(requestOptions); } From a00fedb35109344ddffb1aa496773f63e83c6860 Mon Sep 17 00:00:00 2001 From: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> Date: Fri, 10 Jul 2020 10:48:19 +0200 Subject: [PATCH 22/25] :art: Extract postgres functionality (#737) * :tada: executeQuery function extracted * :construction: add prettierrc to gitignore * :construction: insert function extracted * :zap: extract update function * :bulb: fix function docs * :bulb: add in code documentation * :art: fix format * :art: fix format --- .gitignore | 1 + .../nodes/Postgres/Postgres.node.functions.ts | 129 ++++++++++++++++++ .../nodes/Postgres/Postgres.node.ts | 95 ++----------- 3 files changed, 140 insertions(+), 85 deletions(-) create mode 100644 packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts diff --git a/.gitignore b/.gitignore index b3eac39207..0441d445b4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ _START_PACKAGE .env .vscode .idea +.prettierrc.js diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts new file mode 100644 index 0000000000..fb4c50051c --- /dev/null +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.functions.ts @@ -0,0 +1,129 @@ +import { IDataObject, INodeExecutionData } from 'n8n-workflow'; +import pgPromise = require('pg-promise'); +import pg = require('pg-promise/typescript/pg-subset'); + +/** + * Returns of copy of the items which only contains the json data and + * of that only the define properties + * + * @param {INodeExecutionData[]} items The items to copy + * @param {string[]} properties The properties it should include + * @returns + */ +function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { + // Prepare the data to insert and copy it to be returned + let newItem: IDataObject; + return items.map(item => { + newItem = {}; + for (const property of properties) { + if (item.json[property] === undefined) { + newItem[property] = null; + } else { + newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + } + } + return newItem; + }); +} + +/** + * Executes the given SQL query on the database. + * + * @param {Function} getNodeParam The getter for the Node's parameters + * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance + * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection + * @param {input[]} input The Node's input data + * @returns Promise> + */ +export function pgQuery( + getNodeParam: Function, + pgp: pgPromise.IMain<{}, pg.IClient>, + db: pgPromise.IDatabase<{}, pg.IClient>, + input: INodeExecutionData[], +): Promise> { + const queries: string[] = []; + for (let i = 0; i < input.length; i++) { + queries.push(getNodeParam('query', i) as string); + } + + return db.any(pgp.helpers.concat(queries)); +} + +/** + * Inserts the given items into the database. + * + * @param {Function} getNodeParam The getter for the Node's parameters + * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance + * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection + * @param {INodeExecutionData[]} items The items to be inserted + * @returns Promise> + */ +export async function pgInsert( + getNodeParam: Function, + pgp: pgPromise.IMain<{}, pg.IClient>, + db: pgPromise.IDatabase<{}, pg.IClient>, + items: INodeExecutionData[], +): Promise> { + const table = getNodeParam('table', 0) as string; + const schema = getNodeParam('schema', 0) as string; + let returnFields = (getNodeParam('returnFields', 0) as string).split(',') as string[]; + const columnString = getNodeParam('columns', 0) as string; + const columns = columnString.split(',').map(column => column.trim()); + + const cs = new pgp.helpers.ColumnSet(columns); + + const te = new pgp.helpers.TableName({ table, schema }); + + // Prepare the data to insert and copy it to be returned + const insertItems = getItemCopy(items, columns); + + // Generate the multi-row insert query and return the id of new row + returnFields = returnFields.map(value => value.trim()).filter(value => !!value); + const query = + pgp.helpers.insert(insertItems, cs, te) + + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); + + // Executing the query to insert the data + const insertData = await db.manyOrNone(query); + + return [insertData, insertItems]; +} + +/** + * Updates the given items in the database. + * + * @param {Function} getNodeParam The getter for the Node's parameters + * @param {pgPromise.IMain<{}, pg.IClient>} pgp The pgPromise instance + * @param {pgPromise.IDatabase<{}, pg.IClient>} db The pgPromise database connection + * @param {INodeExecutionData[]} items The items to be updated + * @returns Promise> + */ +export async function pgUpdate( + getNodeParam: Function, + pgp: pgPromise.IMain<{}, pg.IClient>, + db: pgPromise.IDatabase<{}, pg.IClient>, + items: INodeExecutionData[], +): Promise> { + const table = getNodeParam('table', 0) as string; + const updateKey = getNodeParam('updateKey', 0) as string; + const columnString = getNodeParam('columns', 0) as string; + + const columns = columnString.split(',').map(column => column.trim()); + + // Make sure that the updateKey does also get queried + if (!columns.includes(updateKey)) { + columns.unshift(updateKey); + } + + // Prepare the data to update and copy it to be returned + const updateItems = getItemCopy(items, columns); + + // Generate the multi-row update query + const query = + pgp.helpers.update(updateItems, columns, table) + ' WHERE v.' + updateKey + ' = t.' + updateKey; + + // Executing the query to update the data + await db.none(query); + + return updateItems; +} diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index ad074a5f51..f465ca05c8 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -3,36 +3,12 @@ import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription, + INodeTypeDescription } from 'n8n-workflow'; import * as pgPromise from 'pg-promise'; - -/** - * Returns of copy of the items which only contains the json data and - * of that only the define properties - * - * @param {INodeExecutionData[]} items The items to copy - * @param {string[]} properties The properties it should include - * @returns - */ -function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); - } - } - return newItem; - }); -} - +import { pgInsert, pgQuery, pgUpdate } from './Postgres.node.functions'; export class Postgres implements INodeType { description: INodeTypeDescription = { @@ -52,7 +28,7 @@ export class Postgres implements INodeType { { name: 'postgres', required: true, - } + }, ], properties: [ { @@ -103,7 +79,6 @@ export class Postgres implements INodeType { description: 'The SQL query to execute.', }, - // ---------------------------------- // insert // ---------------------------------- @@ -143,9 +118,7 @@ export class Postgres implements INodeType { type: 'string', displayOptions: { show: { - operation: [ - 'insert' - ], + operation: ['insert'], }, }, default: '', @@ -167,7 +140,6 @@ export class Postgres implements INodeType { description: 'Comma separated list of the fields that the operation will return', }, - // ---------------------------------- // update // ---------------------------------- @@ -216,13 +188,10 @@ export class Postgres implements INodeType { placeholder: 'name,description', description: 'Comma separated list of the properties which should used as columns for rows to update.', }, - - ] + ], }; - async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials('postgres'); if (credentials === undefined) { @@ -253,39 +222,15 @@ export class Postgres implements INodeType { // executeQuery // ---------------------------------- - const queries: string[] = []; - for (let i = 0; i < items.length; i++) { - queries.push(this.getNodeParameter('query', i) as string); - } - - const queryResult = await db.any(pgp.helpers.concat(queries)); + const queryResult = await pgQuery(this.getNodeParameter, pgp, db, items); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); - } else if (operation === 'insert') { // ---------------------------------- // insert // ---------------------------------- - const table = this.getNodeParameter('table', 0) as string; - const schema = this.getNodeParameter('schema', 0) as string; - let returnFields = (this.getNodeParameter('returnFields', 0) as string).split(',') as string[]; - const columnString = this.getNodeParameter('columns', 0) as string; - const columns = columnString.split(',').map(column => column.trim()); - - const cs = new pgp.helpers.ColumnSet(columns); - - const te = new pgp.helpers.TableName({ table, schema }); - - // Prepare the data to insert and copy it to be returned - const insertItems = getItemCopy(items, columns); - - // Generate the multi-row insert query and return the id of new row - returnFields = returnFields.map(value => value.trim()).filter(value => !!value); - const query = pgp.helpers.insert(insertItems, cs, te) + (returnFields.length ? ` RETURNING ${returnFields.join(',')}` : ''); - - // Executing the query to insert the data - const insertData = await db.manyOrNone(query); + const [insertData, insertItems] = await pgInsert(this.getNodeParameter, pgp, db, items); // Add the id to the data for (let i = 0; i < insertData.length; i++) { @@ -293,37 +238,17 @@ export class Postgres implements INodeType { json: { ...insertData[i], ...insertItems[i], - } + }, }); } - } else if (operation === 'update') { // ---------------------------------- // update // ---------------------------------- - const table = this.getNodeParameter('table', 0) as string; - const updateKey = this.getNodeParameter('updateKey', 0) as string; - const columnString = this.getNodeParameter('columns', 0) as string; - - const columns = columnString.split(',').map(column => column.trim()); - - // Make sure that the updateKey does also get queried - if (!columns.includes(updateKey)) { - columns.unshift(updateKey); - } - - // Prepare the data to update and copy it to be returned - const updateItems = getItemCopy(items, columns); - - // Generate the multi-row update query - const query = pgp.helpers.update(updateItems, columns, table) + ' WHERE v.' + updateKey + ' = t.' + updateKey; - - // Executing the query to update the data - await db.none(query); - - returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); + const updateItems = await pgUpdate(this.getNodeParameter, pgp, db, items); + returnItems = this.helpers.returnJsonArray(updateItems); } else { await pgp.end(); throw new Error(`The operation "${operation}" is not supported!`); From ae67735d149e8b76b9aaf49b86e5db4681b04293 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 10 Jul 2020 10:58:39 +0200 Subject: [PATCH 23/25] :zap: Minor improvements to QuestDb-Node --- ...uestdb.credentials.ts => QuestDb.credentials.ts} | 4 ++-- .../QuestDB.node.ts => QuestDb/QuestDb.node.ts} | 8 ++++---- .../nodes/{QuestDB => QuestDb}/questdb.png | Bin packages/nodes-base/package.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename packages/nodes-base/credentials/{Questdb.credentials.ts => QuestDb.credentials.ts} (94%) rename packages/nodes-base/nodes/{QuestDB/QuestDB.node.ts => QuestDb/QuestDb.node.ts} (97%) rename packages/nodes-base/nodes/{QuestDB => QuestDb}/questdb.png (100%) diff --git a/packages/nodes-base/credentials/Questdb.credentials.ts b/packages/nodes-base/credentials/QuestDb.credentials.ts similarity index 94% rename from packages/nodes-base/credentials/Questdb.credentials.ts rename to packages/nodes-base/credentials/QuestDb.credentials.ts index 3672f6204d..24c1522737 100644 --- a/packages/nodes-base/credentials/Questdb.credentials.ts +++ b/packages/nodes-base/credentials/QuestDb.credentials.ts @@ -1,7 +1,7 @@ import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; -export class QuestDB implements ICredentialType { - name = 'questdb'; +export class QuestDb implements ICredentialType { + name = 'questDb'; displayName = 'QuestDB'; properties = [ { diff --git a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts b/packages/nodes-base/nodes/QuestDb/QuestDb.node.ts similarity index 97% rename from packages/nodes-base/nodes/QuestDB/QuestDB.node.ts rename to packages/nodes-base/nodes/QuestDb/QuestDb.node.ts index bc8af9f17c..20b6d48615 100644 --- a/packages/nodes-base/nodes/QuestDB/QuestDB.node.ts +++ b/packages/nodes-base/nodes/QuestDb/QuestDb.node.ts @@ -5,10 +5,10 @@ import * as pgPromise from 'pg-promise'; import { pgInsert, pgQuery, pgUpdate } from '../Postgres/Postgres.node.functions'; -export class QuestDB implements INodeType { +export class QuestDb implements INodeType { description: INodeTypeDescription = { displayName: 'QuestDB', - name: 'questdb', + name: 'questDb', icon: 'file:questdb.png', group: ['input'], version: 1, @@ -21,7 +21,7 @@ export class QuestDB implements INodeType { outputs: ['main'], credentials: [ { - name: 'questdb', + name: 'questDb', required: true, }, ], @@ -176,7 +176,7 @@ export class QuestDB implements INodeType { }; async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials('questdb'); + const credentials = this.getCredentials('questDb'); if (credentials === undefined) { throw new Error('No credentials got returned!'); diff --git a/packages/nodes-base/nodes/QuestDB/questdb.png b/packages/nodes-base/nodes/QuestDb/questdb.png similarity index 100% rename from packages/nodes-base/nodes/QuestDB/questdb.png rename to packages/nodes-base/nodes/QuestDb/questdb.png diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 0de5410ea0..94716b7f4b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -112,7 +112,7 @@ "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", "dist/credentials/PostmarkApi.credentials.js", - "dist/credentials/Questdb.credentials.js", + "dist/credentials/QuestDb.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", "dist/credentials/RundeckApi.credentials.js", @@ -261,7 +261,7 @@ "dist/nodes/Pipedrive/PipedriveTrigger.node.js", "dist/nodes/Postgres/Postgres.node.js", "dist/nodes/Postmark/PostmarkTrigger.node.js", - "dist/nodes/QuestDB/QuestDB.node.js", + "dist/nodes/QuestDb/QuestDb.node.js", "dist/nodes/ReadBinaryFile.node.js", "dist/nodes/ReadBinaryFiles.node.js", "dist/nodes/ReadPdf.node.js", From f33eebcec9d23cf8518e90fc4fc8bfec347ed8ab Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 10 Jul 2020 12:30:40 +0200 Subject: [PATCH 24/25] :zap: Remove empty description properties --- .../nodes/Mailchimp/Mailchimp.node.ts | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index 443fd5d20c..ff5ee63645 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -283,27 +283,22 @@ export class Mailchimp implements INodeType { { name: 'Subscribed', value: 'subscribed', - description: '', }, { name: 'Unsubscribed', value: 'unsubscribed', - description: '', }, { name: 'Cleaned', value: 'cleaned', - description: '', }, { name: 'Pending', value: 'pending', - description: '', }, { name: 'Transactional', value: 'transactional', - description: '', }, ], default: '', @@ -314,7 +309,6 @@ export class Mailchimp implements INodeType { name: 'jsonParameters', type: 'boolean', default: false, - description: '', displayOptions: { show: { resource:[ @@ -351,12 +345,10 @@ export class Mailchimp implements INodeType { { name: 'HTML', value: 'html', - description: '', }, { name: 'Text', value: 'text', - description: '', }, ], default: '', @@ -523,7 +515,6 @@ export class Mailchimp implements INodeType { alwaysOpenEditWindow: true, }, default: '', - description: '', displayOptions: { show: { resource:[ @@ -546,7 +537,6 @@ export class Mailchimp implements INodeType { alwaysOpenEditWindow: true, }, default: '', - description: '', displayOptions: { show: { resource:[ @@ -608,14 +598,12 @@ export class Mailchimp implements INodeType { name: 'categoryFieldId', type: 'string', default: '', - description: '', }, { displayName: 'Value', name: 'value', type: 'boolean', default: false, - description: '', }, ], }, @@ -629,7 +617,6 @@ export class Mailchimp implements INodeType { alwaysOpenEditWindow: true, }, default: '', - description: '', displayOptions: { show: { resource:[ @@ -882,12 +869,10 @@ export class Mailchimp implements INodeType { { name: 'HTML', value: 'html', - description: '', }, { name: 'Text', value: 'text', - description: '', }, ], default: '', @@ -901,27 +886,22 @@ export class Mailchimp implements INodeType { { name: 'Subscribed', value: 'subscribed', - description: '', }, { name: 'Unsubscribed', value: 'unsubscribed', - description: '', }, { name: 'Cleaned', value: 'cleaned', - description: '', }, { name: 'Pending', value: 'pending', - description: '', }, { name: 'Transactional', value: 'transactional', - description: '', }, ], default: '', @@ -984,7 +964,6 @@ export class Mailchimp implements INodeType { name: 'jsonParameters', type: 'boolean', default: false, - description: '', displayOptions: { show: { resource:[ @@ -1021,12 +1000,10 @@ export class Mailchimp implements INodeType { { name: 'HTML', value: 'html', - description: '', }, { name: 'Text', value: 'text', - description: '', }, ], default: '', @@ -1079,14 +1056,12 @@ export class Mailchimp implements INodeType { name: 'categoryFieldId', type: 'string', default: '', - description: '', }, { displayName: 'Value', name: 'value', type: 'boolean', default: false, - description: '', }, ], }, @@ -1194,27 +1169,22 @@ export class Mailchimp implements INodeType { { name: 'Subscribed', value: 'subscribed', - description: '', }, { name: 'Unsubscribed', value: 'unsubscribed', - description: '', }, { name: 'Cleaned', value: 'cleaned', - description: '', }, { name: 'Pending', value: 'pending', - description: '', }, { name: 'Transactional', value: 'transactional', - description: '', }, ], default: '', @@ -1289,7 +1259,6 @@ export class Mailchimp implements INodeType { alwaysOpenEditWindow: true, }, default: '', - description: '', displayOptions: { show: { resource:[ @@ -1312,7 +1281,6 @@ export class Mailchimp implements INodeType { alwaysOpenEditWindow: true, }, default: '', - description: '', displayOptions: { show: { resource:[ @@ -1335,7 +1303,6 @@ export class Mailchimp implements INodeType { alwaysOpenEditWindow: true, }, default: '', - description: '', displayOptions: { show: { resource:[ From 941ee06b146d0c72dc8de56288dd45d6abea945a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 12 Jul 2020 09:28:08 +0200 Subject: [PATCH 25/25] :zap: Make n8n work in subfolder & Fix events in AffinityTrigger --- docker/compose/subfolderWithSSL/.env | 25 ++++++++ docker/compose/subfolderWithSSL/README.md | 26 +++++++++ .../subfolderWithSSL/docker-compose.yml | 57 +++++++++++++++++++ packages/cli/config/index.ts | 7 +++ packages/cli/src/GenericHelpers.ts | 5 +- packages/cli/src/Server.ts | 39 ++++++++++--- packages/editor-ui/public/index.html | 3 +- .../editor-ui/src/components/MainSidebar.vue | 4 +- packages/editor-ui/src/router.ts | 3 +- packages/editor-ui/src/store.ts | 3 +- packages/editor-ui/vue.config.js | 2 +- .../nodes/Affinity/AffinityTrigger.node.ts | 6 +- 12 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 docker/compose/subfolderWithSSL/.env create mode 100644 docker/compose/subfolderWithSSL/README.md create mode 100644 docker/compose/subfolderWithSSL/docker-compose.yml diff --git a/docker/compose/subfolderWithSSL/.env b/docker/compose/subfolderWithSSL/.env new file mode 100644 index 0000000000..7008bd631a --- /dev/null +++ b/docker/compose/subfolderWithSSL/.env @@ -0,0 +1,25 @@ +# Folder where data should be saved +DATA_FOLDER=/root/n8n/ + +# The top level domain to serve from +DOMAIN_NAME=example.com + +# The subfolder to serve from +SUBFOLDER=app1 +N8N_PATH=/app1/ + +# DOMAIN_NAME and SUBDOMAIN combined decide where n8n will be reachable from +# above example would result in: https://example.com/n8n/ + +# The user name to use for autentication - IMPORTANT ALWAYS CHANGE! +N8N_BASIC_AUTH_USER=user + +# The password to use for autentication - IMPORTANT ALWAYS CHANGE! +N8N_BASIC_AUTH_PASSWORD=password + +# Optional timezone to set which gets used by Cron-Node by default +# If not set New York time will be used +GENERIC_TIMEZONE=Europe/Berlin + +# The email address to use for the SSL certificate creation +SSL_EMAIL=user@example.com diff --git a/docker/compose/subfolderWithSSL/README.md b/docker/compose/subfolderWithSSL/README.md new file mode 100644 index 0000000000..61fcb5b7e7 --- /dev/null +++ b/docker/compose/subfolderWithSSL/README.md @@ -0,0 +1,26 @@ +# n8n on Subfolder with SSL + +Starts n8n and deployes it on a subfolder + + +## Start + +To start n8n in a subfolder simply start docker-compose by executing the following +command in the current folder. + + +**IMPORTANT:** But before you do that change the default users and passwords in the `.env` file! + +``` +docker-compose up -d +``` + +To stop it execute: + +``` +docker-compose stop +``` + +## Configuration + +The default name of the database, user and password for MongoDB can be changed in the `.env` file in the current directory. diff --git a/docker/compose/subfolderWithSSL/docker-compose.yml b/docker/compose/subfolderWithSSL/docker-compose.yml new file mode 100644 index 0000000000..5e540abbb5 --- /dev/null +++ b/docker/compose/subfolderWithSSL/docker-compose.yml @@ -0,0 +1,57 @@ +version: "3" + +services: + traefik: + image: "traefik" + command: + - "--api=true" + - "--api.insecure=true" + - "--api.dashboard=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.websecure.address=:443" + - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true" + - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}" + - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json" + - /home/jan/www/n8n/n8n:/data + ports: + - "443:443" + - "80:80" + volumes: + - ${DATA_FOLDER}/letsencrypt:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro + n8n: + image: n8nio/n8n + ports: + - "127.0.0.1:5678:5678" + labels: + - traefik.enable=true + - traefik.http.routers.n8n.rule=Host(`${DOMAIN_NAME}`) + - traefik.http.routers.n8n.tls=true + - traefik.http.routers.n8n.entrypoints=websecure + - "traefik.http.routers.n8n.rule=PathPrefix(`/${SUBFOLDER}{regex:$$|/.*}`)" + - "traefik.http.middlewares.n8n-stripprefix.stripprefix.prefixes=/${SUBFOLDER}" + - "traefik.http.routers.n8n.middlewares=n8n-stripprefix" + - traefik.http.routers.n8n.tls.certresolver=mytlschallenge + - traefik.http.middlewares.n8n.headers.SSLRedirect=true + - traefik.http.middlewares.n8n.headers.STSSeconds=315360000 + - traefik.http.middlewares.n8n.headers.browserXSSFilter=true + - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true + - traefik.http.middlewares.n8n.headers.forceSTSHeader=true + - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME} + - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true + - traefik.http.middlewares.n8n.headers.STSPreload=true + environment: + - N8N_BASIC_AUTH_ACTIVE=true + - N8N_BASIC_AUTH_USER + - N8N_BASIC_AUTH_PASSWORD + - N8N_HOST=${DOMAIN_NAME} + - N8N_PORT=5678 + - N8N_PROTOCOL=https + - NODE_ENV=production + - N8N_PATH + - WEBHOOK_TUNNEL_URL=http://${DOMAIN_NAME}${N8N_PATH} + - VUE_APP_URL_BASE_API=http://${DOMAIN_NAME}${N8N_PATH} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_FOLDER}/.n8n:/root/.n8n diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index 847587460f..380a93c4a8 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -204,6 +204,13 @@ const config = convict({ }, // How n8n can be reached (Editor & REST-API) + path: { + format: String, + default: '/', + arg: 'path', + env: 'N8N_PATH', + doc: 'Path n8n is deployed to' + }, host: { format: String, default: 'localhost', diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index 8b02b73e8e..cab67f7bce 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -40,11 +40,12 @@ export function getBaseUrl(): string { const protocol = config.get('protocol') as string; const host = config.get('host') as string; const port = config.get('port') as number; + const path = config.get('path') as string; if (protocol === 'http' && port === 80 || protocol === 'https' && port === 443) { - return `${protocol}://${host}/`; + return `${protocol}://${host}${path}`; } - return `${protocol}://${host}:${port}/`; + return `${protocol}://${host}:${port}${path}`; } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 2507b6b136..1a0b75842a 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -931,7 +931,8 @@ class App { // Authorize OAuth Data this.app.get(`/${this.restEndpoint}/oauth1-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { if (req.query.id === undefined) { - throw new Error('Required credential id is missing!'); + res.status(500).send('Required credential id is missing!'); + return ''; } const result = await Db.collections.Credentials!.findOne(req.query.id as string); @@ -943,7 +944,8 @@ class App { let encryptionKey = undefined; encryptionKey = await UserSettings.getEncryptionKey(); if (encryptionKey === undefined) { - throw new Error('No encryption key got found to decrypt the credentials!'); + res.status(500).send('No encryption key got found to decrypt the credentials!'); + return ''; } // Decrypt the currently saved credentials @@ -1015,7 +1017,8 @@ class App { const { oauth_verifier, oauth_token, cid } = req.query; if (oauth_verifier === undefined || oauth_token === undefined) { - throw new Error('Insufficient parameters for OAuth1 callback'); + const errorResponse = new ResponseHelper.ResponseError('Insufficient parameters for OAuth1 callback. Received following query parameters: ' + JSON.stringify(req.query), undefined, 503); + return ResponseHelper.sendErrorResponse(res, errorResponse); } const result = await Db.collections.Credentials!.findOne(cid as any); // tslint:disable-line:no-any @@ -1085,7 +1088,8 @@ class App { // Authorize OAuth Data this.app.get(`/${this.restEndpoint}/oauth2-credential/auth`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { if (req.query.id === undefined) { - throw new Error('Required credential id is missing!'); + res.status(500).send('Required credential id is missing.'); + return ''; } const result = await Db.collections.Credentials!.findOne(req.query.id as string); @@ -1097,7 +1101,8 @@ class App { let encryptionKey = undefined; encryptionKey = await UserSettings.getEncryptionKey(); if (encryptionKey === undefined) { - throw new Error('No encryption key got found to decrypt the credentials!'); + res.status(500).send('No encryption key got found to decrypt the credentials!'); + return ''; } // Decrypt the currently saved credentials @@ -1161,7 +1166,8 @@ class App { const {code, state: stateEncoded } = req.query; if (code === undefined || stateEncoded === undefined) { - throw new Error('Insufficient parameters for OAuth2 callback'); + const errorResponse = new ResponseHelper.ResponseError('Insufficient parameters for OAuth2 callback. Received following query parameters: ' + JSON.stringify(req.query), undefined, 503); + return ResponseHelper.sendErrorResponse(res, errorResponse); } let state; @@ -1211,17 +1217,20 @@ class App { }, }; } + const redirectUri = `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`; const oAuthObj = new clientOAuth2({ clientId: _.get(oauthCredentials, 'clientId') as string, clientSecret: _.get(oauthCredentials, 'clientSecret', '') as string, accessTokenUri: _.get(oauthCredentials, 'accessTokenUrl', '') as string, authorizationUri: _.get(oauthCredentials, 'authUrl', '') as string, - redirectUri: `${WebhookHelpers.getWebhookBaseUrl()}${this.restEndpoint}/oauth2-credential/callback`, + redirectUri, scopes: _.split(_.get(oauthCredentials, 'scope', 'openid,') as string, ',') }); - const oauthToken = await oAuthObj.code.getToken(req.originalUrl, options); + const queryParameters = req.originalUrl.split('?').splice(1, 1).join(''); + + const oauthToken = await oAuthObj.code.getToken(`${redirectUri}?${queryParameters}`, options); if (oauthToken === undefined) { const errorResponse = new ResponseHelper.ResponseError('Unable to get access tokens!', undefined, 404); @@ -1693,9 +1702,21 @@ class App { }); } + + // Read the index file and replace the path placeholder + const editorUiPath = require.resolve('n8n-editor-ui'); + const filePath = pathJoin(pathDirname(editorUiPath), 'dist', 'index.html'); + let readIndexFile = readFileSync(filePath, 'utf8'); + const n8nPath = config.get('path'); + readIndexFile = readIndexFile.replace(/\/%BASE_PATH%\//g, n8nPath); + + // Serve the altered index.html file separately + this.app.get(`/index.html`, async (req: express.Request, res: express.Response) => { + res.send(readIndexFile); + }); + // Serve the website const startTime = (new Date()).toUTCString(); - const editorUiPath = require.resolve('n8n-editor-ui'); this.app.use('/', express.static(pathJoin(pathDirname(editorUiPath), 'dist'), { index: 'index.html', setHeaders: (res, path) => { diff --git a/packages/editor-ui/public/index.html b/packages/editor-ui/public/index.html index 2f2450023d..9193fda976 100644 --- a/packages/editor-ui/public/index.html +++ b/packages/editor-ui/public/index.html @@ -4,7 +4,8 @@ - + + n8n.io - Workflow Automation diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 71388c2cd3..a9cf787ef4 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -16,7 +16,7 @@ @@ -208,6 +208,8 @@ export default mixins( data () { return { aboutDialogVisible: false, + // @ts-ignore + basePath: window.BASE_PATH, isCollapsed: true, credentialNewDialogVisible: false, credentialOpenDialogVisible: false, diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts index e82b30b588..4754098c97 100644 --- a/packages/editor-ui/src/router.ts +++ b/packages/editor-ui/src/router.ts @@ -8,7 +8,8 @@ Vue.use(Router); export default new Router({ mode: 'history', - base: process.env.BASE_URL, + // @ts-ignore + base: window.BASE_PATH, routes: [ { path: '/execution/:id', diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index 80454fc9e4..0e1e5f14c4 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -38,7 +38,8 @@ export const store = new Vuex.Store({ activeWorkflows: [] as string[], activeActions: [] as string[], activeNode: null as string | null, - baseUrl: process.env.VUE_APP_URL_BASE_API ? process.env.VUE_APP_URL_BASE_API : '/', + // @ts-ignore + baseUrl: window.BASE_PATH ? window.BASE_PATH : '/', credentials: null as ICredentialsResponse[] | null, credentialTypes: null as ICredentialType[] | null, endpointWebhook: 'webhook', diff --git a/packages/editor-ui/vue.config.js b/packages/editor-ui/vue.config.js index cdcd8259f9..c5ffc5fed8 100644 --- a/packages/editor-ui/vue.config.js +++ b/packages/editor-ui/vue.config.js @@ -29,5 +29,5 @@ module.exports = { }, }, }, - publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : '/', + publicPath: process.env.VUE_APP_PUBLIC_PATH ? process.env.VUE_APP_PUBLIC_PATH : '/%BASE_PATH%/', }; diff --git a/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts b/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts index 6b57d46804..3d387e7b4d 100644 --- a/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts +++ b/packages/nodes-base/nodes/Affinity/AffinityTrigger.node.ts @@ -52,10 +52,10 @@ export class AffinityTrigger implements INodeType { options: [ { name: 'file.created', - value: 'file.deleted', + value: 'file.created', }, { - name: 'file.created', + name: 'file.deleted', value: 'file.deleted', }, { @@ -136,7 +136,7 @@ export class AffinityTrigger implements INodeType { }, { name: 'opportunity.deleted', - value: 'organization.deleted', + value: 'opportunity.deleted', }, { name: 'person.created',