diff --git a/base/executor.class.js b/base/executor.class.js old mode 100644 new mode 100755 index 542714d..a2541e9 --- a/base/executor.class.js +++ b/base/executor.class.js @@ -11,6 +11,8 @@ const ParameterProcessor = require('./parameterProcessor.class'); const { encrypt, decrypt } = require('../helper/encryption'); const { ENC_MODE, DEFAULT_LNG_KEY, ENC_ENABLED } = require('../helper/globalConstants'); const jwt = require('../helper/jwt'); +const _ = require("lodash"); +const multiReplace = require('string-multiple-replace'); class executor { constructor() { @@ -18,12 +20,17 @@ class executor { } async executeRequest(request) { - try { this.setResponse('UNKNOWN_ERROR'); - + + // Property finding factory + const findPropInRequest = baseHelper.deepFindPropMaker(request) + + // Find the basic variables from the incoming request // Initializng basic variables - const { lng_key: lngKey, access_token: accessToken, enc_state: encState } = request.headers; + const lngKey = findPropInRequest("lng_key") + const encState = findPropInRequest("enc_state") + const accessToken = findPropInRequest("access_token") // Decide encryption mode. And enforce enc_state to be true if encryption is Strict const { ENCRYPTION_MODE } = JSON.parse(process.env.ENCRYPTION); @@ -32,9 +39,9 @@ class executor { throw new Error(); } const encryptionState = (ENCRYPTION_MODE == ENC_MODE.STRICT || (ENCRYPTION_MODE == ENC_MODE.OPTIONAL && encState == ENC_ENABLED)); - + // Set member variables - this.setMemberVariable('encryptionState', encryptionState); + this.setMemberVariable('encryptionState', this.encryptionState); if (lngKey) this.setMemberVariable('lng_key', lngKey); // Finalize methodName including custom route @@ -80,6 +87,14 @@ class executor { actionInstance.setMemberVariable('userObj', data); } + // Parse the incoming request object and find if the metadata configs are present + // if present, group them under metadata key and make it action instance member + const metaConfig = process.env["METADATA"]; + if(metaConfig && baseHelper.isJSON(metaConfig)) { + const metadata = baseHelper.populateMetadata(request, JSON.parse(metaConfig)); + actionInstance.setMemberVariable('metadata', metadata); + } + // validate & process request parameters const parameterProcessor = new ParameterProcessor(); const params = initInstance.getParameter(); @@ -87,7 +102,7 @@ class executor { let requestData = baseHelper.parseRequestData(request, isFileExpected); // If encyption is enabled, then decrypt the request data - if (!isFileExpected && encryptionState) { + if (!isFileExpected &&this.encryptionState) { requestData = decrypt(requestData.data); if (typeof requestData === 'string') requestData = JSON.parse(requestData); @@ -111,28 +126,21 @@ class executor { // Initiate and Execute method this.responseData = await actionInstance.executeMethod(); const { responseString, responseOptions, packageName } = actionInstance.getResponseString(); - const { responseCode, responseMessage } = this.getResponse(responseString, responseOptions, packageName); - + // If encryption mode is enabled then encrypt the response data if (encryptionState) { // this.responseData = new URLSearchParams({data: encrypt(this.responseData)}).toString().replace("data=",''); this.responseData = encrypt(this.responseData); } - return { - responseCode, - responseMessage, - responseData: this.responseData - }; + const response = this.getResponse(responseString, responseOptions, packageName); + return response; + } catch (e) { console.log("Exception caught", e); - const { responseCode, responseMessage } = this.getResponse(); + const response = this.getResponse(e === "NODE_VERSION_ERROR" ? e : ""); if (process.env.MODE == "DEV" && e.message) this.setDebugMessage(e.message); - return { - responseCode, - responseMessage, - responseData: {} - }; + return response; } } @@ -192,12 +200,14 @@ class executor { const BASE_RESPONSE = require(path.resolve(process.cwd(), `src/global/i18n/response.js`)).RESPONSE; const PROJECT_RESPONSE = require(`../i18n/response.js`).RESPONSE; + const CUSTOM_RESPONSE_TEMPLATE = require(path.resolve(process.cwd(), `src/config/responseTemplate.json`)); + let RESP = { ...PROJECT_RESPONSE, ...BASE_RESPONSE }; if (packageName) { try { let packageVals = packageName.split('/'); - const PACKAGE_RESPONSE = require(path.resolve(process.cwd(), `njs2_modules/${[...packageVals.slice(0, packageVals.length - 1)].join('/')}/contract/response.json`)); + const PACKAGE_RESPONSE = require(path.resolve(process.cwd(), `node_modules/${[...packageVals.slice(0, packageVals.length - 1)].join('/')}/contract/response.json`)); RESP = { ...RESP, ...PACKAGE_RESPONSE }; } catch { } @@ -206,25 +216,52 @@ class executor { if (!RESP[this.responseString]) { RESP = RESP["RESPONSE_CODE_NOT_FOUND"]; } else { - RESP = RESP[this.responseString]; + RESP = {...RESP[this.responseString]}; } - this.responseCode = RESP.responseCode; - this.responseMessage = this.lng_key && RESP.responseMessage[this.lng_key] + RESP.responseMessage = this.lng_key && RESP.responseMessage[this.lng_key] ? RESP.responseMessage[this.lng_key] : RESP.responseMessage[DEFAULT_LNG_KEY]; - if (this.responseOptions) - Object.keys(this.responseOptions).map(keyName => { - this.responseMessage = this.responseMessage.replace(keyName, this.responseOptions[keyName]); + RESP.responseData = this.responseData; + + if(this.responseOptions) + Object.keys(this.responseOptions).map(keyName => { + RESP.responseMessage = RESP.responseMessage.replace(keyName, this.responseOptions[keyName]); + }); + + return this.parseResponseData(CUSTOM_RESPONSE_TEMPLATE,RESP); + + } + + parseResponseData(CUSTOM_RESPONSE_TEMPLATE,RESP){ + try{ + Object.entries(RESP).forEach(array => { + const [key,value] = array; + if(typeof value === 'object'){ + RESP[key] = "{" + JSON.stringify(value) + "}"; + } }); + + const compiled = _.template(typeof CUSTOM_RESPONSE_TEMPLATE === 'string' ? CUSTOM_RESPONSE_TEMPLATE : JSON.stringify(CUSTOM_RESPONSE_TEMPLATE)); + + const resultTemplate = compiled(RESP); + + const matcherObj = { + '"{{': '{', + '}}"': '}', + '"{[': '[', + ']}"': ']' + } + + const replacedString = multiReplace(resultTemplate, matcherObj); - return { - responseCode: this.responseCode, - responseMessage: this.responseMessage, - responseData: this.responseData - }; + return typeof CUSTOM_RESPONSE_TEMPLATE === 'string' ? replacedString : JSON.parse(replacedString); + }catch(error){ + throw new Error("parseResponseData Error:"+error); + } } + } module.exports = executor; \ No newline at end of file diff --git a/helper/baseHelper.class.js b/helper/baseHelper.class.js index edd8d26..baaca65 100644 --- a/helper/baseHelper.class.js +++ b/helper/baseHelper.class.js @@ -120,6 +120,51 @@ class baseHelper { return requestData ? requestData : {}; } + + static deepFindPropMaker(obj) { + return (prop) => { + if (!obj || typeof obj !== 'object') return null; + if (obj.hasOwnProperty(prop) && obj[prop] !== null && obj[prop] !== undefined) { + return obj[prop]; + } + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + let found = this.deepFindPropMaker(obj[key])(prop); + if (found) return found; + } + } + return null; + } + } + + static populateMetadata(request, configs) { + + if(configs.length == 0) return {} + + const deepFindPropInRequestObject = this.deepFindPropMaker(request) + + const keys = configs.map(key => key.trim()); + + let resultObj = {}; + + for (const key of keys) { + const value = deepFindPropInRequestObject(key); + if (value !== undefined) { + resultObj[key] = value; + } + } + return resultObj + } + + static isJSON(str) { + try { + JSON.parse(str); + return true; + } catch (e) { + return false; + } + } + } module.exports = baseHelper; diff --git a/package.json b/package.json index e86156d..5ce681f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "node-cron": "^3.0.2", "query-string": "^7.0.1", "require-dir": "^1.2.0", + "serverless-http": "^3.2.0", "socket.io": "^4.1.2" }, "njs2-type": "base", diff --git a/template/frameworkStructure/lambda.js b/template/frameworkStructure/lambda.js index 1b19159..cd96eb0 100644 --- a/template/frameworkStructure/lambda.js +++ b/template/frameworkStructure/lambda.js @@ -1,26 +1,70 @@ -const serverless = require('./serverless'); +const serverlessExpress = require('serverless-http') +const express = require('express') +const multer = require('multer'); +const app = express() +const upload = multer(); + const websockets = require('./websockets'); -//const init = require('./src/library/roomHandler/init'); // Make sure to create this file and add defualt content. +const { Executor } = require("@njs2/base"); -module.exports.handler = async (event) => { +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(upload.any()); + + +app.all('*', async function (req, res) { + const {event} = req.apiGateway + let result = {} try { const requestType = event.stageVariables.requestType; if (requestType === 'API') { - return await serverless.execute(event); + result = await lambdaExecutor(event) } else if (requestType === 'Socket') { - return await websockets.handler(event); - } else if (requestType === 'mCron') { - // get the taskName + result = await websockets.handler(event); + } else if (requestType === 'scheduler') { const taskName = event.stageVariables.taskName; - // require the file by taskName - const mCron = require(`./src/tasks/${taskName}.task.js`); - // call default of taskName - mCron(); - } else if (requestType === 'cron') { - const cron = require(`./cron`); - cron(); + const task = require(`./src/tasks/${taskName}.task.js`); + task(); + } + res.send({...result}) + } catch(error) { + console.error(error) + res.send({ + responseCode: 100011, + responseMessage: "Something went wrong!", + responseData: {} + }) + } +}) + +exports.handler = serverlessExpress(app) + +async function lambdaExecutor(eventObject) { + try { + const { httpMethod, queryStringParameters, path, files, body, headers } = eventObject + // Neutralize input parameter received from express for Executor.executeRequest + let executorReq = {}; + executorReq.httpMethod = httpMethod; + executorReq.queryStringParameters = queryStringParameters; + executorReq.body = body; + executorReq.pathParameters = { + proxy: path.length ? path.slice(1) : path + }; + executorReq.headers = headers; + if (files && files.length) { + if (files.length > 1) throw new Error("Only one file upload at a time is allowed") + files.forEach(file => { + executorReq.body[file.fieldname] = { + type: 'file', + filename: file.originalname, + contentType: file.mimetype, + content: file.buffer + }; + }); } + const executor = new Executor(); + return await executor.executeRequest(executorReq); } catch (error) { - console.error(error); + throw error } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/template/frameworkStructure/serverless.js b/template/frameworkStructure/serverless.js index 1a26559..f0fbe10 100644 --- a/template/frameworkStructure/serverless.js +++ b/template/frameworkStructure/serverless.js @@ -11,7 +11,7 @@ module.exports.execute = async (event) => { let fileCount = 0; if (event.headers["Content-Type"] === "application/x-www-form-urlencoded") { const querystring = require("querystring"); - event.body = querystring.parse(event.body); + event.body = Object.assign({}, querystring.parse(event.body)); } if ( diff --git a/template/frameworkStructure/src/config/config.json b/template/frameworkStructure/src/config/config.json index 60361a0..f01453c 100644 --- a/template/frameworkStructure/src/config/config.json +++ b/template/frameworkStructure/src/config/config.json @@ -16,6 +16,7 @@ "END_POINT": "", "LAMBDA_FUNCTION_NAME": "" }, + "METADATA": [], "AUTH": { "AUTH_MODE": "JWT", "JWT_SECRET": "123", diff --git a/template/frameworkStructure/src/config/responseTemplate.json b/template/frameworkStructure/src/config/responseTemplate.json new file mode 100644 index 0000000..4e90d83 --- /dev/null +++ b/template/frameworkStructure/src/config/responseTemplate.json @@ -0,0 +1,5 @@ +{ + "responseCode":"<%=responseCode%>", + "responseMessage":"<%=responseMessage%>", + "responseData":"<%=responseData%>" +} \ No newline at end of file