checking end points of icici bank register api

This commit is contained in:
Chirag
2024-09-12 19:15:21 +05:30
commit 582f88abbb
20 changed files with 2672 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
# Dependencies
node_modules
# yarn error logs
yarn-error.log
# Environment varibales
.env*
!.env*.example
# Code coverage
coverage
# Public
public
# test
test

1920
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "optifii",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "nodemon src/index",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"axios": "^1.7.7",
"compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"express-rate-limit": "^7.4.0",
"helmet": "^7.1.0",
"http-status": "^1.7.4",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.11.2",
"sequelize": "^6.37.3",
"winston": "^3.14.2",
"xss-clean": "^0.1.4",
"yup": "^1.4.0"
}
}

62
src/app.js Normal file
View File

@@ -0,0 +1,62 @@
const express = require('express');
const helmet = require('helmet');
const xss = require('xss-clean');
const compression = require('compression');
const cors = require('cors');
const httpStatus = require('http-status');
const config = require('./config/config');
const morgan = require('./config/morgan');
const { authLimiter } = require('./middlewares/rateLimiter');
const routes = require('./routes');
const { errorConverter, errorHandler } = require('./middlewares/error');
const ApiError = require('./utils/handler/ApiError.handler');
const path = require('path')
const app = express();
if (config.env !== 'test') {
app.use(morgan.successHandler);
app.use(morgan.errorHandler);
}
// set security HTTP headers
// app.use(helmet());
// parse json request body
app.use(express.json());
// parse urlencoded request body
app.use(express.urlencoded({ extended: true }));
// sanitize request data
app.use(xss());
// gzip compression
app.use(compression());
// enable cors
app.use(cors());
app.options('*', cors());
app.use('/public', express.static(path.join(__dirname, '../public')));
// limit repeated failed requests to auth endpoints
if (config.env === 'production') {
app.use('/api/v1/auth', authLimiter);
}
// v1 api routes
app.use('/api/v1', routes);
// send back a 404 error for any unknown api request
app.use((req, res, next) => {
next(new ApiError(httpStatus.NOT_FOUND, 'Not found'));
});
// convert error to ApiError, if needed
app.use(errorConverter);
// handle error
app.use(errorHandler);
module.exports = app;

150
src/config/config.js Normal file
View File

