diff --git a/README.md b/README.md index 958db0c..a81e60f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ # node-red-contrib-postgres-listen A Node-RED node to listen to pg_notify + +Install +------- + +Run the following command in the root directory of your Node-RED install + + npm install node-red-contrib-postgres-listen + + +Overview +------- + +This add-on, allows to listen to PostgreSQL [pg_notify](https://www.postgresql.org/docs/9.0/static/sql-notify.html) mechanism. + +The node takes two parameters : + +- postgresdb : The PostgreSQL connection configuration +- channel : The channel name specified in the pg_notify command + +PostgreSQL sample code +---------------------- + +1. Create a base table: + + CREATE TABLE realtime + ( + id INTEGER DEFAULT nextval('realtime_id_seq'::regclass) NOT NULL, + title CHARACTER VARYING(128), + PRIMARY KEY (id) + ); + +2. Create a trigger on the table: + + CREATE TRIGGER "updated_realtime_trigger" + BEFORE INSERT OR DELETE OR UPDATE ON realtime + FOR EACH ROW + EXECUTE PROCEDURE notify_realtime() + +3. Create a trigger function: + + CREATE FUNCTION public.notify_realtime() + RETURNS trigger + LANGUAGE 'plpgsql' + COST 100.0 + VOLATILE NOT LEAKPROOF + AS $BODY$ + + BEGIN + PERFORM pg_notify('addedrecord', '' || row_to_json(NEW)); + RETURN NEW; + END; + $BODY$; + +Result +------ + +The node will produce a message like that : + + {"name":"notification","length":47,"processId":16147,"channel":"addedrecord","payload":{"id":2,"title":"plopcsd"}} + +All fields are generated by Postgres with *payload* being the content of the table row. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..79dd9fd --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "node-red-contrib-postgres-listen", + "version": "0.1.0", + "description": "A Node-RED node to listen to pg_notify", + "dependencies": { + "pg": "6.1.5" + }, + "repository": { + "type": "git", + "url": "https://github.com/arkancrow/node-red-contrib-postgres-listen" + }, + "license": "Apache-2.0", + "keywords": [ + "node-red", + "postgres", + "postgresql", + "pg_notify" + ], + "node-red": { + "nodes": { + "PostgreSQLListen": "pglisten.js" + } + }, + "author": { + "name": "Vincent Schoonenburg", + "email": "arkancrow@gmail.com" + } +} \ No newline at end of file diff --git a/pglisten.html b/pglisten.html new file mode 100644 index 0000000..c6afbe1 --- /dev/null +++ b/pglisten.html @@ -0,0 +1,113 @@ + + + + + + + + + + + + diff --git a/pglisten.js b/pglisten.js new file mode 100644 index 0000000..1102413 --- /dev/null +++ b/pglisten.js @@ -0,0 +1,123 @@ +/** + * Copyright 2017 Vincent Schoonenburg. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +module.exports = function(RED) { + + var pg = require('pg'); + + function Notify(n) { + RED.nodes.createNode(this,n); + + this.postgresdb = n.postgresdb; + this.postgresConfig = RED.nodes.getNode(this.postgresdb); + this.channel = n.channel; + console.log(this.channel); + var node = this; + var clientdb = null; + this.status({fill:"red",shape:"ring",text:"disconnected"}); + + if(this.postgresConfig) { + try { + var config = {}; + + if (node.postgresConfig.connectionString) { + config = node.postgresConfig.connectionString + } else { + if (node.postgresConfig.user) { config.user = node.postgresConfig.user; } + if (node.postgresConfig.password) { config.password = node.postgresConfig.password; } + if (node.postgresConfig.hostname) { config.host = node.postgresConfig.hostname; } + if (node.postgresConfig.port) { config.port = node.postgresConfig.port; } + if (node.postgresConfig.db) { config.database = node.postgresConfig.db; } + config.ssl = node.postgresConfig.ssl; + } + clientdb = new pg.Client(config); + + clientdb.connect(function(err) { + try { + + if(err) { + console.log(err); + node.error(err); + } else { + console.log("Connected"); + node.status({fill:"green",shape:"dot",text:"connected"}); + clientdb.on('notification', function(msg) { + console.log("Notification received"); + msg.payload = JSON.parse(msg.payload); + node.log(JSON.stringify(msg)); + node.send(msg); + }); + var query = "LISTEN " + node.channel; + clientdb.query(query); + console.log("Listening to :" + node.channel); + } + } catch (error) { + node.error(error); + } + }); + + } catch (err) { + node.error(err); + } + + } else { + this.error("missing postgres configuration"); + } + + this.on("close", function() { + if(node.clientdb) node.clientdb.end(); + }); + } + + + function PostgresDatabaseNode(n) { + RED.nodes.createNode(this,n); + this.hostname = n.hostname; + this.port = n.port; + this.db = n.db; + this.ssl = n.ssl; + this.connectionString = n.connectionstring; + + var credentials = this.credentials; + if (credentials) { + this.user = credentials.user; + this.password = credentials.password; + } + } + + function PostgresArrayNode(n) { + RED.nodes.createNode(this,n); + + try { + this.columns = JSON.parse(n.columns); + } catch (e) { + node.error(e.message); + this.columns = []; + } + } + try { + RED.nodes.registerType("postgresdb",PostgresDatabaseNode,{ + credentials: { + user: {type:"text"}, + password: {type: "password"} + } + }); + RED.nodes.registerType("postgresarray",PostgresArrayNode); + } catch (e) { + + } + + RED.nodes.registerType("PG Listen",Notify); +};