UNPKG

backstrap-server

Version:

Backstrap is a lightweight framework for building APIs

828 lines (788 loc) 28.8 kB
// ================================================================== // SERVICE REGISTRATION // ================================================================== // Service Registration handles loading, processing, and validation // of endpoints and arguments. Works in tandem with endpoints.js // ================================================================== const moment = require('moment'); const base64 = require('./base64.js'); class ServiceRegistration { constructor() { this.endpoints = null; } init(e) { this.endpoints = e; } registerServiceCall(call, area, controller, verb, version, args, authRequired, description) { return new Promise((resolve, reject) => { // ------------------------------------------------------------------- // VALIDATE THAT WE HAVE ALL THE INFO NECESSARY TO CREATE AN ENDPOINT // ------------------------------------------------------------------- var isValid = true; var invalidArgs = []; if(call===null) { invalidArgs.push('call'); isValid = false; } if(area===null) { invalidArgs.push('area'); isValid = false; } if(controller===null) { invalidArgs.push('controller'); isValid = false; } if(verb===null) { invalidArgs.push('verb'); isValid = false; } if(version===null) { invalidArgs.push('version'); isValid = false; } if(authRequired===null) { authRequired = false; } // MISSING REQUIRED ARGS TO CREATE AN ENDPOINT if(isValid===false) { var errorObj = new ErrorObj(500, 'sr0001', __filename, 'registerServiceCall', 'invalid args', 'Invalid args', invalidArgs ); reject(errorObj); } else { verb = verb.toUpperCase(); // CHECK IF WE HAVE ALREADY CREATED THIS ENDPOINT this.serviceCallExists(call, area, controller, verb, version) .then(() => { var errorObj = new ErrorObj(500, 'sr0002', __filename, 'registerServiceCall', 'duplicate service call' ); reject(errorObj); }) .catch((err) => { if(err != null && (err.message==='no matching controller found' || err.message==='no matching area found' || err.message==='no matching method found')) { var areaNames = Object.getOwnPropertyNames(this.endpoints.areas); // NO AREA IN ENDPOINTS FILE if(areaNames.indexOf(area) === -1) { this.endpoints.areas[area] = [{ 'name': controller, 'version': version, 'methods': [ { 'verb': verb, 'call': call, 'desc': description, 'authRequired': authRequired, 'args': args, 'isUserCreated': true } ] }]; } // FOUND THE AREA, CHECK FOR CONTROLLER/VERSION else { var controllers = this.endpoints.areas[area]; var foundController = false; for(var cIdx = 0; cIdx < controllers.length; cIdx++) { if(controllers[cIdx].name.toLowerCase() === controller.toLowerCase() && controllers[cIdx].version === version) { // FOUND THE CONTROLLER, ADD THE METHOD controllers[cIdx].methods.push({ 'verb': verb, 'call': call, 'desc': description, 'authRequired': authRequired, 'args': args, 'isUserCreated': true }); foundController = true break; } } if(!foundController) { // GOT THE AREA, BUT NO CONTROLLER // ADD A CONTROLLER AND THE METHOD var controllerObj = { 'name': controller, 'version': version, 'methods': [ { 'verb': verb, 'call': call, 'desc': description, 'authRequired': authRequired, 'args': args, 'isUserCreated': true } ] }; this.endpoints.areas[area].push(controllerObj); } } // WRITE TO FILE this.endpoints.save(true) .then((save_res) => { resolve(save_res); }) .catch((err) => { if(err !== undefined && err !== null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'registerServiceCall')); } else { var errorObj = new ErrorObj(500, 'sr1002', __filename, 'registerServiceCall', 'error saving endpoints config', 'Error saving endpoints config', err ); reject(errorObj); } }); } else { if(err != null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'registerServiceCall')); } else { let errorObj = new ErrorObj(500, 'sr1003', __filename, 'registerServiceCall', 'error registering service call', 'Error registering service call', err ); reject(errorObj); } } }); } }); } updateServiceCall(call, area, controller, verb, version, args, authRequired, description) { return new Promise((resolve, reject) => { this.serviceCallExists(call, area, controller, verb, version) .then(() => { var updatedMethod = false; var controllers = this.endpoints.areas[area]; for(var cIdx = 0; cIdx < controllers.length; cIdx++) { if(controllers[cIdx].name.toLowerCase() === controller.toLowerCase() && controllers[cIdx].version === version) { // FOUND THE CONTROLLER, DELETE THE METHOD for(var mIdx = 0; mIdx < controllers[cIdx].methods.length; mIdx++) { var method = controllers[cIdx].methods[mIdx]; if(method.verb.toLowerCase() === verb.toLowerCase() && method.call.toLowerCase() === call.toLowerCase()) { if(args !== undefined) { method.args = args; } if(authRequired !== undefined) { method.authRequired = authRequired; } if(description !== undefined) { method.description = description; } updatedMethod = true; break; } } break; } } if(updatedMethod) { // WRITE TO FILE this.endpoints.save(true) .then((save_res) => { resolve(save_res); }) .catch((err) => { if(err !== undefined && err !== null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'updateServiceCall')); } else { let errorObj = new ErrorObj(500, 'sr1004', __filename, 'updateServiceCall', 'error saving endpoints config', 'Error saving endpoints config', err ); reject(errorObj); } }); } else { var errorObj = new ErrorObj(500, 'sr0003', __filename, 'updateServiceCall', 'no service call found to update' ); reject(errorObj); } }) .catch((err) => { if(err !== undefined && err !== null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'updateServiceCall')); } else { let errorObj = new ErrorObj(500, 'sr1005', __filename, 'updateServiceCall', 'error updating endpoint', 'Error updating endpoint', err ); reject(errorObj); } }); }); } deleteServiceCall(call, area, controller, verb, version) { return new Promise((resolve, reject) => { this.serviceCallExists(call, area, controller, verb, version) .then(() => { var controllers = this.endpoints.areas[area]; for(var cIdx = 0; cIdx < controllers.length; cIdx++) { if(controllers[cIdx].name.toLowerCase() === controller.toLowerCase() && controllers[cIdx].version === version) { // FOUND THE CONTROLLER, UPDATE THE METHOD for(var mIdx = 0; mIdx < controllers[cIdx].methods.length; mIdx++) { var method = controllers[cIdx].methods[mIdx]; if(method.verb.toLowerCase() === verb.toLowerCase() && method.call.toLowerCase() === call.toLowerCase()) { controllerIdx = cIdx; methodIdx = mIdx; break; } } break; } } if(controllerIdx !== -1 && methodIdx !== -1) { this.endpoints.areas[area][controllerIdx].methods.splice(methodIdx, 1); // WRITE TO FILE this.endpoints.save(true) .then((save_res) => { resolve(save_res); }) .catch((err) => { if(err !== undefined && err !== null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'deleteServiceCall')); } else { let errorObj = new ErrorObj(500, 'sr1006', __filename, 'deleteServiceCall', 'error deleting endpoint', 'Error deleting endpoint', err ); reject(errorObj); } }); } else { let errorObj = new ErrorObj(500, 'sr0004', __filename, 'deleteServiceCall', 'no service call found to delete' ); reject(errorObj); } }) .catch((err) => { if(err !== undefined && err !== null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'deleteServiceCall')); } else { var errorObj = new ErrorObj(500, 'sr1007', __filename, 'deleteServiceCall', 'error deleting endpoint', 'Error deleting endpoint', err ); reject(errorObj); } }); }); } getServiceCall(call, area, controller, verb, version) { return new Promise((resolve, reject) => { var serviceCallObj = { 'object_type':'webServiceCallDescriptor' }; if(this.endpoints.areas.hasOwnProperty(area) && this.endpoints.areas[area]!==null) { // FOUND THE AREA var foundController = false; var activeController = null; if(version !== undefined && version !== null) { for(var cIdx = 0; cIdx < this.endpoints.areas[area].length; cIdx++) { var cntrl = this.endpoints.areas[area][cIdx]; if(cntrl.name.toLowerCase()===controller.toLowerCase() && cntrl.version===version) { foundController = true; activeController = cntrl; serviceCallObj.area = area.toLowerCase(); break; } } } else { var greatestVersionWithCall = '0.0.0'; for(var cIdx = 0; cIdx < this.endpoints.areas[area].length; cIdx++) { var cntrl = this.endpoints.areas[area][cIdx]; // THIS CONTROLLER HAS THE CORRECT NAME. SEE IF IT HAS THE METHOD if(cntrl.name.toLowerCase() === controller.toLowerCase()) { if(cntrl.hasOwnProperty('methods') && cntrl.methods.length > 0) { methodloop: for(var mIdx = 0; mIdx < cntrl.methods.length; mIdx++) { var mthd = cntrl.methods[mIdx]; // THIS CONTROLLER HAS THE CORRECT METHOD. MARK THE VERSION // SO WE CAN SEE IF THIS IS THE LATEST if(mthd.verb.toLowerCase() === verb.toLowerCase() && mthd.call.toLowerCase() === call.toLowerCase()) { var compareRes = this.#compareVersionStrings(cntrl.version, greatestVersionWithCall); if(compareRes === 1) { greatestVersionWithCall = cntrl.version; foundController = true; activeController = cntrl; serviceCallObj.area = area.toLowerCase(); break methodloop; } } } } } } } if(foundController) { // FOUND THE CONTROLLER AND VERSION serviceCallObj.controller = activeController.name; serviceCallObj.version = activeController.version; if(activeController.hasOwnProperty('methods') && activeController.methods.length > 0) { var foundMethod = false; var activeMethod = null; for(var mIdx = 0; mIdx < activeController.methods.length; mIdx++) { var mthd = activeController.methods[mIdx]; if(mthd.verb.toLowerCase()===verb.toLowerCase() && mthd.call.toLowerCase()===call.toLowerCase()) { if(mthd.isUserCreated === undefined || mthd.isUserCreated === null) { mthd.isUserCreated = false; } // FOUND METHOD WITH MATCHING VERB AND CALL activeMethod = mthd; foundMethod = true; serviceCallObj.verb = activeMethod.verb; serviceCallObj.call = activeMethod.call; serviceCallObj.description = activeMethod.desc; serviceCallObj.args = activeMethod.args; serviceCallObj.authRequired = activeMethod.authRequired; serviceCallObj.isUserCreated = activeMethod.isUserCreated; break; } } if(foundMethod) { resolve(serviceCallObj); } else { // COULDN'T FIND IT let errorObj = new ErrorObj(400, 'sr0005', __filename, 'getServiceCall', 'no matching method found' ); reject(errorObj); } } else { // COULDN'T FIND IT let errorObj = new ErrorObj(400, 'sr0006', __filename, 'getServiceCall', 'no matching method found' ); reject(errorObj); } } else { let errorObj = new ErrorObj(400, 'sr0007', __filename, 'getServiceCall', 'no matching controller found' ); reject(errorObj); } } else { // COULDN'T FIND IT let errorObj = new ErrorObj(500, 'sr0008', __filename, 'getServiceCall', 'no matching area found' ); reject(errorObj); } }); } serviceCallExists(call, area, controller, verb, version) { return new Promise((resolve, reject) => { this.getServiceCall(call, area, controller, verb, version) .then((sc_res) => { resolve(sc_res); }) .catch((gsc_err) => { if(gsc_err !== undefined && gsc_err !== null && typeof(gsc_err.AddToError) === 'function') { reject(gsc_err.AddToError(__filename, 'serviceCallExists')); } else { let errorObj = new ErrorObj(500, 'sr1008', __filename, 'serviceCallExists', 'error finding service call', 'Error finding service call', gsc_err ); reject(errorObj); } }); }); } validateArguments(call, area, controller, verb, version, inputArgs) { return new Promise((resolve, reject) => { this.getServiceCall(call, area, controller, verb, version) .then((get_res) => { if(get_res.args!==null && get_res.args.length > 0) { var isValid = true; var invalidArgs = []; for(var argIdx = 0; argIdx < get_res.args.length; argIdx++) { var arg = get_res.args[argIdx]; if(arg.isRequired===null) { arg.isRequired = false; } if(arg.isRequired===true && (inputArgs[arg.name]===null || inputArgs[arg.name]==='')) { isValid = false; invalidArgs.push(arg.name); break; } if(arg.type!==null) { if((inputArgs[arg.name]===undefined || inputArgs[arg.name]===null) && !arg.isRequired) { continue; } if(arg.type==='string') { if(typeof(inputArgs[arg.name])!=='string') { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='number') { if(isNaN(inputArgs[arg.name])) { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='array') { if(typeof(inputArgs[arg.name])!=='object') { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='object') { if(typeof(inputArgs[arg.name])!=='object') { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='boolean') { if(verb.toLowerCase() === 'get') { inputArgs[arg.name] = inputArgs[arg.name].toLowerCase() == 'true' ? true : false; } if(typeof(inputArgs[arg.name])!=='boolean') { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='symbol') { if(typeof(inputArgs[arg.name])!=='symbol') { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='date') { if(typeof(inputArgs[arg.name])==='string') { var dateToStore = this.#formatDateForStorage(inputArgs[arg.name]); if(dateToStore === 'Invalid Date') { isValid = false; invalidArgs.push(arg.name); break; } else { inputArgs[arg.name] = dateToStore; } } else { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type==='file') { // VERIFY THAT THIS IS A STRING CONTAINING VALID BASE64 DATA if(typeof(inputArgs[arg.name]) === 'string') { var stringData = inputArgs[arg.name]; if(!base64.validate(stringData)) { isValid = false; invalidArgs.push(arg.name); break; } } else { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type === 'filestreamarray') { // THIS IS A MULTER ARRAY OF FILE UPLOADS // FOR NOW JUST MAKE SURE WE HAVE AN ARRAY if(!Array.isArray(inputArgs[arg.name])) { isValid = false; invalidArgs.push(arg.name); break; } } else if(arg.type === '*') { // VALID } // I DON'T KNOW WHY YOU'D WANT TO DO THIS, BUT HERE... else if(arg.type==='undefined') { if(typeof(inputArgs[arg.name])!=='undefined') { isValid = false; invalidArgs.push(arg.name); break; } } } else { console.log( 'Could not find a type for this argument. This service may have been misformed during registration.'); isValid = false; break; } } if(isValid===true) { resolve(true); } else { var errorObj = new ErrorObj(400, 'sr0009', __filename, 'validateArguments', 'invalid args', 'Invalid args', invalidArgs ); reject(errorObj); } } else { resolve(true); } }) .catch((err) => { if(err !== undefined && err !== null && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'validateArguments')); } else { let errorObj = new ErrorObj(500, 'sr1009', __filename, 'validateArguments', 'error validating arguments', 'Error validating arguments', err ); reject(errorObj); } }); }); } getAllServiceCalls() { var serviceCalls = []; var areaNames = Object.keys(this.endpoints.areas); for(var aIdx = 0; aIdx < areaNames.length; aIdx++) { var areaName = areaNames[aIdx]; var controllerArray = this.endpoints.areas[areaNames[aIdx]]; for(var cIdx = 0; cIdx < controllerArray.length; cIdx++) { var controllerObj = controllerArray[cIdx]; var controllerName = controllerObj.name; var controllerVersion = controllerObj.version; var methodArray = controllerObj.methods; for(var mIdx = 0; mIdx < methodArray.length; mIdx++) { var methodObj = methodArray[mIdx]; if(methodObj.isUserCreated === undefined || methodObj.isUserCreated === null) { methodObj.isUserCreated = false; } var serviceCall = { 'area':areaName, 'controller':controllerName, 'version':controllerVersion, 'call':methodObj.call, 'verb':methodObj.verb, 'description':methodObj.desc, 'authRequired':methodObj.authRequired, 'args':methodObj.args, 'isUserCreated':methodObj.isUserCreated }; serviceCalls.push(serviceCall); } } } return Promise.resolve(serviceCalls); } #formatDateForStorage(dateString) { var re = /(\+|\-)\d\d:?\d\d$/; var reZ = /(Z$|\+00:?00$)/; var timezone; var isLocalTime = false; var dateToStore; if(reZ.test(dateString)) { // ZULU TIME timezone = dateString.match(reZ)[0]; } else if(re.test(dateString)) { // LOCAL TIME timezone = dateString.match(re)[0]; isLocalTime = true; } else { // NO TIMEZONE, ASSUME ZULU dateString += 'Z'; } var isValid = false; if(moment(dateString, 'YYYY-MM-DDZ', true).isValid()) { isValid = true; if(isLocalTime) { var dsNoTimezone = dateString.substring(0, dateString.indexOf(timezone)); var dts = dsNoTimezone+'T00:00:00.000'+timezone; dateToStore = dts; } else { dateToStore = moment(dateString, 'YYYY-MM-DDZ', true).utc().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); } } else if(moment(dateString, 'YYYY-MM-DDTHH:mmZ', true).isValid()) { isValid = true; if(isLocalTime) { var dsNoTimezone = dateString.substring(0, dateString.indexOf(timezone)); var dts = dsNoTimezone+':00.000'+timezone; dateToStore = dts; } else { dateToStore = moment(dateString, 'YYYY-MM-DDTHH:mmZ', true).utc().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); } } else if(moment(dateString, 'YYYY-MM-DDTHH:mm:ssZ', true).isValid()) { isValid = true; if(isLocalTime) { var dsNoTimezone = dateString.substring(0, dateString.indexOf(timezone)); var dts = dsNoTimezone+'.000'+timezone; dateToStore = dts; } else { dateToStore = moment(dateString, 'YYYY-MM-DDTHH:mm:ssZ', true).utc().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); } } else if(moment(dateString, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true).isValid()) { isValid = true; if(isLocalTime) { dateToStore = dateString; } else { dateToStore = moment(dateString, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true).utc().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); } } else { isValid = false; } if(isValid) { return dateToStore; } else { return "Invalid Date"; } } #compareVersionStrings(a, b) { var aObj = {}; var pIdx = a.indexOf('.'); aObj.major = parseInt(a.substring(0, pIdx)); var tempInput = a.substring(pIdx+1); pIdx = tempInput.indexOf('.'); aObj.minor = parseInt(tempInput.substring(0, pIdx)); tempInput = tempInput.substring(pIdx+1); aObj.bug = parseInt(tempInput); var bObj = {}; pIdx = b.indexOf('.'); bObj.major = parseInt(b.substring(0, pIdx)); tempInput = b.substring(pIdx+1); pIdx = tempInput.indexOf('.'); bObj.minor = parseInt(tempInput.substring(0, pIdx)); tempInput = tempInput.substring(pIdx+1); bObj.bug = parseInt(tempInput); if(aObj.major > bObj.major) { return 1; } else if(aObj.major < bObj.major) { return -1; } else { if(aObj.minor > bObj.minor) { return 1; } else if(aObj.minor < bObj.minor) { return -1; } else { if(aObj.bug > bObj.bug) { return 1; } else if(aObj.bug < bObj.bug) { return -1; } else { return 0; } } } } } var instance = new ServiceRegistration(); module.exports = instance;