Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Enhancements & Minor Fixes to Framework files #48

Merged
merged 18 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 68 additions & 31 deletions base/executor.class.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@ 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() {
this.responseData = {};
}

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);
Expand All @@ -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
Expand Down Expand Up @@ -80,14 +87,22 @@ 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();
const isFileExpected = baseHelper.isFileExpected(params);
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);
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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 {
}
Expand All @@ -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;
45 changes: 45 additions & 0 deletions helper/baseHelper.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
76 changes: 60 additions & 16 deletions template/frameworkStructure/lambda.js
Original file line number Diff line number Diff line change
@@ -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
}
};
}
2 changes: 1 addition & 1 deletion template/frameworkStructure/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
1 change: 1 addition & 0 deletions template/frameworkStructure/src/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"END_POINT": "",
"LAMBDA_FUNCTION_NAME": ""
},
"METADATA": [],
"AUTH": {
"AUTH_MODE": "JWT",
"JWT_SECRET": "123",
Expand Down
5 changes: 5 additions & 0 deletions template/frameworkStructure/src/config/responseTemplate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"responseCode":"<%=responseCode%>",
"responseMessage":"<%=responseMessage%>",
"responseData":"<%=responseData%>"
}