@@ -0,0 +1,150 @@
const dotenv = require('dotenv');
const path = require('path');
const yup = require('yup');
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
dotenv.config({ path: path.join(__dirname, '../../.env') });
const envVarsSchema = yup.object().shape({
NODE_ENV: yup.string().oneOf(['production', 'development', 'test']).required(),
PORT: yup.number().default(3000),
BY_PASS_OTP: yup.boolean().default(true).required('by pass otp is required'),
JWT_SECRET: yup.string().required('JWT secret key is required'),
JWT_ACCESS_EXPIRATION_MINUTES: yup.number().default(30).required('minutes after which access tokens expire'),
JWT_REFRESH_EXPIRATION_DAYS: yup.number().default(30).required('days after which refresh tokens expire'),
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: yup.number()
.default(10)
.required('minutes after which reset password token expires'),
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: yup.number()
.default(10)
.required('minutes after which verify email token expires'),
SMTP_HOST: yup.string().nullable().required('server that will send the emails'),
SMTP_PORT: yup.number().nullable().required('port to connect to the email server'),
SMTP_USERNAME: yup.string().nullable().required('username for email server'),
SMTP_PASSWORD: yup.string().nullable().required('password for email server'),
EMAIL_FROM: yup.string().nullable().required('the from field in the emails sent by the app'),
TWILIO_ACCOUNT_SID: yup.string().nullable().required(),
TWILIO_AUTH_TOKEN: yup.string().nullable().required(),
TWILIO_SMS_FROM: yup.string().nullable().required(),
OTP_EXPIRE_IN_MIN: yup.number().nullable(),
CODE_SECRET: yup.string().required('Code secret key is required'),
ONESIGNAL_APPID: yup.string().required('app id key is required'),
ONESIGNAL_REST_APIKEY: yup.string().required('api key is required'),
ONESIGNAL_AUTHKEY: yup.string().required('auth key is required'),
}).noUnknown();
try {
const envVars = envVarsSchema.validateSync(process.env, { abortEarly: false, stripUnknown: true });
module.exports = {
env: envVars?.NODE_ENV,
port: envVars?.PORT,
byPassOTP: envVars?.BY_PASS_OTP,
mysql: {
development: {
username: "root",
password: "root",
database: "optifii",
host: "127.0.0.1",
port: 3306,
dialect: "mysql",
dialectOptions: {
bigNumberStrings: true,
},
logging: false,
define: {
freezeTableName: true,
},
pool: {
max: 10, // Maximum number of connection in pool
min: 0, // Minimum number of connection in pool
acquire: 30000, // Maximum time in ms to acquire a connection
idle: 10000, // Maximum time in ms a connection can be idle before being released
}
},
test: {
username: "root",
password: "root",
database: "optifii_test",
host: "127.0.0.1",
port: 3306,
dialect: "mysql",
dialectOptions: {
bigNumberStrings: true,
},
logging: false,
define: {
freezeTableName: true,
},
pool: {
max: 10, // Maximum number of connection in pool
min: 0, // Minimum number of connection in pool
acquire: 30000, // Maximum time in ms to acquire a connection
idle: 10000, // Maximum time in ms a connection can be idle before being released
}
},
production: {
username: "mymotukuser",
password: "password_Carrot11",
database: "optifii_sprint",
host: "127.0.0.1",
port: 3306,
dialect: "mysql",
dialectOptions: {
bigNumberStrings: true,
},
logging: false,
define: {
freezeTableName: true,
socketPath: "/var/run/mysqld/mysqld.sock",
},
pool: {
max: 10, // Maximum number of connection in pool
min: 0, // Minimum number of connection in pool
acquire: 30000, // Maximum time in ms to acquire a connection
idle: 10000, // Maximum time in ms a connection can be idle before being released
}
},
},
jwt: {
secret: envVars?.JWT_SECRET,
accessExpirationMinutes: envVars?.JWT_ACCESS_EXPIRATION_MINUTES,
refreshExpirationDays: envVars?.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars?.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars?.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
},
email: {
smtp: {
host: envVars?.SMTP_HOST,
port: envVars?.SMTP_PORT,
secure: process.env.SMTP_PORT == 465, // true for 465, false for other ports
auth: {
user: envVars?.SMTP_USERNAME,
pass: envVars?.SMTP_PASSWORD,
},
},
from: envVars?.EMAIL_FROM,
},
SMS: {
accountSid: envVars.TWILIO_ACCOUNT_SID,
authToken: envVars.TWILIO_AUTH_TOKEN,
from: envVars.TWILIO_SMS_FROM,
},
expiryTime: {
otp_in_Min: envVars.OTP_EXPIRE_IN_MIN || 2,
},
code: {
secret: crypto.createHash('sha256').update(envVars.CODE_SECRET).digest(),
blacklist: new Set(),
},
oneSignal:{
appID:envVars.ONESIGNAL_APPID,
userAuthKey:envVars.ONESIGNAL_AUTHKEY,
restApiKey:envVars.ONESIGNAL_REST_APIKEY,
}
};
} catch (error) {
throw new Error(`Config validation error: ${Array.isArray(error.errors) ? error?.errors?.join(', ') : error}`);
}

26
src/config/logger.js Normal file
View File

@@ -0,0 +1,26 @@
const winston = require('winston');
const config = require('./config');
const enumerateErrorFormat = winston.format((info) => {
if (info instanceof Error) {
Object.assign(info, { message: info.stack });
}
return info;
});
const logger = winston.createLogger({
level: config.env === 'development' ? 'debug' : 'info',
format: winston.format.combine(
enumerateErrorFormat(),
config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
winston.format.splat(),
winston.format.printf(({ level, message }) => `${level}: ${message}`)
),
transports: [
new winston.transports.Console({
stderrLevels: ['error'],
}),
],
});
module.exports = logger;

24
src/config/morgan.js Normal file
View File

