diff --git a/README.md b/README.md index 603ede0..f597e58 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ # node-red-contrib-firebase-admin + A node-red module that wraps the server-side admin SDK of firebase, firestore, et.c. + +# Overview +The main difference of this module and all other firebase/store modules for node-red is +that it takes a service account credential token as configuration. + +This means that the nodes can run outside of the normal security rules, in admin mode, which is usefule when running on the back-end. + +# Realtime Database (rtdb) Nodes + +## rtdb-get +Get data from a path in the rtdb database + +input: {payload: {path: 'foo/bar'}} + +output: + +## rtdb-set +Set data at a path in the rtdb database. Use 'on' snapshot so will fire everytime the data at the path changes and so drive flow executin from that point. + +input: {payload: {path: 'foo/bar'}, {some: 'object', foo: 17}} + +## rtdb-query +Set up a reactive wuery for a path in the rtdb database. + +input: {payload: {path: 'foo/bar', queries:[], on: 'value}} + +on: 'value' (can also be 'child_added', 'child_removed', 'child_changed', 'child_moved'). +If an 'on' property is missing, on: 'value' is assumed as default + +Where each query is an object that can look like either of the following examples; + +- {startAt: 'foo'} +- {endAt: 'bar'} +- {equalTo: 'quux'} +- {orderBy: 'child', value: 'height'} (can also be 'key' or 'value) +- {limitTo: 'last', value: 3} (can also be 'first') + +output: [an array of results for the query] + + +# Firestore nodes + +TBD + +# Storage nodes + +TBD + +# Auth nodes + +TBD + diff --git a/firebase-config.html b/firebase-config.html new file mode 100644 index 0000000..3a41290 --- /dev/null +++ b/firebase-config.html @@ -0,0 +1,28 @@ + + + \ No newline at end of file diff --git a/firebase-config.js b/firebase-config.js new file mode 100644 index 0000000..01d282b --- /dev/null +++ b/firebase-config.js @@ -0,0 +1,23 @@ +const _admin = require('firebase-admin') +let init = false + +module.exports = function(RED) { + function FirebaseConfigNode(n) { + RED.nodes.createNode(this,n); + this.cred = n.cred + this.dburl = n.dburl + this.admin = _admin + if(!init){ + console.log('setting admin....') + init = true + this.credentials = JSON.parse(this.cred); + this.dburl = this.dburl + console.log('*** parsed firebase credentials: '+this.credentials.type+', project-id: '+this.credentials.project_id) + _admin.initializeApp({ + credential: _admin.credential.cert(this.credentials), + databaseURL: this.dburl + }); + } + } + RED.nodes.registerType("firebase-config", FirebaseConfigNode); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7637c2 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "node-red-contrib-firebase-admin", + "version": "1.0.0", + "description": "A node-red module that wraps the server-side admin SDK of firebase, firestore, et.c.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/psvensson/node-red-contrib-firebase-admin.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/psvensson/node-red-contrib-firebase-admin/issues" + }, + "homepage": "https://github.com/psvensson/node-red-contrib-firebase-admin#readme", + "node-red": { + "nodes": { + "rtdb-set": "rtdb-set.js", + "rtdb-push": "rtdb-push.js", + "rtdb-get": "rtdb-get.js", + "rtdb-query": "rtdb-query.js", + "firebase-config": "firebase-config.js" + } + }, + "dependencies": { + "firebase-admin": "^8.0.0" + } +} diff --git a/rtdb-get.html b/rtdb-get.html new file mode 100644 index 0000000..51a753a --- /dev/null +++ b/rtdb-get.html @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/rtdb-get.js b/rtdb-get.js new file mode 100644 index 0000000..e508c43 --- /dev/null +++ b/rtdb-get.js @@ -0,0 +1,39 @@ + + +let oldpath + +module.exports = function(RED) { + function FirebaseAdmin(config) { + RED.nodes.createNode(this, config); + var node = this; + + console.log('config is..') + console.dir(config) + if(config.cred){ + let c = RED.nodes.getNode(config.cred) + this.admin = c.admin + } + + const cb = (res)=>{ + console.log('firebase get result '+res) + console.dir(res) + let val = res.val() + console.log('val='+val) + node.send({payload:val}) + } + + node.on('input', function(msg) { + if(msg && msg.payload){ + const path = msg.payload.path + if(oldpath){ + this.admin.database().ref(oldpath).off('value', cb) + } + this.admin.database().ref(path).on('value', cb) + oldpath = path + } + }.bind(this)); + + + } + RED.nodes.registerType("rtdb-get", FirebaseAdmin); +} \ No newline at end of file diff --git a/rtdb-push.html b/rtdb-push.html new file mode 100644 index 0000000..3312143 --- /dev/null +++ b/rtdb-push.html @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/rtdb-push.js b/rtdb-push.js new file mode 100644 index 0000000..86844bf --- /dev/null +++ b/rtdb-push.js @@ -0,0 +1,32 @@ + + +module.exports = function(RED) { + function FirebaseAdmin(config) { + RED.nodes.createNode(this, config); + var node = this; + + console.log('config is..') + console.dir(config) + if(config.cred){ + let c = RED.nodes.getNode(config.cred) + this.admin = c.admin + } + + node.on('input', function(msg) { + if(msg && msg.payload){ + console.log('rtdb-push got input') + console.dir(msg) + const path = msg.payload.path + const obj = msg.payload.obj + console.log('storing '+obj+' at rtdb path '+path) + this.admin.database().ref(path).push(obj).then((res)=>{ + console.log('firebase set result '+res) + console.dir(res) + }) + } + }.bind(this)); + + + } + RED.nodes.registerType("rtdb-push", FirebaseAdmin); +} \ No newline at end of file diff --git a/rtdb-query.html b/rtdb-query.html new file mode 100644 index 0000000..425dc56 --- /dev/null +++ b/rtdb-query.html @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/rtdb-query.js b/rtdb-query.js new file mode 100644 index 0000000..caf083e --- /dev/null +++ b/rtdb-query.js @@ -0,0 +1,91 @@ + + +let oldpath +let oldeventtype + +module.exports = function(RED) { + function FirebaseAdmin(config) { + RED.nodes.createNode(this, config); + var node = this; + + console.log('config is..') + console.dir(config) + if(config.cred){ + let c = RED.nodes.getNode(config.cred) + this.admin = c.admin + } + + const cb = (res)=>{ + console.log('firebase get result '+res) + console.dir(res) + let val = res.val() + console.log('val='+val) + node.send({payload:val}) + } + + node.on('input', function(msg) { + if(msg && msg.payload){ + const path = msg.payload.path + const eventtype = msg.payload.on || 'value' + if(oldpath){ + this.admin.database().ref(oldpath).off(oldeventtype, cb) + } + let ref = this.admin.database().ref(path) + + // Decorate with queries + if(msg.payload.queries && msg.payload.queries.length > 0){ + console.log('found queries') + let ordered = false + msg.payload.queries.forEach((query)=>{ + console.dir(query) + if(query.orderBy){ + ordered = true + console.log('setting explicit orderBy') + if(query.orderBy === 'value'){ + ref = ref.orderByValue() + } else if(query.orderBy === 'key'){ + ref = ref.orderByKey() + } else if(query.orderBy === 'child'){ + ref = ref.orderByChild(query.value) + } + } + if(query.startAt){ + console.log('startAt '+query.startAt) + ref = ref.startAt(query.startAt) + } + if(query.endAt){ + console.log('endAt '+query.endAt) + ref = ref.endAt(query.endAt) + } + if(query.equalTo){ + console.log('equalTo '+query.equalTo) + ref = ref.equalTo(query.equalTo) + } + if(query.limitTo){ + console.log('limitTo '+query.limitTo+' -> '+query.value) + if(query.limitTo === 'first'){ + ref = ref.limitToFirst(query.value) + } else if(query.limitTo === 'last'){ + ref = ref.limitToLast(query.value) + } + } + }) + if(!ordered) { + console.log('setting implicit orderBy') + ref = ref.orderByValue() + } + } + + console.log('finished rtdb query is') + console.dir(ref.queryParams_) + + ref.on(eventtype, cb) + oldpath = path + oldeventtype = eventtype + } + }.bind(this)); + + + } + RED.nodes.registerType("rtdb-query", FirebaseAdmin); +} \ No newline at end of file diff --git a/rtdb-set.html b/rtdb-set.html new file mode 100644 index 0000000..c5f32b6 --- /dev/null +++ b/rtdb-set.html @@ -0,0 +1,50 @@ + + + + + \ No newline at end of file diff --git a/rtdb-set.js b/rtdb-set.js new file mode 100644 index 0000000..7e94174 --- /dev/null +++ b/rtdb-set.js @@ -0,0 +1,32 @@ + + +module.exports = function(RED) { + function FirebaseAdmin(config) { + RED.nodes.createNode(this, config); + var node = this; + + console.log('config is..') + console.dir(config) + if(config.cred){ + let c = RED.nodes.getNode(config.cred) + this.admin = c.admin + } + + node.on('input', function(msg) { + if(msg && msg.payload){ + console.log('rtdb-set got input') + console.dir(msg) + const path = msg.payload.path + const obj = msg.payload.obj + console.log('storing '+obj+' at rtdb path '+path) + this.admin.database().ref(path).set(obj).then((res)=>{ + console.log('firebase set result '+res) + console.dir(res) + }) + } + }.bind(this)); + + + } + RED.nodes.registerType("rtdb-set", FirebaseAdmin); +} \ No newline at end of file