mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Separate library api and runtime components
This commit is contained in:
		| @@ -86,10 +86,12 @@ module.exports = { | ||||
|  | ||||
|             // Library | ||||
|             var library = require("./library"); | ||||
|             library.init(editorApp,runtime); | ||||
|             editorApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,apiUtil.errorHandler); | ||||
|             library.init(editorApp,runtimeAPI); | ||||
|  | ||||
|             editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler); | ||||
|             editorApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,apiUtil.errorHandler); | ||||
|             editorApp.get(/library\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry); | ||||
|             editorApp.post(/library\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry); | ||||
|  | ||||
|  | ||||
|             // Credentials | ||||
|             var credentials = require("./credentials"); | ||||
|   | ||||
| @@ -13,149 +13,71 @@ | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| var apiUtils = require("../util"); | ||||
| var fs = require('fs'); | ||||
| var fspath = require('path'); | ||||
| var when = require('when'); | ||||
|  | ||||
| var redApp = null; | ||||
| var storage; | ||||
| var log; | ||||
| var redNodes; | ||||
| var needsPermission = require("../auth").needsPermission; | ||||
|  | ||||
| function createLibrary(type) { | ||||
|     if (redApp) { | ||||
|         redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),needsPermission("library.read"),function(req,res) { | ||||
|             var path = req.params[1]||""; | ||||
|             storage.getLibraryEntry(type,path).then(function(result) { | ||||
|                 log.audit({event: "library.get",type:type},req); | ||||
|                 if (typeof result === "string") { | ||||
|                     res.writeHead(200, {'Content-Type': 'text/plain'}); | ||||
|                     res.write(result); | ||||
|                     res.end(); | ||||
|                 } else { | ||||
|                     res.json(result); | ||||
|                 } | ||||
|             }).catch(function(err) { | ||||
|                 if (err) { | ||||
|                     log.warn(log._("api.library.error-load-entry",{path:path,message:err.toString()})); | ||||
|                     if (err.code === 'forbidden') { | ||||
|                         log.audit({event: "library.get",type:type,error:"forbidden"},req); | ||||
|                         res.status(403).end(); | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 log.audit({event: "library.get",type:type,error:"not_found"},req); | ||||
|                 res.status(404).end(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         redApp.post(new RegExp("/library/"+type+"\/(.*)"),needsPermission("library.write"),function(req,res) { | ||||
|             var path = req.params[0]; | ||||
|             var meta = req.body; | ||||
|             var text = meta.text; | ||||
|             delete meta.text; | ||||
|  | ||||
|             storage.saveLibraryEntry(type,path,meta,text).then(function() { | ||||
|                 log.audit({event: "library.set",type:type},req); | ||||
|                 res.status(204).end(); | ||||
|             }).catch(function(err) { | ||||
|                 log.warn(log._("api.library.error-save-entry",{path:path,message:err.toString()})); | ||||
|                     if (err.code === 'forbidden') { | ||||
|                     log.audit({event: "library.set",type:type,error:"forbidden"},req); | ||||
|                     res.status(403).end(); | ||||
|                     return; | ||||
|                 } | ||||
|                 log.audit({event: "library.set",type:type,error:"unexpected_error",message:err.toString()},req); | ||||
|                 res.status(500).json({error:"unexpected_error", message:err.toString()}); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| var runtimeAPI; | ||||
|  | ||||
| module.exports = { | ||||
|     init: function(app,runtime) { | ||||
|         redApp = app; | ||||
|         log = runtime.log; | ||||
|         storage = runtime.storage; | ||||
|         redNodes = runtime.nodes; | ||||
|     init: function(app,_runtimeAPI) { | ||||
|         runtimeAPI = _runtimeAPI; | ||||
|     }, | ||||
|     register: createLibrary, | ||||
|  | ||||
|     getAll: function(req,res) { | ||||
|         storage.getAllFlows().then(function(flows) { | ||||
|             log.audit({event: "library.get.all",type:"flow"},req); | ||||
|             var examples = redNodes.getNodeExampleFlows(); | ||||
|             if (examples) { | ||||
|                 flows.d = flows.d||{}; | ||||
|                 flows.d._examples_ = redNodes.getNodeExampleFlows(); | ||||
|             } | ||||
|             res.json(flows); | ||||
|         var opts = { | ||||
|             user: req.user, | ||||
|             type: 'flows' | ||||
|         } | ||||
|         runtimeAPI.library.getEntries(opts).then(function(result) { | ||||
|             res.json(result); | ||||
|         }).catch(function(err) { | ||||
|             apiUtils.rejectHandler(req,res,err); | ||||
|         }); | ||||
|     }, | ||||
|     get: function(req,res) { | ||||
|         if (req.params[0].indexOf("_examples_/") === 0) { | ||||
|             var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(req.params[0]); | ||||
|             if (m) { | ||||
|                 var module = m[1]; | ||||
|                 var path = m[2]; | ||||
|                 var fullPath = redNodes.getNodeExampleFlowPath(module,path); | ||||
|                 if (fullPath) { | ||||
|                     try { | ||||
|                         fs.statSync(fullPath); | ||||
|                         log.audit({event: "library.get",type:"flow",path:req.params[0]},req); | ||||
|                         return res.sendFile(fullPath,{ | ||||
|                             headers:{ | ||||
|                                 'Content-Type': 'application/json' | ||||
|                             } | ||||
|                         }) | ||||
|                     } catch(err) { | ||||
|                         console.log(err); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             // IF we get here, we didn't find the file | ||||
|             log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req); | ||||
|             return res.status(404).end(); | ||||
|         } else { | ||||
|             storage.getFlow(req.params[0]).then(function(data) { | ||||
|                 // data is already a JSON string | ||||
|                 log.audit({event: "library.get",type:"flow",path:req.params[0]},req); | ||||
|                 res.set('Content-Type', 'application/json'); | ||||
|                 res.send(data); | ||||
|             }).catch(function(err) { | ||||
|                 if (err) { | ||||
|                     log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()})); | ||||
|                     if (err.code === 'forbidden') { | ||||
|                         log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req); | ||||
|                         res.status(403).end(); | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req); | ||||
|                 res.status(404).end(); | ||||
|             }); | ||||
|     getEntry: function(req,res) { | ||||
|         var opts = { | ||||
|             user: req.user, | ||||
|             type: req.params[0], | ||||
|             path: req.params[1]||"" | ||||
|         } | ||||
|         runtimeAPI.library.getEntry(opts).then(function(result) { | ||||
|             if (typeof result === "string") { | ||||
|                 if (opts.type === 'flows') { | ||||
|                     res.writeHead(200, {'Content-Type': 'application/json'}); | ||||
|                 } else { | ||||
|                     res.writeHead(200, {'Content-Type': 'text/plain'}); | ||||
|                 } | ||||
|                 res.write(result); | ||||
|                 res.end(); | ||||
|             } else { | ||||
|                 res.json(result); | ||||
|             } | ||||
|         }).catch(function(err) { | ||||
|             apiUtils.rejectHandler(req,res,err); | ||||
|         }); | ||||
|     }, | ||||
|     post: function(req,res) { | ||||
|         // if (req.params[0].indexOf("_examples_/") === 0) { | ||||
|         //     log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:"forbidden"})); | ||||
|         //     log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req); | ||||
|         //     return res.status(403).send({error:"unexpected_error", message:"forbidden"}); | ||||
|         // } | ||||
|         var flow = JSON.stringify(req.body); | ||||
|         storage.saveFlow(req.params[0],flow).then(function() { | ||||
|             log.audit({event: "library.set",type:"flow",path:req.params[0]},req); | ||||
|     saveEntry: function(req,res) { | ||||
|         var opts = { | ||||
|             user: req.user, | ||||
|             type: req.params[0], | ||||
|             path: req.params[1]||"" | ||||
|         } | ||||
|         // TODO: horrible inconsistencies between flows and all other types | ||||
|         if (opts.type === "flows") { | ||||
|             opts.meta = {}; | ||||
|             opts.body = JSON.stringify(req.body); | ||||
|         } else { | ||||
|             opts.meta = req.body; | ||||
|             opts.body = opts.meta.text; | ||||
|             delete opts.meta.text; | ||||
|         } | ||||
|         runtimeAPI.library.saveEntry(opts).then(function(result) { | ||||
|             res.status(204).end(); | ||||
|         }).catch(function(err) { | ||||
|             log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:err.toString()})); | ||||
|             if (err.code === 'forbidden') { | ||||
|                 log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req); | ||||
|                 res.status(403).end(); | ||||
|                 return; | ||||
|             } | ||||
|             log.audit({event: "library.set",type:"flow",path:req.params[0],error:"unexpected_error",message:err.toString()},req); | ||||
|             res.status(500).send({error:"unexpected_error", message:err.toString()}); | ||||
|             apiUtils.rejectHandler(req,res,err); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -223,7 +223,7 @@ var api = module.exports = { | ||||
|     */ | ||||
|     getNodeCredentials: function(opts) { | ||||
|         return new Promise(function(resolve,reject) { | ||||
|             log.audit({event: "credentials.get",type:opts.type,id:opts.id}); | ||||
|             runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id}); | ||||
|             var credentials = runtime.nodes.getCredentials(opts.id); | ||||
|             if (!credentials) { | ||||
|                 return resolve({}); | ||||
|   | ||||
| @@ -30,6 +30,7 @@ var api = module.exports = { | ||||
|         api.flows.init(runtime); | ||||
|         api.nodes.init(runtime); | ||||
|         api.settings.init(runtime); | ||||
|         api.library.init(runtime); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -15,21 +15,107 @@ | ||||
|  **/ | ||||
|  | ||||
| /** | ||||
|  * @module red/library | ||||
|  * @namespace RED.library | ||||
|  */ | ||||
|  | ||||
| module.exports = { | ||||
| var runtime; | ||||
|  | ||||
| var api = module.exports = { | ||||
|     init: function(_runtime) { | ||||
|         runtime = _runtime; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|     * Does something | ||||
|     * Gets an entry from the library. | ||||
|     * @param {Object} opts | ||||
|     * @param {User} opts.user - the user calling the api | ||||
|     * @param {String} opts.type - the type of entry | ||||
|     * @param {String} opts.path - the path of the entry | ||||
|     * @return {Promise<String|Object>} - resolves when complete | ||||
|     * @memberof RED.library | ||||
|     */ | ||||
|     setEnty: function() {}, | ||||
|     getEntry: function(opts) { | ||||
|         return new Promise(function(resolve,reject) { | ||||
|             runtime.library.getEntry(opts.type,opts.path).then(function(result) { | ||||
|                 runtime.log.audit({event: "library.get",type:opts.type,path:opts.path}); | ||||
|                 return resolve(result); | ||||
|             }).catch(function(err) { | ||||
|                 if (err) { | ||||
|                     runtime.log.warn(runtime.log._("api.library.error-load-entry",{path:opts.path,message:err.toString()})); | ||||
|                     if (err.code === 'forbidden') { | ||||
|                         err.status = 403; | ||||
|                         return reject(err); | ||||
|                     } else if (err.code === "not_found") { | ||||
|                         err.status = 404; | ||||
|                     } else { | ||||
|                         err.status = 400; | ||||
|                     } | ||||
|                     runtime.log.audit({event: "library.get",type:opts.type,path:opts.path,error:err.code}); | ||||
|                     return reject(err); | ||||
|                 } | ||||
|                 runtime.log.audit({event: "library.get",type:type,error:"not_found"}); | ||||
|                 var error = new Error(); | ||||
|                 error.code = "not_found"; | ||||
|                 error.status = 404; | ||||
|                 return reject(error); | ||||
|             }); | ||||
|         }) | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|     * Does something | ||||
|     * Saves an entry to the library | ||||
|     * @param {Object} opts | ||||
|     * @param {User} opts.user - the user calling the api | ||||
|     * @param {String} opts.type - the type of entry | ||||
|     * @param {String} opts.path - the path of the entry | ||||
|     * @param {Object} opts.meta - any meta data associated with the entry | ||||
|     * @param {String} opts.body - the body of the entry | ||||
|     * @return {Promise} - resolves when complete | ||||
|     * @memberof RED.library | ||||
|     */ | ||||
|     getEntry: function() {}, | ||||
|     saveEntry: function(opts) { | ||||
|         return new Promise(function(resolve,reject) { | ||||
|             runtime.library.saveEntry(opts.type,opts.path,opts.meta,opts.body).then(function() { | ||||
|                 runtime.log.audit({event: "library.set",type:opts.type,path:opts.path}); | ||||
|                 return resolve(); | ||||
|             }).catch(function(err) { | ||||
|                 runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()})); | ||||
|                     if (err.code === 'forbidden') { | ||||
|                     runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"}); | ||||
|                     err.status = 403; | ||||
|                     return reject(err); | ||||
|                 } | ||||
|                 runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()}); | ||||
|                 var error = new Error(); | ||||
|                 error.code = "not_found"; | ||||
|                 error.status = 400; | ||||
|                 return reject(error); | ||||
|             }); | ||||
|         }) | ||||
|     }, | ||||
|     /** | ||||
|     * Does something | ||||
|     * Returns a complete listing of all entries of a given type in the library. | ||||
|     * @param {Object} opts | ||||
|     * @param {User} opts.user - the user calling the api | ||||
|     * @param {String} opts.type - the type of entry | ||||
|     * @return {Promise<Object>} - the entry listing | ||||
|     * @memberof RED.library | ||||
|     */ | ||||
|     getEntries: function() {} | ||||
|     getEntries: function(opts) { | ||||
|         return new Promise(function(resolve,reject) { | ||||
|             if (opts.type !== 'flows') { | ||||
|                 return reject(new Error("API only supports flows")); | ||||
|  | ||||
|             } | ||||
|             runtime.storage.getAllFlows().then(function(flows) { | ||||
|                 runtime.log.audit({event: "library.get.all",type:"flow"}); | ||||
|                 var examples = runtime.nodes.getNodeExampleFlows(); | ||||
|                 if (examples) { | ||||
|                     flows.d = flows.d||{}; | ||||
|                     flows.d._examples_ = examples; | ||||
|                 } | ||||
|                 return resolve(flows); | ||||
|             }); | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,27 +14,12 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
| "use strict" | ||||
|  /** | ||||
|   * @namespace RED.nodes | ||||
|   */ | ||||
| /** | ||||
|  * @namespace RED.nodes | ||||
|  */ | ||||
|  | ||||
| var runtime; | ||||
|  | ||||
| function putNode(node, enabled) { | ||||
|     var promise; | ||||
|     if (!node.err && node.enabled === enabled) { | ||||
|         promise = Promise.resolve(node); | ||||
|     } else { | ||||
|         if (enabled) { | ||||
|             promise = runtime.nodes.enableNode(node.id); | ||||
|         } else { | ||||
|             promise = runtime.nodes.disableNode(node.id); | ||||
|         } | ||||
|     } | ||||
|     return promise; | ||||
| } | ||||
|  | ||||
|  | ||||
| var api = module.exports = { | ||||
|     init: function(_runtime) { | ||||
|         runtime = _runtime; | ||||
|   | ||||
| @@ -18,6 +18,7 @@ var when = require('when'); | ||||
|  | ||||
| var redNodes = require("./nodes"); | ||||
| var storage = require("./storage"); | ||||
| var library = require("./library"); | ||||
| var log = require("./log"); | ||||
| var i18n = require("./i18n"); | ||||
| var events = require("./events"); | ||||
| @@ -65,6 +66,7 @@ function init(userSettings,_adminApi) { | ||||
|         adminApi = _adminApi; | ||||
|     } | ||||
|     redNodes.init(runtime); | ||||
|     library.init(runtime); | ||||
| } | ||||
|  | ||||
| var version; | ||||
| @@ -245,6 +247,7 @@ var runtime = module.exports = { | ||||
|     storage: storage, | ||||
|     events: events, | ||||
|     nodes: redNodes, | ||||
|     library: library, | ||||
|     util: require("./util"), | ||||
|     get adminApi() { return adminApi }, | ||||
|     get nodeApp() { return nodeApp }, | ||||
|   | ||||
							
								
								
									
										100
									
								
								red/runtime/library/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								red/runtime/library/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| /** | ||||
|  * Copyright JS Foundation and other contributors, http://js.foundation | ||||
|  * | ||||
|  * 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. | ||||
|  **/ | ||||
|  | ||||
| var fs = require('fs'); | ||||
| var fspath = require('path'); | ||||
|  | ||||
| var runtime; | ||||
| var knownTypes = {}; | ||||
|  | ||||
| var storage; | ||||
|  | ||||
| function init(_runtime) { | ||||
|     runtime = _runtime; | ||||
|     storage = runtime.storage; | ||||
|     knownTypes = {}; | ||||
| } | ||||
|  | ||||
| function registerType(id,type) { | ||||
|     if (knownTypes.hasOwnProperty(type)) { | ||||
|         throw new Error(`Library type '${type}' already registerd by ${id}'`) | ||||
|     } | ||||
|     knownTypes[type] = id; | ||||
| } | ||||
|  | ||||
| function getAllEntries(type) { | ||||
|     if (!knownTypes.hasOwnProperty(type)) { | ||||
|         throw new Error(`Unknown library type '${type}'`); | ||||
|     } | ||||
| } | ||||
| function getEntry(type,path) { | ||||
|     if (type !== 'flows') { | ||||
|         if (!knownTypes.hasOwnProperty(type)) { | ||||
|             throw new Error(`Unknown library type '${type}'`); | ||||
|         } | ||||
|         return storage.getLibraryEntry(type,path); | ||||
|     } else { | ||||
|         return new Promise(function(resolve,reject) { | ||||
|             if (path.indexOf("_examples_/") === 0) { | ||||
|                 var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(path); | ||||
|                 if (m) { | ||||
|                     var module = m[1]; | ||||
|                     var entryPath = m[2]; | ||||
|                     var fullPath = runtime.nodes.getNodeExampleFlowPath(module,entryPath); | ||||
|                     if (fullPath) { | ||||
|                         try { | ||||
|                             fs.readFile(fullPath,'utf8',function(err, data) { | ||||
|                                 runtime.log.audit({event: "library.get",type:"flow",path:path}); | ||||
|                                 if (err) { | ||||
|                                     return reject(err); | ||||
|                                 } | ||||
|                                 return resolve(data); | ||||
|                             }) | ||||
|                         } catch(err) { | ||||
|                             return reject(err); | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     // IF we get here, we didn't find the file | ||||
|                     var error = new Error("not_found"); | ||||
|                     error.code = "not_found"; | ||||
|                     return reject(error); | ||||
|                 } | ||||
|             } else { | ||||
|                 resolve(storage.getFlow(path)); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| function saveEntry(type,path,meta,body) { | ||||
|     if (type !== 'flows') { | ||||
|         if (!knownTypes.hasOwnProperty(type)) { | ||||
|             throw new Error(`Unknown library type '${type}'`); | ||||
|         } | ||||
|         return storage.saveLibraryEntry(type,path,meta,body); | ||||
|     } else { | ||||
|         return storage.saveFlow(path,body); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     init: init, | ||||
|     registerType: registerType, | ||||
|     getAllEntries: getAllEntries, | ||||
|     getEntry: getEntry, | ||||
|     saveEntry: saveEntry | ||||
|  | ||||
| } | ||||
| @@ -80,7 +80,11 @@ function createNodeApi(node) { | ||||
|     copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]); | ||||
|     if (runtime.adminApi) { | ||||
|         red.comms = runtime.adminApi.comms; | ||||
|         red.library = runtime.adminApi.library; | ||||
|         red.library = { | ||||
|             register: function(type) { | ||||
|                 return runtime.library.registerType(node.id,type); | ||||
|             } | ||||
|         }; | ||||
|         red.auth = runtime.adminApi.auth; | ||||
|         red.httpAdmin = runtime.adminApi.adminApp; | ||||
|         red.httpNode = runtime.nodeApp; | ||||
| @@ -377,7 +381,7 @@ function addModule(module) { | ||||
|  | ||||
| function loadNodeHelp(node,lang) { | ||||
|     var base = path.basename(node.template); | ||||
|     var localePath = undefined; | ||||
|     var localePath; | ||||
|     if (node.module === 'node-red') { | ||||
|         var cat_dir = path.dirname(node.template); | ||||
|         var cat = path.basename(cat_dir); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user