@@ -0,0 +1,24 @@
const morgan = require('morgan');
const config = require('./config');
const logger = require('./logger');
morgan.token('message', (req, res) => res.locals.errorMessage || '');
const getIpFormat = () => (config.env === 'production' ? ':remote-addr - ' : '');
const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`;
const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`;
const successHandler = morgan(successResponseFormat, {
skip: (req, res) => res.statusCode >= 400,
stream: { write: (message) => logger.info(message.trim()) },
});
const errorHandler = morgan(errorResponseFormat, {
skip: (req, res) => res.statusCode < 400,
stream: { write: (message) => logger.error(message.trim()) },
});
module.exports = {
successHandler,
errorHandler,
};

View File

@@ -0,0 +1,60 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const bankApi = async (req, res) => {
try {
const publicKeyPath = path.join(__dirname, 'public_key.pem');
const publicKeyPem = fs.readFileSync(publicKeyPath, 'utf8');
const jsonData = {
"AGGRNAME": "CIBTESTING",
"AGGRID": "TXBCIBTEST001",
"CORPID": "TXBCORP1",
"USERID": "USER1",
"URN": "TESTING123",
"ALIASID": ""
};
const jsonString = JSON.stringify(jsonData);
// Encrypt JSON data with the public key
const buffer = Buffer.from(jsonString, 'utf-8');
const encrypted = crypto.publicEncrypt(
{
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_PADDING
},
buffer
);
// Encode encrypted bytes to Base64
const base64Encrypted = encrypted.toString('base64');
console.log('Base64 Encrypted Data:', base64Encrypted);
// Make an Axios request
const response = await axios.post(
'https://apibankingonesandbox.icicibank.com/api/Corporate/CIB_SV/v1/Create',
base64Encrypted,
{
headers: {
'accept': "*",
'Content-Type': 'text/plain',
'apikey': 'COIbAAYzt0SMosd3fFexJlk42uqnPvvu',
'x-forwarded-for': '77.68.102.23',
'Content-Length': Buffer.byteLength(base64Encrypted).toString()
}
}
);
res.status(200).json(response.data);
} catch (error) {
console.log(error);
res.status(500).json({ error: 'Encryption or API request failed' });
}
};
module.exports = {
bankApi
}

View File

@@ -0,0 +1,50 @@
-----BEGIN CERTIFICATE-----
MIIFhDCCA2wCCQCIqfcsomoC1jANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCSU4xF
DASBgNV
BAgMC01BSEFSQVNIVFJBMQ8wDQYDVQQHDAZNVU1CQUkxGDAWBgNVBAoMD0lDSUN
JIEJhbmsgTHRk
LjEMMAoGA1UECwwDQ0lCMSUwIwYDVQQDDBx3d3cuY2libmV4dGFwaS5pY2ljaWJhbmsuY
29tMB4X
DTE3MDQxMzA3MTkyOVoXDTIyMDQxMjA3MTkyOVowgYMxCzAJBgNVBAYTAklOMRQwEgY
DVQQIDAtN
QUhBUkFTSFRSQTEPMA0GA1UEBwwGTVVNQkFJMRgwFgYDVQQKDA9JQ0lDSSBCYW5rI
Ex0ZC4xDDAK
BgNVBAsMA0NJQjElMCMGA1UEAwwcd3d3LmNpYm5leHRhcGkuaWNpY2liYW5rLmNvbTCC
AiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAM+en2ErEsETmfoZJjf3I5DIc8KAt6dv/ZkKYHcpli1
g
yLFjJNbZnyk4Um7UaKvU0fpqnsLboapXKR0iHFp4/7SR9kTh4FfvFrrp2pKmQd8f/Rf6OPk2/48i
X7sCs0nl8IZYMqe1Tt1YAMFPJjPIH/ERx3vnWYhgHUmRGJqjHfo4NeNR0IarF3HAYX4hh6K0L
aAQ
hUoq6SuWyWf9m9qzHRHpWq4eJRsbhPYLaTtt8XS+vPpBjFjfQreDtgWdIXwKuHq8EOS/KxBif
ThC
tEBMGZUSYZBoldq1kdaakkt5FaXhe+g0FWrLcxalaSS4bHK0QCv1Lbh3tcPetCO3XyR1Jj28SL
+5
gYm464jmjMGURJwocWUhuNd0qAKt8bMv9NCDgKiWSmAlzeznRYeNaay2ckg5aB5tNO5l/8pU
h8Ew
qLyKECFnCoNvBlcaoJIvZ0sprQO+dHzggT/Q9wl0XRFUkPh4SFGHIiqldy6VgA6I7uVWb7ve1Y3
P
4yhlfTDV/Hr4ZL4gTFVrorS1a4Tqap38iqHnfM3djwgwbnzv0TJZCywZ5ED8MRDmub6W4jNYMV
ar
uG1gLVf7gE2sUY/dgTRu1Hdw3/YlOY9XpQebBP37RD3+Up+oEYxjPe04Cy4rTFx9/8SluuBPvw
NH
WVmHkv1ULNHum0VQ3kej17jbEeO+FftJAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAMGX2
dKuXsGj
ujhKxZOFzo8A0QKu+nsw+pFtiJ5KjyOR1vW9pOdG7roJJGr6cU5fUDlUpYDDVIvPiVbPYgWLkV
e3
7+tpM8T77ZYSXdO7G9hhU8uw2pcRHiQMlDotV/RcTGZHyVVaw7TJty3xMH2j0/FIHejcFaYXZY
QB
A5+zKc7PBsvwn/KQgJ9R4BTqmdWeca1r0+iBXGq1iRg4IGePf0lIc+80AUneC1ceC07RfvI0PJp
k
LVTkDCXdNK7QtG/cIqjdZ1jtB+ne7cwtksw1ewu5dE3BFNmqdT3DmKHAupTc2ILSup2w/JEEep
MI
DHO8GvqR0dUXS5xCcXNKwXUMiLPYA56mRKoST5+e2RO5WtVQMHiizEF5iID+WjyXNlVtqM
arEjih
Z0+/vkABp/Q3AfKs3rtaXxU4crt+RLaaldG/dBXOoUDTpaNR+ktUkNmEPTe9zc7pwwRDC2zNylt
4
FnhNP2b2t+RLuP+smAROVaXA1owpte3zeh7aiUe02Y6udEzVrKCAvRUiCKoCDH9N101k3lzC
Fy80
rRquHZ7ZZmUrX4DksuPnSuLILR5ss6UkQTZbg7HXtMN2lDTgPjO2UMCjqI+5gPGTqdld4XWD
TEW0
xdyhJiEgATeqQllbn47B7C7603ltWFpoInafn2NwxBW89wv938bMKpxFxmQcseGH
-----END CERTIFICATE-----

View File

@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAz56fYSsSwROZ+hkmN/cj
kMhzwoC3p2/9mQpgdymWLWDIsWMk1tmfKThSbtRoq9TR+mqewtuhqlcpHSIcWnj/
tJH2ROHgV+8WuunakqZB3x/9F/o4+Tb/jyJfuwKzSeXwhlgyp7VO3VgAwU8mM8gf
8RHHe+dZiGAdSZEYmqMd+jg141HQhqsXccBhfiGHorQtoBCFSirpK5bJZ/2b2rMd
Eelarh4lGxuE9gtpO23xdL68+kGMWN9Ct4O2BZ0hfAq4erwQ5L8rEGJ9OEK0QEwZ
lRJhkGiV2rWR1pqSS3kVpeF76DQVastzFqVpJLhscrRAK/UtuHe1w960I7dfJHUm
PbxIv7mBibjriOaMwZREnChxZSG413SoAq3xsy/00IOAqJZKYCXN7OdFh41prLZy
SDloHm007mX/ylSHwTCovIoQIWcKg28GVxqgki9nSymtA750fOCBP9D3CXRdEVSQ
+HhIUYciKqV3LpWADoju5VZvu97Vjc/jKGV9MNX8evhkviBMVWuitLVrhOpqnfyK
oed8zd2PCDBufO/RMlkLLBnkQPwxEOa5vpbiM1gxVqu4bWAtV/uATaxRj92BNG7U
d3Df9iU5j1elB5sE/ftEPf5Sn6gRjGM97TgLLitMXH3/xKW64E+/A0dZWYeS/VQs
0e6bRVDeR6PXuNsR474V+0kCAwEAAQ==
-----END PUBLIC KEY-----

3
src/controllers/index.js Normal file
View File

@@ -0,0 +1,3 @@
module.export = {
bankDetailsController: require('./bankDetails/bankDetails.controller'),
}

49
src/index.js Normal file
View File

@@ -0,0 +1,49 @@
const app = require('./app');
const config = require('./config/config');
const logger = require('./config/logger');
const db = require('./models');
// const seed = require('./seed');
let server;
db.sequelize
.sync({ force: config.env === 'test' })
.then(() => {
logger.info('Connected to MySQL');
// seed(db)
server = app.listen(config.port, () => {
logger.info(`Server listening on port ${config.port}`)
logger.info(`Enviorment :- ${config.env}`);
logger.info(`Is OTP by pass :- ${config.byPassOTP}`);
});
})
.catch((error) => {
logger.error('Error connecting to MySQL:', error);
process.exit(1);
});
const exitHandler = () => {
if (server) {
server.close(() => {
logger.info('Server closed');
process.exit(1);
});
} else {
process.exit(1);
}
};
const unexpectedErrorHandler = (error) => {
logger.error(error);
exitHandler();
};
process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);
process.on('SIGTERM', () => {
logger.info('SIGTERM received');
if (server) {
server.close();
}
});

55
src/middlewares/error.js Normal file
View File

@@ -0,0 +1,55 @@
const { BaseError, ValidationError, UniqueConstraintError } = require("sequelize");
const ApiError = require("../utils/handler/ApiError.handler");
const config = require("../config/config");
const httpStatus = require("http-status");
const logger = require("../config/logger");
const multer = require("multer");
const errorConverter = (err, req, res, next) => {
let error = err;
if (error instanceof multer.MulterError) {
// Handle Multer errors
error = new ApiError(httpStatus.BAD_REQUEST, error.message, error, false, err.stack);
} else if (error instanceof ValidationError || error instanceof UniqueConstraintError) {
// Handle Sequelize validation and unique constraint errors
const messages = error.errors.map(e => e.message);
error = new ApiError(httpStatus.BAD_REQUEST, messages.join(", "), error, false, err.stack);
} else if (!(error instanceof ApiError)) {
// Handle other errors
const statusCode =
error.statusCode || error instanceof BaseError ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR;
const message = error.message || httpStatus[statusCode];
error = new ApiError(statusCode, message, error, false, err.stack);
}
next(error);
};
// eslint-disable-next-line no-unused-vars
const errorHandler = (err, req, res, next) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
}
res.locals.errorMessage = err.message;
const response = {
code: statusCode,
message,
...(config.env === 'development' && { stack: err.stack }),
};
if (config.env === 'development') {
logger.error(err);
}
res.status(statusCode).send(response);
};
module.exports = {
errorConverter,
errorHandler,
};

View File

@@ -0,0 +1,11 @@
const rateLimit = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
skipSuccessfulRequests: true,
});
module.exports = {
authLimiter,
};

39
src/models/index.js Normal file
View File

@@ -0,0 +1,39 @@
"use strict";
const { Sequelize } = require("sequelize");
const config = require("../config/config");
const logger = require("../config/logger");
// Initialize Sequelize instance
const sequelize = new Sequelize(
config.mysql[config.env].database,
config.mysql[config.env].username,
config.mysql[config.env].password,
config.mysql[config.env]
);
// Test database connection
sequelize
.authenticate()
.then(() => logger.info("Connection has been established successfully."))
.catch((error) => logger.error("Unable to connect to the database: ", error));
// Load models
const db = {};
// db.bank_details = require('./main/bank_details.model')(sequelize);
// Handle model associations
(async () => {
for (const modelName of Object.keys(db)) {
if (db[modelName].associate) {
await db[modelName].associate(db);
}
}
})();
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

69
src/routes/index.js Normal file
View File

@@ -0,0 +1,69 @@
const express = require('express');
const { bankApi } = require('../controllers/bankDetails/bankDetails.controller'); // Adjust the path to your controller
const router = express.Router();
// Define your route here
router.post('/bankDetails/create', bankApi); // Define the route directly in this file
module.exports = router;
// const express = require('express');
// const router = express.Router();
// // Ensure the correct path to your route file
// const defaultRoutes = [
// {
// path: '/bankDetails',
// route: require('./bankDetails.route'), // Check this path
// },
// ];
// defaultRoutes.forEach((route) => {
// router.use(route.path, route.route);
// });
// module.exports = router;
// // const express = require('express');
// // const router = express.Router();
// // const defaultRoutes = [
// // {
// // path: '/bankDetails',
// // route: require('./bankDetails.route'),
// // },
// // ];
// // defaultRoutes.forEach((route) => {
// // router.use(route.path, route.route);
// // });
// // module.exports = router;
// // const express = require('express');;
// // const config = require('../../config/config');
// // const router = express.Router();
// // const defaultRoutes = [
// // {
// // path: '/bankDetails',
// // route: require('./bankDetails')
// // },
// // ];
// // /* istanbul ignore next */
// // // if (config.env === 'development') {
// // // devRoutes.forEach((route) => {
// // // router.use(route.path, route.route);
// // // })
// // // }
// // module.exports = router;

View File

@@ -0,0 +1,41 @@
/**
* Custom error class for handling API errors. Extends the native Error class.
* @class ApiError
* @extends Error
*
* @param {number} statusCode - HTTP status code associated with the error.
* @param {string} [message="Something went wrong"] - Error message (default is a generic message).
* @param {Array} [errors=[]] - Array containing specific error details or additional information.
* @param {boolean} [isOperational=true] - Indicates if the error is operational or not.
* @param {string} [stack=""] - Stack trace for the error (optional, defaults to an empty string).
*/
class ApiError extends Error {
constructor(
statusCode,
message = "Something went wrong",
errors = [],
isOperational = true,
stack = ""
) {
// Call the constructor of the parent class (Error)
super(message);
// Custom properties for API error handling
this.statusCode = statusCode; // HTTP status code associated with the error.
this.data = null; // Additional data associated with the error (initially set to null).
this.message = message; // Error message for the API response.
this.success = false; // Indicates the failure nature of the API request.
this.errors = errors; // Specific error details or additional information.
this.isOperational = isOperational; // Indicates if the error is operational or not.
// Set the stack trace if provided; otherwise, capture the current stack trace.
if (stack) {
this.stack = stack; // Provided stack trace.
} else {
Error.captureStackTrace(this, this.constructor); // Capture the current stack trace.
}
}
}
// Export the ApiError class for use in other modules.
module.exports = ApiError;

View File

@@ -0,0 +1,19 @@
/**
* Class representing a standard API response.
* @class ApiResponse
*
* @param {number} statusCode - HTTP status code associated with the response.
* @param {any} data - Data to be returned in the response.
* @param {string} [message="Success"] - Response message (default is "Success").
*/
class ApiResponse {
constructor(statusCode, data, message = "Success") {
this.statusCode = statusCode; // HTTP status code associated with the response.
this.data = data; // Data to be returned in the response.
this.message = message; // Response message (default is "Success").
this.success = statusCode < 400; // Indicates if the response is successful based on the status code.
}
}
// Export the ApiResponse class for use in other modules.
module.exports = ApiResponse;

View File

@@ -0,0 +1,11 @@
/**
* Asynchronous error handler for Express routes.
* @function AsyncHandler
*
* @param {Function} fn - Asynchronous function to be executed.
* @returns {Function} Middleware function that handles errors for asynchronous routes.
*/
module.exports.AsyncHandler = (fn) => (req, res, next) => {
// Wrap the asynchronous function in a promise and handle any errors by passing them to the next middleware.
Promise.resolve(fn(req, res, next)).catch(next);
};

View File

@@ -0,0 +1,21 @@
/**
* Create an object composed of the picked object properties.
* @function pick
*
* @param {Object} object - The source object to pick properties from.
* @param {string[]} keys - The array of property names to pick from the source object.
* @returns {Object} New object with only the picked properties.
*/
const pick = (object, keys) => {
return keys.reduce((obj, key) => {
// Check if the object has the specified property
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
obj[key] = object[key]; // Assign the property to the new object
}
return obj; // Return the new object
}, {}); // Initialize the new object as an empty object
};
// Export the pick function for use in other modules.
module.exports = pick;