Implemented ICICI API endpoints

This commit is contained in:
Chirag
2024-10-11 13:20:53 +05:30
parent b002bee415
commit ab64b292c9
80 changed files with 4574 additions and 0 deletions

16
ecosystem.config.json Normal file
View File

@@ -0,0 +1,16 @@
{
"apps": [
{
"name": "Tanami-Backend",
"script": "src/index.js",
"instances": 1,
"autorestart": true,
"watch": false,
"time": true,
"env": {
"NODE_ENV": "production"
}
}
]
}

46
package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "Tanami-Backend",
"version": "v1.0.0",
"main": "src/index.js",
"author": "Swapnil Bendal",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"scripts": {
"start": "pm2 start ecosystem.config.json --no-daemon",
"dev": "cross-env NODE_ENV=development nodemon src/index.js",
"test": "cross-env NODE_ENV=test nodemon src/index.js"
},
"dependencies": {
"@onesignal/node-onesignal": "^5.0.0-alpha-01",
"axios": "^1.7.5",
"bcrypt": "^5.1.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-rate-limit": "^7.3.1",
"helmet": "^7.1.0",
"http-status": "^1.7.4",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.10.2",
"nodemailer": "^6.9.14",
"request-ip": "^3.3.0",
"sequelize": "^6.37.3",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"twilio": "^5.2.2",
"winston": "^3.13.0",
"xss-clean": "^0.1.4",
"yup": "^1.4.0"
},
"devDependencies": {
"nodemon": "^3.1.4"
}
}

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

@@ -0,0 +1,3 @@
module.exports = {
getAllCorporateDTO: require('./corporate/corporate.dto'),
}

View File

@@ -0,0 +1,64 @@
const axios = require("axios");
const config = require("../../config/config");
const instance = axios.create({
baseURL: config.icici.baseURL,
headers: {
'accept': "*",
'Content-Type': 'text/plain',
'apikey': config.icici.apikey,
'x-forwarded-for': config.icici.x_forwarded_for,
'Content-Length': 684,
}
})
module.exports = {
createOtp: async (encryptedData) => {
try {
const { data } = await instance.post('/api/Corporate/CIB_SV/v1/Create', encryptedData); // Assuming `url` is relative to baseURL
return data;
} catch (error) {
console.error("Error during registration API call:", error.message); // Log error message
throw error; // Throw the actual error object
}
},
balanceInquiry: async (encryptedData) => {
try {
const { data } = await instance.post('/api/Corporate/CIB_SV/v1/BalanceInquiry', encryptedData); // Assuming `url` is relative to baseURL
return data;
} catch (error) {
console.error("Error during Balance Inquiry API call:", error.message); // Log error message
throw error; // Throw the actual error object
}
},
transactionOTP: async (encryptedData) => {
try {
const { data } = await instance.post('/api/Corporate/CIB_SV/v1/TransactionOTP', encryptedData); // Assuming `url` is relative to baseURL
console.log("transactionOTP", data)
return data;
} catch (error) {
console.error("Error during transaction OTP API call:", error.message); // Log error message
throw error; // Throw the actual error object
}
},
accountStatement: async (encryptedData) => {
try {
const {data} = await instance.post('/api/Corporate/CIB_SV/v1/AccountStatement', encryptedData); // Assuming `url` is relative to baseURL
return data;
} catch (error) {
console.error("Error during account statement API call:", error.message); // Log error message
throw error; // Throw the actual error object
}
},
RegistrationStatus: async (encryptedData) => {
try {
const { data } = await instance.post('registration-status', encryptedData);
return data;
} catch (error) {
throw new Error('Error during registration status API call');
}
},
};

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

@@ -0,0 +1,3 @@
module.exports = {
iciciApi: require('./ICICI/icici.api'),
}

73
src/app.js Normal file
View File

@@ -0,0 +1,73 @@
const express = require('express');
const requestIp = require('request-ip');
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/v1');
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);
}
// Middleware to set client IP
app.use(requestIp.mw());
// Middleware to set dynamic Content Security Policy
app.use((req, res, next) =>
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:', `http://${req.hostname}`, `https://${req.hostname}`],
},
})(req, res, next)
);
// 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;

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

@@ -0,0 +1,179 @@
const dotenv = require('dotenv');
const path = require('path');
const yup = require('yup');
const crypto = require('crypto');
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 Passes
BY_PASS_OTP: yup.boolean().default(true).required('by pass otp is required'),
BY_PASS_NOTIFICATION: yup.boolean().default(true).required('by pass notification is required'),
BY_PASS_EMAIL: yup.boolean().default(true).required('by pass email is required'),
// JWT
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
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'),
// SMS
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().default(5).nullable(),
// OneSignal
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'),
// Code
CODE_SECRET: yup.string().required('Code secret key is required'),
// DataBase
DB_USERNAME: yup.string().required('DB Username is required'),
DB_PASSWORD: yup.string().required('DB Password is required'),
DB_DATABASE_NAME: yup.string().required('Database name is required'),
DB_HOSTNAME: yup.string().default('127.0.0.1').required('DB Hostname is required'),
DB_PORT: yup.number().default(3306).required('DB Port 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,
byPassNotification: envVars?.BY_PASS_NOTIFICATION,
byPassEmail: envVars?.BY_PASS_EMAIL,
mysql: {
development: {
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
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: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
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: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
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,
},
code: {
secret: crypto.createHash('sha256').update(envVars.CODE_SECRET).digest(),
blacklist: new Set(),
},
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,
},
oneSignal: {
appID: envVars.ONESIGNAL_APPID,
userAuthKey: envVars.ONESIGNAL_AUTHKEY,
restApiKey: envVars.ONESIGNAL_REST_APIKEY,
},
icici: {
baseURL: "https://apibankingonesandbox.icicibank.com",
apikey: "COIbAAYzt0SMosd3fFexJlk42uqnPvvu",
x_forwarded_for: "122.179.140.110"
}
};
} 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;

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

@@ -0,0 +1,33 @@
const morgan = require('morgan');
const config = require('./config');
const logger = require('./logger');
// Create a custom token for client IP
morgan.token('clientIp', (req) => req.clientIp || '');
// Custom token for error message
morgan.token('message', (req, res) => res.locals.errorMessage || '');
// Function to get IP format based on environment
const getIpFormat = () => (config.env === 'production' ? ':clientIp - ' : '');
// Define the response formats using morgan tokens
const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`;
const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`;
// Success handler (for requests with status codes below 400)
const successHandler = morgan(successResponseFormat, {
skip: (req, res) => res.statusCode >= 400,
stream: { write: (message) => logger.info(message.trim()) },
});
// Error handler (for requests with status codes 400 and above)
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,7 @@
const nodemailer = require('nodemailer');
const config = require('./config');
const transporter = nodemailer.createTransport(config.email.smtp);
module.exports = { transporter }

View File

@@ -0,0 +1,10 @@
const OneSignal = require("@onesignal/node-onesignal");
const config = require("./config");
const configuration = OneSignal.createConfiguration({
userAuthKey: config.oneSignal.userAuthKey, // Your User Auth Key
restApiKey: config.oneSignal.restApiKey, // Your REST API Key
});
const client = new OneSignal.DefaultApi(configuration);
module.exports = client;

12
src/config/roles.js Normal file
View File

@@ -0,0 +1,12 @@
const allRoles = {
user: [],
admin: ['getUsers', 'manageUsers'],
};
const roles = Object.keys(allRoles);
const roleRights = new Map(Object.entries(allRoles));
module.exports = {
roles,
roleRights,
};

10
src/config/tokens.js Normal file
View File

@@ -0,0 +1,10 @@
const tokenTypes = {
ACCESS: 'access',
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
};
module.exports = {
tokenTypes,
};

View File

@@ -0,0 +1,10 @@
const twilio = require('twilio');
const config = require('./config');
const accountSid = config.SMS.accountSid;
const authToken = config.SMS.authToken;
const twilioClient = twilio(accountSid, authToken);
module.exports = twilioClient;

View File

@@ -0,0 +1,115 @@
const { AsyncHandler } = require('../../utils/handler/Async.handler');
const ApiError = require('../../utils/handler/ApiError.handler');
const ApiResponse = require('../../utils/handler/ApiResponse.handler');
const { cryptoService } = require('../../services');
const { iciciApi } = require('../../api');
module.exports = {
createOTP: AsyncHandler(async (req, res) => {
try {
const jsonData = {
"AGGRNAME": "CIBTESTING",
"AGGRID": "TXBCIBTEST001",
"CORPID": "TXBCORP1",
"USERID": "USER1",
"URN": "TESTING123",
"UNIQUEID": "ABC125"
}
const encryptedData = await cryptoService.encryptWithPublicKey(jsonData)
const result = await iciciApi.createOtp(encryptedData)
const decryptedData = await cryptoService.decryptWithPrivateKey(result);
res.status(200).json(new ApiResponse(200, decryptedData, "Registration successful"));
} catch (error) {
throw new ApiError(500, "Registration failed");
}
}),
balanceInquiry: AsyncHandler(async (req, res) => {
try {
const jsonData = {
"AGGRID": "TXBCIBTEST001",
"CORPID": "TXBCORP1",
"USERID": "USER1",
"URN": "TESTING123",
"ACCOUNTNO": "010205001809"
}
const encryptedData = await cryptoService.encryptWithPublicKey(jsonData)
const result = await iciciApi.balanceInquiry(encryptedData)
const decryptedData = await cryptoService.decryptWithPrivateKey(result);
res.status(200).json(new ApiResponse(200, decryptedData, "Balance Inquiry successful"));
} catch (error) {
throw new ApiError(500, "Balance Inquiry failed");
}
}),
transactionOTP: AsyncHandler(async (req, res) => {
try {
const jsonData = {
"AGGRNAME": "CIBTESTING",
"AGGRID": "TXBCIBTEST001",
"CORPID": "TXBCORP1",
"USERID": "USER1",
"URN": "TESTING123",
"UNIQUEID": "ABC126",
"DEBITACC": "010205001809",
"CREDITACC": "000451000301",
"IFSC": "ICIC0000011",
"AMOUNT": "96.74",
"CURRENCY": "INR",
"TXNTYPE": "TPA",
"OTP": "580601",
"PAYEENAME": "WDI",
"REMARKS": "Test"
}
const encryptedData = await cryptoService.encryptWithPublicKey(jsonData)
const result = await iciciApi.transactionOTP(encryptedData)
const decryptedData = await cryptoService.decryptWithPrivateKey(result);
res.status(200).json(new ApiResponse(200, decryptedData, "Transaction OTP successful"));
} catch (error) {
throw new ApiError(500, "Transaction OTP failed");
}
}),
accountStatement: AsyncHandler(async (req, res) => {
try {
const jsonData = {
"AGGRID": "TXBCIBTEST001",
"CORPID": "TXBCORP1",
"USERID": "USER1",
"ACCOUNTNO": "010205001809",
"FROMDATE": "01-01-2016",
"TODATE": "30-12-2016",
"URN": "TESTING123"
}
const encryptedData = await cryptoService.encryptWithPublicKey(jsonData)
const result = await iciciApi.accountStatement(encryptedData)
console.log("result", result)
const decryptEncryptedKey = await cryptoService.decryptKey(result.encryptedKey)
const decodedData = await cryptoService.base64Decode(result.encryptedData);
const getIV = await cryptoService.getIV(decodedData);
const statement = await cryptoService.decryptData(decodedData, decryptEncryptedKey, getIV)
res.status(200).json(new ApiResponse(200, statement, "Account Statement Data Fetched Successfully"));
} catch (error) {
throw new ApiError(500, "Account Statement Data failed");
}
}),
RegistrationStatus: AsyncHandler(async (req, res) => {
try {
const jsonData = {
AGGRNAME: "CIBTESTING",
AGGRID: "TXBCIBTEST001",
CORPID: "TXBCORP1",
USERID: "USER1",
URN: "TESTING123"
};
const data = await registrationService.checkRegistrationStatus();
res.status(200).json(new ApiResponse(200, data, "Registration status retrieved"));
} catch (error) {
throw new ApiError(500, "Failed to retrieve registration status");
}
}),
};

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

@@ -0,0 +1,3 @@
module.exports = {
iciciBankController: require('./icici/icicibank.controller')
}

106
src/data/data.json Normal file
View File

@@ -0,0 +1,106 @@
{
"iamType": [
{
"principalTypeName": "OptifiiAdmin"
},
{
"principalTypeName": "CorporateAdmin"
},
{
"principalTypeName": "User"
}
],
"iamSource": [
{
"sourceName": "Web"
},
{
"sourceName": "Mobile"
}
],
"administrator": {
"firstName": "Vinay",
"lastName": "Agarwal",
"ISDcode": "+91",
"mobileNumber": "88888888888",
"emailAddress": "admin@optifii.com",
"password": "Admin@123",
"principalType": "OptifiiAdmin",
"principalSource": "Web"
},
"corporate_status": [
{
"status": "Requested"
},
{
"status": "Submitted"
},
{
"status": "Registerd"
},
{
"status": "Approved"
},
{
"status": "Rejected"
}
],
"corporate_type": [
{
"corporate_type": "C Corporation (C Corp)"
},
{
"corporate_type": "S Corporation (S Corp)"
},
{
"corporate_type": "Limited Liability Company (LLC)"
},
{
"corporate_type": "Nonprofit Corporation"
},
{
"corporate_type": "Benefit Corporation (B Corp)"
},
{
"corporate_type": "Professional Corporation (PC)"
},
{
"corporate_type": "Cooperative (Co-op)"
}
],
"industry": [
{
"industry_name": "Agriculture and Natural Resources"
},
{
"industry_name": "Manufacturing"
},
{
"industry_name": "Construction and Real Estate"
},
{
"industry_name": "Services"
},
{
"industry_name": "Technology"
},
{
"industry_name": "Finance and Insurance"
},
{
"industry_name": "Media and Entertainment"
},
{
"industry_name": "Energy and Utilities"
},
{
"industry_name": "Professional Services"
},
{
"industry_name": "Nonprofit and Community Services"
},
{
"industry_name": "Emerging Industries"
}
]
}

18
src/docs/swaggerDef.js Normal file
View File

@@ -0,0 +1,18 @@
const { version } = require('../../package.json');
const config = require('../config/config');
const swaggerDef = {
openapi: '3.0.0',
info: {
title: 'Tanami Backend API documentation',
version,
description: 'Tanami Backend API documentation',
},
servers: [
{
url: `http://localhost:${config.port}/v1`,
},
],
};
module.exports = swaggerDef;

51
src/index.js Normal file
View File

@@ -0,0 +1,51 @@
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}`);
logger.info(`Is Notification by pass :- ${config.byPassNotification}`);
logger.info(`Is Email by pass :- ${config.byPassEmail}`);
});
})
.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();
}
});

50
src/keys/certificate.pem Normal file
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-----

14
src/keys/public_key.pem Normal file
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-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAqj4v2DMCY3kl5cedsFzM9/jjiYfXbp5PiaXrtGfl9o1kNej4
YGg6+uV4sLclxT66n2mknE7hKLRmMRbjpN3d2NU+KzVx0CKEmJ2RKtCYsTw0iCaA
b2D4hDYZBhdzyBbqdhCgdUhzexFuTtlXlT8U5fZvv+nj8xXrm5Q6z8AuQy/q2AN1
blGaifSbebPgdyhJ3DqgBSP0x67pvyEVWOVerhMw6L6eqfnDHEvaEopynEpm8eQx
kUxroVbERyFfGP/poOb2LSlUqvhK4LOR3Zt1YW/KdcJ+a/8W2zSHyhHVuHwVb4ke
LYC2IdYpcZuwyxN6PX4/k9mLcQbhGUerD+c59U/DhXnOjyHLMc6HtfO4MO5v6DbC
XsZca5qjqBabKJbrpcGZrEQ+6ECriajYySfgUbxppytSBhcIjSzFPKyM4oUgEOFU
LVmoVXo1UjuhaR7Q7NhDEqE4lg67dtGrCTZ/KAITgLhU9CIS6qRes4pO7S/lXfRE
yBaYepcaLsS66dnq9zjXNnHY/L6zwCuyzjt2Im34TXDYfjxkKnGLDvLN15ZGt6Ji
VAFh4Tb2hCF1tWs97VwaZJq/czIBt/jmabGBNJ1wZ2yFshTfeCnvtPlqO6VZeNP7
HqoRF7rVPuePkRiff8E6s2bxisF2pt+2t/swNlZj4pHEoRiEhdJavWLuz+0CAwEA
AQKCAgAUB1NtKdkk106qWSD36/8QSGeKmWDyymy8mrjfhV60cj+BKSq5euG/fUpE
7hMjQMVHdnAPlKcgORMRwOngzlpohXP2NOgyLrFvXBBFW3uvVFAw2WjAUcYDFfM9
OHqeZnXIlJ2wFqlBaBRUcfUAIccFZAgTKcLv6RsKN3bw1KEMtgE4zzVcWHzoMI8v
Ewa8NhD0eDA10pxcdrtYyV1f00Jfk/Hr2+tCP5hhBpp+Fmwz73vrhud++uDpvxHB
9Y7g29DCZZG+T+++Wo5Cn9WwHvdBEpwc3Rgu//iSKvyzvQPqG60Q7W6Pt2YWFoJT
M5gp2B2ISQdVT2F8l2zZoskhCpjSjqMXJxwgg/jp/uXLaTEYNtKCbMM1DSrg1VnQ
78OyhBV7TJBq+hsAwPeuxiaujSv+LjxbeBWHBUZa2Ru2bcN22zzbtAySrTH8TtOw
1UeTkz0lG3alPW1alAYcZF7nNTOv7VAEUhFBgIKF96jgSzUDpkAVClW0gmAwng2u
8zw+Hqtws/UQgeMxoE+Fs+wlwq/GK/219TMoARpdYdgN2l09q9dFZ2d0A2mzvAD1
aUTdgnOrfcel/3IfIBXt8PN51JlASmp7XMVWjVolQ6iG90+zKwpzlZX3XoupyUZj
ChaoR0kwk4GEQgwqlLHTJcrmbDvbJxMmi6f0HFK4s/D19c8xuQKCAQEA0x/mu3I4
dRI46Cgx8BEKH9Isp2t1pCsn4Ai/GhwASRRryi9ZO0pQsR06xayulE1zTSPIg/Mq
wT4Yo/e9QZrRGvhsMSFCU5jHhDHgr77IRoSibZ5C4v99P9Sbi3b3wGsxkiYuttDE
exBy+9HSpRgJxDdXZGEOMSl3+IF/VsZe2eL5JArWBC4XZU/Lm4C3C/HlAA9aVQVb
U+rjlJcL0T9iFzjDiJdzwVxK3oDRVpxyh/hHmD47DZmb7u8Ea1VjIlntrjKymWta
Z/6tPJ81oJftQhSexRHZPFy+Wtm2xfPkENEfsVxtwQQ6Hg7bk9Iwme8Oj/pKFLpI
4W5l4YIqJWX0WQKCAQEAzm3BhEFXOkAy89f9PLx6o1RFqCutx6Silt2rv/SpB4Ju
VWvRLdPZxqQbHQ/5enZ6MEUTCoNzaM2uGbz4TP3718hWW7Sw6QlN1iOYS3eODNNQ
YZIz+IpLJ9hbrWua7LS1Ypb7IO6F+F8lEbue9evyWD2EderA8NckWGEv5JTiBuTT
sXsKNhRuBOBwht7nXwL8s1DEdkok5mLmFlqtIYDpQRm3N9IVa40wn2g+8ww8C7tR
uygDO4PvPnTBhMVEz78MtVOk3MZYjIHhGsJR6iTB0zc3ze6gaVQ3eigyA1Ii/AUy
fke/cPiw30oVRJHnyZFTo06sNXjqtlc+DvSNNIPVtQKCAQEAg/a4vgmTCHovX81g
CXJdJa61gqBElCz1a6+L48IE26kMBwC5gbnd3hcrGUvqg4A1xc3ME832t3sc48CC
Z3NhqL4Gwl7Lmn0wmIykqLVTceCNtn7pyAFyRGecIfxmt7tI7NU49cRgS5vog0aP
p+nykNcWpQOVX11QQ+CNu6uatg6NM94iD9LPhN/voG8/+xNj1DnEeMg0Yau8PLB9
DnnT0jgE4GfqTr5lfdZ/AugfHqYt+hdLyiBtu1djJ8PjhyE201+VCxhzFfW2SuIp
HwxXnKpO41dYtcYypY7YE7tynqBYcwAXCkKeIROgZDFJd65ZJ92GWJZn36ClxeMa
fO3RYQKCAQEAoQFiKnF8Q2bHVwFj9vdbAAE+w0guJsT2O41x5CpDnCPTQrTxVjki
ZpigvS40e5vk+bkmfNDCN0AVRuXpFMQUpd0P3j80rM4g86CXrGT3WnGHBFMwTe4v
aEiMWNrQ30ajIt53yNvBilNLamVYOp9pSgHRStdq3W4wXu6OmE63bIEVzBwXfs64
cP1NRyScebKuvn2Efm6eEUOaCl13I+aUB76y9MrbAiQBhJOeZZtpSg00VjGDM+xX
sfMG7TOf1BlDJDq1H61ka1Lx7BkcIu/Abaln0SsJ2p1hF9o6B/UMbFzxYSU84DRY
YBA8Pls+2iyLe5hlIN/K0aWCNc/wQsGA8QKCAQB4Yg8kQ/Wz5oX0dmMHC/ygXKmi
aJlXigCWzIwmwrp4uVeGeYh7WTzIyt42aYvbKa7liq0NyFiKBJYUuwmqDaHAu5Il
R4CdaWFu8hD5rN2qYwGpUIJdVUwsR9g6bebDYg8TEknSFzLrDwgxKRCmcEnwsmho
k2GnzUzIb+by/qNTKCByExEoYSsqo/V6oTmFmNp1tp3VEvZvQmy4Px7gehmBxbmx
ONDFz27NOwspEE+TsbIds9W5NmBCIUsgGMiK3bV5F3dwy649Vt0cpyGXkxr8ksfX
3visuVX+95IPVyoY6M+J/p2xcYhJwci1A7Cexw7QcalcmfOm8x8qPtnhH6Lk
-----END RSA PRIVATE KEY-----

39
src/middlewares/auth.js Normal file
View File

@@ -0,0 +1,39 @@
const jwt = require('jsonwebtoken');
const httpStatus = require('http-status');
const { roleRights } = require('../config/roles'); // Replace with your actual JWT secret configuration
const ApiError = require('../utils/handler/ApiError.handler');
const config = require('../config/config');
const verifyCallback = async (req, resolve, reject, requiredRights) => {
const token = req.header("x-auth-token"); // Assuming the token is passed in the Authorization header
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
try {
const decoded = jwt.verify(token, config.jwt.secret);
req.user = decoded
if (requiredRights.length) {
const userRights = roleRights.get(req.user.role);
const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight));
if (!hasRequiredRights && req.params.userId !== req.user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
}
resolve();
} catch (err) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Please authenticate'));
}
};
const auth = (...requiredRights) => async (req, res, next) => {
return new Promise((resolve, reject) => {
verifyCallback(req, resolve, reject, requiredRights);
})
.then(() => next())
.catch((err) => next(err));
};
module.exports = auth;

11
src/middlewares/code.js Normal file
View File

@@ -0,0 +1,11 @@
const { decryptUserId } = require("../services/code/code.service");
const ApiError = require("../utils/handler/ApiError.handler")
const code = (err, req, res, next) => {
const code = req.header("x-auth-code");
if (!code) { throw new ApiError(400, "Code is Required") }
req.corporate_code = decryptUserId(code)
next()
}
module.exports = code

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

@@ -0,0 +1,55 @@
const { UniqueConstraintError, BaseError, ValidationError } = 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, true, 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(", "), messages, true, 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 { default: rateLimit } = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
skipSuccessfulRequests: true,
});
module.exports = {
authLimiter,
};

View File

@@ -0,0 +1,92 @@
const multer = require('multer');
const fs = require('fs')
const path = require('path');
const ApiError = require('../utils/handler/ApiError.handler');
const maxFileSize = 10 * 1024 * 1024; // 10 MB (in bytes)
const allowedFileTypes = [
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"video/mp4",
"video/x-msvideo",
"video/x-matroska",
"video/webm",
"video/ogg",
"video/quicktime"
];
// Define the storage engine function with parameters
const uploader = (folderName) => multer.diskStorage({
destination: function (req, file, cb) {
// Check if the 'public' directory exists, and create it if it doesn't
if (!fs.existsSync("public")) {
try {
fs.mkdirSync("public");
} catch (err) {
return cb(err);
}
}
// Handle directory structure based on corporate_code if available
let directoryPath = `public`;
if (req.corporate_code) {
directoryPath += `/${req.corporate_code}`;
}
directoryPath += `/${folderName}`;
// Check if the directory exists, create it if it doesn't
if (!fs.existsSync(directoryPath)) {
try {
fs.mkdirSync(directoryPath, { recursive: true });
} catch (err) {
return cb(err);
}
}
cb(null, directoryPath);
},
filename: function (req, file, cb) {
const transformedFilename = file.originalname
.trim()
.toLowerCase()
.replace(/[^a-z\d.]+/g, "_")
.replace(/_+/g, "_")
.replace(/\.[^.]+$/, "")
.replace(/\./g, "_")
.replace(/^_|_$/g, "");
cb(
null,
transformedFilename + path.extname(file.originalname)
);
},
});
// Define the file filter function with parameters
const fileFilter = (allowedTypes = allowedFileTypes) => function (req, file, cb) {
if (allowedTypes.includes(file.mimetype)) {
cb(null, true); // Allow the file
} else {
cb(new ApiError(400, "File type not allowed")); // Reject the file
}
};
// Set up the multer storage with parameters and file filter
const storage = (folderName, allowedTypes, options = {}) => multer({
storage: uploader(folderName || "asset"),
limits: {
fileSize: options.fileSize || maxFileSize,
},
fileFilter: fileFilter(allowedTypes),
...options, // Merge additional options
});
// Example usage:
// const upload = storage("document", allowedFileTypes, { fileSize: 5 * 1024 * 1024, preservePath: true });
// You can then use `upload` as middleware in your routes.
module.exports = storage;

View File

@@ -0,0 +1,27 @@
const httpStatus = require("http-status");
const ApiError = require("../utils/handler/ApiError.handler");
const pick = require("../utils/handler/pick.handler");
const validate = (schema) => (req, res, next) => {
const validSchema = pick(schema, ['params', 'query', 'body', 'file', 'files']);
const object = pick(req, Object.keys(validSchema));
const promises = Object.keys(validSchema).map((key) =>
validSchema[key].validate(object[key], { abortEarly: false })
);
Promise.all(promises)
.then((validatedValues) => {
validatedValues.forEach((value, index) => {
const key = Object.keys(validSchema)[index];
req[key] = value;
});
next();
})
.catch((err) => {
const errorMessage = err.inner.map((detail) => detail.message).join(', ');
next(new ApiError(httpStatus.BAD_REQUEST, errorMessage));
});
};
module.exports = validate;

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 = {};
//main models
// db.token = require('./main/token.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;

View File

@@ -0,0 +1,5 @@
const iciciRoutes = require('express').Router()
iciciRoutes.use('/', require('./user.routes'))
module.exports = iciciRoutes

View File

@@ -0,0 +1,10 @@
const { iciciBankController } = require('../../../controllers/index')
const userRoutes = require('express').Router();
userRoutes.route('/create-otp').post(iciciBankController.createOTP);
userRoutes.route('/balance-inquiry').post(iciciBankController.balanceInquiry);
userRoutes.route('/transaction-otp').post(iciciBankController.transactionOTP);
userRoutes.route('/account-statement').post(iciciBankController.accountStatement);
module.exports = userRoutes;

27
src/routes/v1/index.js Normal file
View File

@@ -0,0 +1,27 @@
const express = require('express');;
const config = require('../../config/config');
const router = express.Router();
const defaultRoutes = [
{
path: '/icici',
route: require('./icici'),
},
];
const devRoutes = [
// routes available only in development mode
];
defaultRoutes.forEach((route) => {
router.use(route.path, route.route);
});
/* istanbul ignore next */
if (config.env === 'development') {
devRoutes.forEach((route) => {
router.use(route.path, route.route);
});
}
module.exports = router;

View File

@@ -0,0 +1,60 @@
const db = require("../../models");
const iamConstant = require("../../utils/constant/iam.constant");
const ApiError = require("../../utils/handler/ApiError.handler");
module.exports = {
create: async (data) => await db.iam_principal.create(data),
createInvestor: async (data) => {
const source = await db.iam_principal_source.findOne({ where: { sourceName: data?.source || "Mobile" } })
const type = await db.iam_principal_type.findOne({ where: { principalTypeName: data?.type || "Investor" } })
return await db.iam_principal.create({ principalSource_xid: source.id, principalType_xid: type.id, ...data })
},
update: async (data) => await db.iam_principal.update(data, { where: { id: data.id } }),
updateBiometric: async (data) => {
const user = await db.iam_principal.findByPk(data.id)
if (!user) throw new ApiError(400, "User Not found")
return await db.transaction.execute(async (transactionOption) => {
await db.iam_principal.update(data, { where: { id: data.id }, ...transactionOption })
await db.iam_principal_biometric.findCreateFind({
where: { deviceId: data.deviceId, principal_xid: data.id },
defaults: { deviceId: data.deviceId, biometric_type: data.biometric_type, principal_xid: data.id },
...transactionOption
})
})
},
registerWithEmail: async (data) => {
return await db.transaction.execute(async (transactionOptions) => {
await db.iam_principal.update(data, { where: { id: data.id }, ...transactionOptions })
return await db.investor_details.create({ principal_xid: data.id, country_xid: data.country_xid }, transactionOptions)
})
},
getById_lite: async (id) => await db.iam_principal.findByPk(id),
getByID: async (id) => await db.iam_principal.findByPk(id),
getByIdWithBiometric: async (id, deviceId) => await db.iam_principal.findByPk(id, { include: [{ association: "biometrics", where: { deviceId } }] }),
getByNumberWithISDCode: async (mobileNumber, ISDcode) => await db.iam_principal.findOne({ where: { mobileNumber, ISDcode }, include: [{ association: "investor_details" }] }),
togglesStatus: async (id) => {
const user = await db.iam_principal.findByPk(id)
if (!user) throw new ApiError(404, "not Found")
user.IsAppNotificationEnabled = !user.IsAppNotificationEnabled
user.save()
return user
},
togglesNotificationStatus: async (principal_xid, deviceId, playerId, IsAppNotificationEnabled) =>
await db.transaction.execute(async (transactionOptions) => {
await db.iam_principal.update({ IsAppNotificationEnabled: IsAppNotificationEnabled }, { where: { id: principal_xid }, ...transactionOptions })
const [instane, boolean] = await db.iam_principal_notification.findOrCreate({
where: { playerId, deviceId, principal_xid }, defaults: {
principal_xid, deviceId, playerId, IsAppNotificationEnabled,
}, ...transactionOptions
})
if (!boolean) await db.iam_principal_notification.update({ IsAppNotificationEnabled }, { where: { id: instane.id }, ...transactionOptions })
}),
ResetPassword: async (hashedPassword, id) => db.iam_principal.update({ password_hash: hashedPassword }, { where: { id } }),
getByEmail: async (email) => {
return await db.iam_principal.findOne({
where: { emailAddress: email },
include: [{ association: "principal_type", where: { id: iamConstant.principalTypeXid.OPTIFII_ADMIN }, through: { attributes: [] } }]
});
}
}

View File

@@ -0,0 +1,32 @@
const { Op } = require("sequelize")
const db = require("../../models")
module.exports = {
getPrincipalPlayerId: async (principal_xid) => {
const results = await db.iam_principal_notification.findAll({
where: { principal_xid, IsAppNotificationEnabled: true },
attributes: ['playerId']
});
// Map the results to extract playerId directly into an array
return results.map(result => result.playerId);
},
getAllPlayerId: async () => {
return await db.iam_principal_notification.findAll({
where: { IsAppNotificationEnabled: true },
attributes: ['playerId']
},)
},
getSpecificPlayerIds: async (principal_xids) => {
return await db.iam_principal_notification.findAll({
where: {
principal_xid: { [Op.in]: principal_xids }, // No need to wrap `principal_xids` in an array
IsAppNotificationEnabled: true
},
attributes: ['playerId']
})
// .then(result => result.map(record => record.playerId)); // Extracting playerId from each result
}
}

View File

@@ -0,0 +1,8 @@
const db = require("../../models");
module.exports={
get: async (refreshToken) => {
return await db.token.findOne({
where: { token: refreshToken, isblacklisted: false } });
}
}

View File

@@ -0,0 +1,13 @@
const bcrypt = require("bcrypt");
const { hashSync, compareSync, genSaltSync } = bcrypt;
module.exports = {
hash: (data) => {
return hashSync(data, genSaltSync(10));
},
compare: (data, hash) => {
return compareSync(data, hash);
},
};

View File

@@ -0,0 +1,45 @@
const crypto = require('crypto');
const config = require('../../config/config');
const ApiError = require('../../utils/handler/ApiError.handler');
const algorithm = 'aes-256-cbc';
const secretKey = crypto.createHash('sha256').update(config.code.secret).digest();
// Initialize blacklist if it's not already a Set
const blacklist = config.code.blacklist || new Set();
module.exports = {
encryptUserId: (userId) => {
try {
const userIdString = String(userId); // Ensure userId is a string
const iv = crypto.randomBytes(16); // New IV for each encryption
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
let encrypted = cipher.update(userIdString, 'utf8', 'hex');
encrypted += cipher.final('hex');
return `${encrypted}-${iv.toString('hex')}`;
} catch (error) {
throw new ApiError(400, `Encryption failed: ${error.message}`);
}
},
decryptUserId: (encryptedString) => {
try {
if (blacklist.has(encryptedString)) {
throw new ApiError(400, 'The code is blacklisted');
}
const [encryptedData, iv] = encryptedString.split('-');
if (!encryptedData || !iv) {
throw new ApiError(400, 'Invalid code');
}
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.from(iv, 'hex'));
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
throw new ApiError(400, "Invalid Code", [`Decryption failed: ${error.message}`]);
}
},
addToBlacklist: (encryptedString) => {
blacklist.add(encryptedString);
}
};

View File

@@ -0,0 +1,52 @@
const { Op } = require("sequelize");
const db = require("../../models");
const corporateConstant = require("../../utils/constant/corporate.constant");
const iamConstant = require("../../utils/constant/iam.constant");
module.exports = {
getCorporateExist: async (mobileNumber, ISDcode, emailAddress) => await db.corporate.findOne({ where: { mobileNumber, ISDcode, emailAddress } }),
create: async (data) => await db.corporate.create(data),
createWithOtherDetails: async (data) => await db.corporate.create(data, { include: [{ association: 'other_details' }] }),
update: async (data) => await db.corporate.update(data, { where: { id: data.id } }),
updateWithPricipalAndOtherDeatils: async (data) => await db.transaction.execute(async (transactionObject) => {
const principal = await db.iam_principal.create(data.principal, transactionObject)
await db.iam_principal_type_link.create({ principal_xid: principal.id, principalType_xid: data.principal.principalType_xid })
await db.corporate.update(data, { where: { id: data.id }, ...transactionObject })
await db.corporate_other_detail.create(data.other_detail, transactionObject);
await db.corporate_director.bulkCreate(data.directors, transactionObject)
return principal
}),
getCorporateListing: async (query) => {
return await db.pagination({
model: db.corporate,
...query,
where: { is_active: true },
attributes: ['id', 'entity_name', 'corporate_name', 'emailAddress', 'corporate_code', 'onboarding_date', 'opted_for_expence', 'opted_for_benefit', 'opted_for_gifting'],
associations: [
{ association: "industry", attributes: ['id', 'industry_name'], required: false },
{ association: "corporate_type", attributes: ['id', 'corporate_type'], required: false },
{ association: "corporate_status", attributes: ['id', 'status'], required: false }
],
order: [["updatedAt", "ASC"]],
searchableFields: Object.keys(db.corporate.getAttributes()),
})
},
getById: async (id) => await db.corporate.findByPk(id, {
include: [
{ association: 'other_details', required: false },
{ association: 'director', required: false },
{ association: "principal", required: false, attributes: ["userName", "firstName", "lastName", "emailAddress", "ISDcode", "mobileNumber"] }
]
}),
getByID_lite: async (id) => await db.corporate.findByPk(id),
checkIsSubmited: async (id) => await db.corporate.findOne({
where: { id, corporate_status_xid: { [Op.eq]: corporateConstant.statusXid.SUBMITTED } },
include: [{
association: "principal",
attributes: ["fullName"],
required: false,
include: [{ association: "principal_type", where: { id: iamConstant.principalTypeXid.CORPORATE_ADMIN }, through: { attributes: [] } }]
}]
})
}

View File

@@ -0,0 +1,62 @@
const crypto = require("crypto");
const path = require("path");
const fs = require('fs');
const publicKeyPem = fs.readFileSync(path.join(__dirname, '../../keys/public_key.pem'), 'utf8');
const privateKeyPem = fs.readFileSync(path.join(__dirname, '../../keys/rsa_private_key.pem'), 'utf8');
module.exports = {
encryptWithPublicKey: async (jsonData) => {
const buffer = Buffer.from(JSON.stringify(jsonData));
const encrypted = crypto.publicEncrypt({
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_PADDING
}, buffer);
return encrypted.toString("base64");
},
decryptWithPrivateKey: async (encryptedData) => {
try {
const buffer = Buffer.from(encryptedData, 'base64');
const decrypted = crypto.privateDecrypt({
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_PADDING
}, buffer);
const value = decrypted.toString('utf8');
console.log(value)
return JSON.parse(value);
} catch (error) {
console.error('Decryption Error:', error);
throw error;
}
},
decryptKey: async (encryptedKey) => {
const decodedKey = Buffer.from(encryptedKey, 'base64');
const decryptedKey = crypto.privateDecrypt(
{
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_PADDING,
},
decodedKey
);
return decryptedKey;
},
getIV: async (decodedData) => {
return decodedData.slice(0, 16);
},
base64Decode: async (data) => {
return Buffer.from(data, 'base64');
},
decryptData: async (decodedData, sessionKey, iv) => {
const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv);
decipher.setAutoPadding(true);
let decrypted = decipher.update(decodedData.slice(16));
decrypted = Buffer.concat([decrypted, decipher.final()]);
const value = decrypted.toString('utf8');
return JSON.parse(value);
}
};

View File

@@ -0,0 +1,31 @@
const { transporter } = require("../../config/nodemailerConfig");
const config = require("../../config/config");
const logger = require("../../config/logger");
module.exports = {
loginOtp: async (to, otp) => {
try {
if (config.byPassEmail){
logger.info(`Email to ${to} has been bypassed due to bypassEmail configuration.`);
return true;
}
const emailOptions = {
from: config.email.from,
to: to,
subject: "Login OTP",
text: "Login OTP",
html: `<body style="font-family: 'Open Sans', sans-serif; margin: 15px 0; padding: 0; width: 100% !important;">
<div style="padding: 40px 50px 0px 50px; text-align: left;">
<p style="color: #202020; font-weight: 400; font-size: 18px; line-height: 32px;">Hi, your OTP for login is -> ${otp},<br>
Please do not share this otp with anyone.</p>
</div>
</body>`,
};
const result = await transporter.sendMail(emailOptions);
return result;
} catch (error) {
logger.error("Error sending email:", error);
throw new Error("MPIN reset email not sent: " + error.message);
}
},
}

View File

@@ -0,0 +1,65 @@
const { transporter } = require("../../config/nodemailerConfig");
const config = require("../../config/config");
const logger = require("../../config/logger");
module.exports = {
resetOtp: async (to, otp) => {
try {
const emailOptions = {
from: config.email.from,
to: to,
subject: "Reset Your OTP",
text: `We received a request to reset your MPIN. Your One-Time Password (OTP) is: ${otp}. Please use this OTP to reset your MPIN. If you did not request this change, please contact us immediately at <a href="mailto:info@tanamicapital.com" target="_blank" style="color: #468071;">info@tanamicapital.com</a> or +973 3633 1331.`,
html: `<style>
.before::before {
content: url(https://wordpress.betadelivery.com/wdipl2.0/wp-content/uploads/2024/07/Mask.png);
position: absolute;
top: 0;
left: 0;
}
.after::before {
content: url(https://wordpress.betadelivery.com/wdipl2.0/wp-content/uploads/2024/07/Mask.png);
position: absolute;
bottom: -266px;
left: -13px;
transform: rotate(8deg);
}
</style>
<body style="font-family: 'Open Sans', sans-serif; margin: 0; padding: 0; width: 100% !important;">
<!-- header -->
<div style="margin: 0; padding: 0; width: 100%; background-color: #f9f6f3; display: flex; justify-content: center;">
<div style="width: 100%; max-width: 600px; background-color: white;">
<div class="before" style="position: relative; background-color: #002E0F; text-align: center; padding: 30px 50px;">
<a target="_blank">
<img src="https://wordpress.betadelivery.com/wdipl2.0/wp-content/uploads/2024/07/tanami-logo.png" alt="Tanami Logo" style="width: auto;">
</a>
</div>
<div style="padding: 40px 50px 0px 50px; text-align: center;">
<p style="color: #202020; font-weight: 700; font-size: 18px; line-height: 32px;">Reset Your MPIN.</p>
<p style="font-size: 18px; line-height: 28px;">We received a request to reset your MPIN.</p>
<p style="font-size: 18px; line-height: 28px;">Your One-Time Password (OTP) is: <strong>${otp}</strong>.</p>
<p style="padding: 5px 0px; font-size: 18px; line-height: 28px;">
Please use this OTP to reset your MPIN. If you did not request this change, please contact us immediately <br> at
<a href="mailto:info@tanamicapital.com" target="_blank" style="color: #468071;">info@tanamicapital.com</a> or +973 3633 1331
</p>
</div>
<div style="padding: 1px 0px 0px 50px;"><p style="padding: 0px 0px 40px 0px; font-size: 18px; line-height: 32px; margin: 0;">Thank You! <br> Tanami Team</p></div>
<div class="after" style="overflow: hidden; position: relative; background-color: #002E0F; padding: 30px 30px; display: flex; justify-content: flex-start;">
<a href="" style="text-decoration: none; color: #FFFFFF; margin: 0 10px; font-size: 18px;">Disclaimer</a>
<span style="text-decoration: none; color: #FFFFFF; font-size: 18px;">|</span>
<a href="" style="text-decoration: none; color: #FFFFFF; margin: 0 10px; font-size: 18px;">Terms & Conditions</a>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>`,
};
const result = await transporter.sendMail(emailOptions);
return result;
} catch (error) {
logger.error("Error sending email:", error);
throw new Error("MPIN reset email not sent: " + error.message);
}
},
}

View File

@@ -0,0 +1,20 @@
const { Op } = require("sequelize");
const db = require("../../models");
const ApiError = require("../../utils/handler/ApiError.handler");
module.exports = {
create: async (data) => {
await db.iam_principal_otp.destroy({
where: { principal_xid: data.principal_xid, isUsed: { [Op.eq]: false } }, force: true
});
return await db.iam_principal_otp.create(data);
},
getOtp: async (principal_xid) => {
const data = await db.iam_principal_otp.findOne({ where: { principal_xid, isUsed: false }, });
if (!data) { throw new ApiError(400, "Invalid OTP") }
const validTillTimestamp = new Date(data.validTill).getTime();
const currentTime = Date.now();
if (currentTime > validTillTimestamp) { throw new ApiError(400, "OTP has expired") }
return data
}
}

13
src/services/index.js Normal file
View File

@@ -0,0 +1,13 @@
module.exports = {
tokenService: require('./token/token.service'),
iamPrincipalService: require('./IAM/iam_principal.service'),
otpSenderTemplate: require('./emailTemplate/otpSenderTemplate'),
bcryptService: require('./bcrypt/bcrypt.service'),
codeService: require('./code/code.service'),
iamPrincipalOTPService: require('./iam_principal_otp/iamPrincipalOTP.service'),
resetOtpTemplate: require('./emailTemplate/resetOtpTemplate'),
tokenNewService: require('./access/tokenService'),
corporateService: require('./corporate/corporate.service'),
industryService: require('./industry/industry.service'),
cryptoService: require('./crypto/crypto.service'),
}

View File

@@ -0,0 +1,5 @@
const db = require("../../models");
module.exports = {
getAll: async () => await db.industry.findAll(),
};

View File

@@ -0,0 +1,50 @@
const jwt = require('jsonwebtoken')
const config = require("../../config/config");
const moment = require('moment');
const db = require('../../models');
const { tokenTypes } = require('../../config/tokens');
const generateToken = (userId, countryId, languageId, expires, type, secret = config.jwt.secret) => {
const payload = { userId, countryId, languageId, iat: moment().unix(), exp: moment(expires).unix(), type, };
return jwt.sign(payload, secret)
}
const saveToken = async (token, userId, expires, type, isblacklisted = false) => {
return await db.token.create({ token, userId, expires: moment(expires).toDate(), type, isblacklisted })
}
module.exports = {
verifyToken: (token, type) => {
return jwt.verify(token, config.jwt.secret)
},
generateAuthTokens: async (user) => {
const { id, investor_details } = user
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(id, investor_details?.country_xid, investor_details?.defaultLanguage_xid, accessTokenExpires, tokenTypes.ACCESS);
const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = generateToken(id, investor_details?.country_xid, investor_details?.defaultLanguage_xid, null, refreshTokenExpires, tokenTypes.REFRESH);
await saveToken(refreshToken, id, refreshTokenExpires, tokenTypes.REFRESH);
return {
access: {
token: accessToken,
expires: moment(accessTokenExpires).toDate(),
},
refresh: {
token: refreshToken,
expires: moment(refreshTokenExpires).toDate(),
},
};
},
reGenerateAccessTokens: async (user) => {
const { id, investor_details } = user
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(id, investor_details?.country_xid, investor_details?.defaultLanguage_xid, accessTokenExpires, tokenTypes.ACCESS);
return {
access: {
token: accessToken,
expires: moment(accessTokenExpires).toDate(),
}
};
},
generateToken,
}

View File

@@ -0,0 +1,26 @@
module.exports = {
corporateType: {
PRIVATE: "Private",
PUBLIC: "Public",
LLP: "LLP"
},
industryType: {
TECHNOLOGY: "Technology",
HEALTHCARE: "Healthcare",
FINANCE: "Finance",
SERVICES: "Services"
},
corporateStatus: {
REQUESTED: "Requested",
APPROVED: "Approved",
REJECTED: "Rejected",
SUBMITTED: "Submitted"
},
statusXid: {
REQUESTED: 1,
SUBMITTED: 2,
REGISTERD: 3,
APPROVED: 4,
REJECTED: 5,
}
}

View File

@@ -0,0 +1,14 @@
module.exports = {
otpPurpose : {
PASSWORD_RESET : "Password reset",
RESEND: "Resend",
LOGIN: "Login"
},
principalTypeXid: {
OPTIFII_ADMIN: 1,
CORPORATE_ADMIN: 2,
USER: 3
}
}

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;

View File

@@ -0,0 +1,21 @@
const crypto = require('crypto');
// AES encryption function using crypto
function aesEncrypt(trandata, key) {
const iv = Buffer.from("PGKEYENCDECIVSPC"); // 16-byte IV for AES
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(trandata, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
// AES decryption function using crypto
function aesDecryption(encryptedHex, key) {
const iv = Buffer.from("PGKEYENCDECIVSPC");
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
module.exports = { aesEncrypt, aesDecryption };

View File

@@ -0,0 +1,30 @@
const dns = require("node:dns");
// Async function to resolve MX records for a domain
async function resolveMx(domain, recordType) {
return new Promise((resolve, reject) => {
dns.resolveMx(domain, (err, mxRecords) => {
if (err) {
reject(err);
return;
}
const addresses = mxRecords.map((mxRecord) => mxRecord.exchange);
resolve(addresses);
});
});
}
// Async function to check email address validity
async function checkEmailValidity(email) {
try {
const domain = email?.split("@")[1];
const addresses = await resolveMx(domain, "MX");
if (addresses && addresses?.length > 0) {
return true;
}
return false; // No MX record exists
} catch (err) {
return false; // Error occurred
}
}
module.exports = checkEmailValidity

View File

@@ -0,0 +1,12 @@
function isValidFileSize(value) {
// Check the file size here, e.g., to ensure it's not larger than a specific limit.
// Adjust the maximum size as needed (in bytes).
const maxSizeInBytes = 10 * 1024 * 1024; // 10 MB
if (value && value.size <= maxSizeInBytes) {
return true; // Valid file size
}
return false; // Invalid file size
}
module.exports = isValidFileSize

View File

@@ -0,0 +1,36 @@
function generateCompanyCode(input, count) {
let abbreviation;
// Split the input by spaces to check if it's a single word or multiple words
const words = input.split(' ');
// If it's a single word and more than 5 characters
if (words.length === 1 && input.length > 5) {
// Use the first 4 letters of the single word
abbreviation = input.slice(0, 4).toUpperCase(); // "Accenture" -> "ACCE"
} else if (words.length > 1) {
// Multiple words: take the first letter of each word
abbreviation = words.map(word => word[0].toUpperCase()).join(''); // "J. P. Morgan" -> "JPM"
} else {
// For other cases (like short single words), use the input as uppercase
abbreviation = input.toUpperCase();
}
// Get the current year
const currentYear = new Date().getFullYear();
// Increment the count: If the passed count is 0, set it to 1; otherwise, add 1 to the count
const incrementedCount = (count === 0) ? 1 : count + 1;
// Format the incremented count as '0001' with leading zeros
const countString = String(incrementedCount).padStart(4, '0'); // Assuming count is up to 9999
// Combine abbreviation, year, and count to form the code
const code = `${abbreviation}${currentYear}${countString}`;
return code;
}
module.exports = generateCompanyCode

View File

@@ -0,0 +1,131 @@
const { sendNotification } = require("../../services/push_notification/onesignal.service");
// Function to get deposit received message
const getDepositReceivedMessage = amount => ({
en: `Funds Added to Your Wallet: A deposit of $${amount} has been credited to your account.`,
ar: `تمت إضافة أموال إلى محفظتك: تمت إضافة إيداع بقيمة ${amount} دولار إلى حسابك.`
});
// Function to get educational material published message
const getEducationalMaterialPublishedMessage = () => ({
en: 'Learn and Grow! Expand your knowledge with new educational material in our Academy.',
ar: 'تعلم ونم! وسع معرفتك من خلال المواد التعليمية الجديدة في أكاديميتنا.'
});
// Function to get investment opportunity cancelled message
const investmentOpportunityCancelled = investmentName => ({
en: `Investment Opportunity Cancelled: ${investmentName} has been cancelled.`,
ar: `تم إلغاء فرصة الاستثمار: ${investmentName} قد تم إلغاؤه.`
});
// Function to get investor qualification upgrade message
const investorQualificationUpgrade = () => ({
en: 'Congratulations, Investor! Your Investor Qualification has been upgraded. Enjoy enhanced benefits!',
ar: 'تهانينا، مستثمر! تم ترقية مؤهلاتك كمستثمر. استمتع بالمزايا المعززة!'
});
// Function to get KYC status approved message
const kycStatusApproved = () => ({
en: 'KYC Approved: Great news! Your KYC verification is successful.',
ar: 'تمت الموافقة على KYC: أخبار رائعة! تم التحقق من KYC بنجاح.'
});
// Function to get KYC status declined message
const kycStatusDeclined = () => ({
en: 'KYC Update: Your KYC status requires attention. Please pass the verification again.',
ar: 'تحديث KYC: حالة KYC الخاصة بك تتطلب انتباهاً. يرجى إعادة إجراء التحقق.'
});
// Function to get new investment opportunity message
const newInvestmentOpportunity = () => ({
en: 'Discover New Opportunities! A fresh investment opportunity awaits you. Dive in now!',
ar: 'اكتشف الفرص الجديدة! تنتظرك فرصة استثمارية جديدة. ابدأ الآن!'
});
// Function to get subscription investment closed message
const subscriptionInvestmentClosed = () => ({
en: 'Investment Update: Your investment opportunity has just closed.',
ar: 'تحديث الاستثمار: لقد تم إغلاق فرصة استثمارك للتو.'
});
// Function to get subscription investment updated message
const subscriptionInvestmentUpdated = () => ({
en: 'Investment Status Alert: Your investment subscription details have been updated.',
ar: 'تنبيه حالة الاستثمار: تمت تحديث تفاصيل اشتراكك في الاستثمار.'
});
// Function to get withdrawal completed message
const withdrawalCompleted = () => ({
en: 'Withdrawal Complete: Your withdrawal is processed. Check your bank for funds!',
ar: 'اكتمل السحب: تمت معالجة سحبك. تحقق من بنكك للحصول على الأموال!'
});
// Function to send new investment opportunity notification
const sendNewInvestmentOpportunityNotification = async (investmentName, manualDate, manualTime, expectedReturn, playerIds = [], imageUrl = '', additionalData = {}) => {
const title = 'New investment opportunity';
const message = `${investmentName} launching on ${manualDate} at ${manualTime} with an expected return of ${expectedReturn}%. Tap for more details.`;
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Function to send investment open notification
const sendInvestmentOpenNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
const title = `${investmentName} is LIVE`;
const message = `${investmentName} is now open for investment! Tap for more details.`;
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Function to send investment fully subscribed notification
const sendInvestmentFullySubscribedNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
const title = `${investmentName} fully subscribed`;
const message = `${investmentName} has been fully subscribed and is now closed to new investors.`;
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Function to send deposit received notification
const sendDepositReceivedNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
const title = 'Deposit received';
const message = 'A new deposit has been made into your Tanami wallet. Explore exclusive investment opportunities only at Tanami.';
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Function to send distribution notice notification
const sendDistributionNoticeNotification = async (investmentName, playerIds = [], imageUrl = '', additionalData = {}) => {
const title = 'Distribution notice';
const message = `New distribution received regarding your investment in ${investmentName}. Tap for more details.`;
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Function to send KYC approved notification
const sendKYCApprovedNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
const title = 'You\'re verified!';
const message = 'KYC approved - You\'re all set to start investing! Tap to explore the latest exclusive opportunities available.';
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Function to send investor status upgrade notification
const sendInvestorStatusUpgradeNotification = async (playerIds = [], imageUrl = '', additionalData = {}) => {
const title = 'You\'ve been upgraded!';
const message = 'Congrats! You can now enjoy investing with no limits! Tap to explore the latest exclusive opportunities available.';
return await sendNotification(title, message, playerIds, imageUrl, additionalData);
};
// Exporting functions
module.exports = {
getDepositReceivedMessage,
getEducationalMaterialPublishedMessage,
investmentOpportunityCancelled,
investorQualificationUpgrade,
kycStatusApproved,
kycStatusDeclined,
newInvestmentOpportunity,
subscriptionInvestmentClosed,
subscriptionInvestmentUpdated,
withdrawalCompleted,
sendNewInvestmentOpportunityNotification,
sendInvestmentOpenNotification,
sendInvestmentFullySubscribedNotification,
sendDepositReceivedNotification,
sendDistributionNoticeNotification,
sendKYCApprovedNotification,
sendInvestorStatusUpgradeNotification
};

View File

@@ -0,0 +1,16 @@
const config = require("../../config/config");
const generateOTP = () => {
var digits = "0123456789";
let OTP = "";
if (config.byPassOTP) {
OTP = "123456"
} else {
for (let i = 0; i < 6; i++) {
OTP += digits[Math.floor(Math.random() * 10)];
}
}
return OTP;
}
module.exports = generateOTP

View File

@@ -0,0 +1,42 @@
const moment = require('moment');
class TransactionNumberGenerator {
constructor() {
this.timestampMap = new Map();
this.lock = { isLocked: false };
}
_acquireLock() {
while (this.lock.isLocked) { }
this.lock.isLocked = true;
}
_releaseLock() {
this.lock.isLocked = false;
}
// Convert date and time to Unix timestamp
_getUnixTimestamp(dateTime) {
return moment(dateTime, 'YYYY-MM-DD HH:mm:ss').unix();
}
generateUniqueTimestamp(dateTime) {
this._acquireLock();
const timestamp = this._getUnixTimestamp(dateTime);
if (!this.timestampMap.has(timestamp)) {
this.timestampMap.set(timestamp, 0);
}
let counter = this.timestampMap.get(timestamp) + 1;
this.timestampMap.set(timestamp, counter);
this._releaseLock();
// Create unique number with timestamp and counter
const uniqueNumber = ((timestamp % 10000000000) * 1000 + counter).toString().padStart(11, '0');
return uniqueNumber;
}
}
module.exports = TransactionNumberGenerator

View File

@@ -0,0 +1,15 @@
const yup = require('yup');
module.exports={
body: yup.object().shape({
emailAddress: yup
.string()
.email('Invalid email format') // Ensure the email address is in a valid format
.required('Email address is required'), // Validation message for required field
password_hash: yup
.string()
.required('Password hash is required') // Validation message for required field
})
}

View File

@@ -0,0 +1,17 @@
const yup = require('yup');
module.exports = {
body: yup.object().shape({
is_2FA_on: yup
.boolean()
.required('2FA status is required'),
deviceId: yup
.string()
// .matches(/^QKQ1\.\d{6}\.\d{3}$/, 'Invalid device ID format')
.required('Device ID is required'),
biometric_type: yup
.string()
.oneOf(['fingerprint', 'face'], 'Biometric type must be one of: fingerprint, face')
.notRequired(),
}),
};

View File

@@ -0,0 +1,10 @@
const yup = require("yup");
module.exports = {
body: yup.object().shape({
emailAddress: yup
.string()
.email("Invalid email format") // Ensure the email address is in a valid format
.required("Email address is required"), // Validation message for required field
}),
};

View File

@@ -0,0 +1,10 @@
module.exports = {
otpVerification: require('./otpVerification.schema'),
registration: require('./registration.schema'),
login: require('./login.schema'),
biometric: require('./biometric.schema'),
updatePassword: require('./updatePassword.schema'),
resetPassword: require('./resetPaasword.schema'),
adminLogin: require('./admin.schema'),
forgetPassword: require('./forgetPassword.schema')
}

View File

@@ -0,0 +1,16 @@
const yup = require('yup');
module.exports = {
body: yup.object().shape({
mobileNumber: yup
.string()
.required('Mobile number is required')
.matches(/^[0-9]+$/, 'Mobile number must be digits only')
.min(8, 'Mobile number must be at least 8 digits')
.max(10, 'Mobile number must be at most 10 digits'),
ISDcode: yup
.string()
.required('ISD code is required')
.matches(/^\+\d{1,3}$/, 'ISD code must be in the format + followed by 1 to 3 digits'),
password: yup.string().required("Password is required")
}),
}

View File

@@ -0,0 +1,10 @@
const yup = require('yup');
module.exports = {
body: yup.object().shape({
otp: yup
.string()
.required('OTP is required')
.matches(/^[0-9]{6}$/, 'OTP must be exactly 6 digits'),
}),
}

View File

@@ -0,0 +1,41 @@
const yup = require('yup');
const checkEmailValidity = require('../../utils/helper/emailValidator.helper');
module.exports = {
body: yup.object().shape({
firstName: yup
.string()
.required('First name is required')
.min(2, 'First name must be at least 2 characters long')
.max(35, 'First name must be at most 50 characters long'),
lastName: yup
.string()
.required('Last name is required')
.min(2, 'Last name must be at least 2 characters long')
.max(35, 'Last name must be at most 50 characters long'),
emailAddress: yup
.string()
.email("Invalid email address")
.required("Email address is required")
.min(6, "Email address must be at least 6 characters long")
.max(255, "Email address can be at most 255 characters long")
.test("emailValidity", "Email address is invalid", async function (value) {
if (!value) {
return true; // Allow if the field is empty
}
return await checkEmailValidity(value);
}),
password: yup
.string()
.required('Password is required')
.min(8, 'Password must be at least 8 characters long')
.max(16, 'Password must be at most 50 characters long')
.matches(/[A-Z]/, 'Password must contain at least one uppercase letter')
.matches(/[a-z]/, 'Password must contain at least one lowercase letter')
.matches(/[0-9]/, 'Password must contain at least one number')
.matches(/[@$!%*?&#]/, 'Password must contain at least one special character'),
confirmPassword: yup
.string()
.oneOf([yup.ref('password'), null], 'Passwords must match')
.required('Confirm password is required'),
}),
}

View File

@@ -0,0 +1,27 @@
const yup = require('yup');
module.exports = {
body: yup.object().shape({
password: yup
.string()
.required('Password is required')
.min(8, 'Password must be at least 8 characters long')
.max(16, 'Password must be at most 50 characters long')
.matches(/[A-Z]/, 'Password must contain at least one uppercase letter')
.matches(/[a-z]/, 'Password must contain at least one lowercase letter')
.matches(/[0-9]/, 'Password must contain at least one number')
.matches(/[@$!%*?&#]/, 'Password must contain at least one special character'),
confirmPassword: yup
.string()
.oneOf([yup.ref('password'), null], 'Passwords must match')
.required('Confirm new password is required'),
code: yup
.string()
// .matches(/^[A-Za-z0-9-]{36,64}$/, 'Code must be between 36 and 64 alphanumeric characters or hyphens')
.required('Code is required'),
})
}

View File

@@ -0,0 +1,22 @@
const yup = require('yup');
module.exports = {
body: yup.object().shape({
oldPassword: yup
.string()
.required('Old password is required'),
newPassword: yup
.string()
.required('Password is required')
.min(8, 'Password must be at least 8 characters long')
.max(16, 'Password must be at most 50 characters long')
.matches(/[A-Z]/, 'Password must contain at least one uppercase letter')
.matches(/[a-z]/, 'Password must contain at least one lowercase letter')
.matches(/[0-9]/, 'Password must contain at least one number')
.matches(/[@$!%*?&#]/, 'Password must contain at least one special character'),
confirmNewPassword: yup
.string()
.oneOf([yup.ref('newPassword'), null], 'Passwords must match')
.required('Confirm new password is required')
}),
}

View File

@@ -0,0 +1,9 @@
const yup = require('yup');
module.exports = {
body: yup.object().shape({
code: yup
.string()
// .matches(/^[A-Za-z0-9-]{36,64}$/, 'Code must be between 36 and 64 alphanumeric characters or hyphens')
.required('Code is required'),
}),
}

View File

@@ -0,0 +1,11 @@
const yup = require('yup');
module.exports = {
params: yup.object().shape({
id: yup
.number()
.nullable()
.typeError("ID must be a number")
.positive("ID must be a positive number")
.required('ID is required')
}),
}

View File

@@ -0,0 +1,5 @@
module.exports = {
search: require('./search.schema'),
id: require('./id.schema'),
code: require('./code.schema')
}

View File

@@ -0,0 +1,8 @@
const yup = require('yup');
module.exports = {
query: yup.object().shape({
page: yup.number().integer().min(1).optional().default(1),
size: yup.number().integer().min(1).optional().default(10),
search: yup.string().max(100).optional(),
}),
}

View File

@@ -0,0 +1,38 @@
const yup = require('yup');
const { email } = require('../../config/config');
const checkEmailValidity = require('../../utils/helper/emailValidator.helper');
module.exports = {
body: yup.object().shape({
entity_name: yup
.string()
.required('Entity name is required')
.max(100, 'Entity name should not be greater than 100 characters'),
corporate_name: yup
.string()
.required('Company name is required.')
.max(100, 'Corporate name should not be greater than 100 characters'),
emailAddress: yup
.string()
.email("Invalid email address")
.required("Email address is required")
.min(6, "Email address must be at least 6 characters long")
.max(255, "Email address can be at most 255 characters long")
.test("emailValidity", "Email address is invalid", async function (value) {
if (!value) {
return true; // Allow if the field is empty
}
return await checkEmailValidity(value);
}),
mobileNumber: yup
.string()
.required('Mobile number is required')
// .length(10, 'Mobile number must be exactly 10 digits')
.matches(/^[0-9]+$/, 'Mobile number must be digits only')
.max(15, 'Mobile number corporate must be at most 15 digits'),
ISDcode: yup
.string()
.required('ISD code is required')
.matches(/^\+\d{1,3}$/, 'ISD code must be in the format + followed by 1 to 3 digits'),
}),
}

View File

@@ -0,0 +1,99 @@
const yup = require('yup');
const checkEmailValidity = require('../../utils/helper/emailValidator.helper');
module.exports = {
body: yup.object().shape({
full_name_principal: yup
.string()
.required('Full name is required')
.max(100, 'Full name must be at most 100 characters long'),
emailAddress_principal: yup
.string()
.email("Invalid email principal address")
.required("Email address is required")
.max(100, "Email address can be at most 100 characters long")
.test("emailValidity", "Email address pricnipal is invalid", async function (value) {
if (!value) {
return true; // Allow if the field is empty
}
return await checkEmailValidity(value);
}),
ISDCode_principal: yup
.string()
.required('ISD code is required')
.matches(/^\+\d{1,3}$/, 'ISD code must be in the format + followed by 1 to 3 digits'),
mobileNumber_principal: yup
.string()
.required('Mobile number is required')
.matches(/^[0-9]+$/, 'Mobile number must be digits only')
.max(15, 'Mobile number must be at most 15 digits'),
corporate_name: yup
.string()
.required('Corporate name is required')
.max(100, 'Corporate name must be at most 100 characters long'),
industry_xid: yup
.number()
.nullable()
.typeError("Industry ID must be a number")
.positive("Industry ID must be a positive number")
.required('Industry ID is required'),
ISDcode_corporate: yup
.string()
.required('ISD code corporate is required')
.matches(/^\+\d{1,3}$/, 'ISD code corporate must be in the format + followed by 1 to 3 digits'),
mobileNumber_corporate: yup
.string()
.required('Mobile number corporate is required')
.matches(/^[0-9]+$/, 'Mobile number corporate must be digits only')
.max(15, 'Mobile number corporate must be at most 15 digits'),
logo_path_file_name: yup
.string()
.nullable()
.notRequired(),
cin_number: yup
.string()
.max(20, "CIN number must be at most 20 digits"),
pancard_number: yup
.string()
.max(20, "CIN number must be at most 20 digits"),
gst_number: yup
.string()
.max(20, "CIN number must be at most 20 digits"),
gst_file_path_name: yup
.string()
.nullable()
.notRequired(),
pancard_file_path_name: yup
.string()
.nullable()
.notRequired(),
opted_for_expence: yup.boolean().default(false).required('Expence is required'),
opted_for_gifting: yup.boolean().default(false).required('Gifting is required'),
opted_for_benefit: yup.boolean().default(false).required('Benefit is required'),
directors: yup.array().of(
yup.object().shape({
director_name: yup
.string()
.required('User name is required')
.max(100, 'User name must be at most 100 characters long'),
emailAddress: yup
.string()
.email("Invalid email director address")
.required("Email address is required")
.max(100, "Email address can be at most 100 characters long"),
ISDcode: yup
.string()
.required('ISD code corporate is required')
.matches(/^\+\d{1,3}$/, 'ISD code corporate must be in the format + followed by 1 to 3 digits'),
mobileNumber: yup
.string()
.required('Mobile number corporate is required')
.matches(/^[0-9]+$/, 'Mobile number corporate must be digits only')
.max(15, 'Mobile number corporate must be at most 15 digits'),
pancard_file_path_name: yup
.string()
.notRequired(),
}).optional()
)
}),
}

View File

@@ -0,0 +1,4 @@
module.exports = {
corporateQuickAdd: require('./corporateQuickAdd.schema'),
registerWithCode: require('./createCorporate.schema')
}

View File

@@ -0,0 +1,10 @@
const yup = require('yup');
const isValidFileSize = require('../../utils/helper/fileValidator.helper');
module.exports = {
file: yup.mixed().test('isFile', 'File size exceeds limit', (value) => {
if (!value) return true; // No attachment provided is allowed.
return isValidFileSize(value);
}).required("Document is equired"),
}

View File

@@ -0,0 +1,3 @@
module.exports = {
allSchema: require('./all.schema')
}

View File

@@ -0,0 +1,3 @@
module.exports = {
registrationSchema: require('./registration.schema')
}

View File

@@ -0,0 +1,99 @@
const yup = require('yup');
const checkEmailValidity = require('../../utils/helper/emailValidator.helper');
module.exports = {
body: yup.object().shape({
AGGRNAME: yup
.string()
.required('Full name is required')
.max(100, 'Full name must be at most 100 characters long'),
emailAddress_principal: yup
.string()
.email("Invalid email principal address")
.required("Email address is required")
.max(100, "Email address can be at most 100 characters long")
.test("emailValidity", "Email address pricnipal is invalid", async function (value) {
if (!value) {
return true; // Allow if the field is empty
}
return await checkEmailValidity(value);
}),
ISDCode_principal: yup
.string()
.required('ISD code is required')
.matches(/^\+\d{1,3}$/, 'ISD code must be in the format + followed by 1 to 3 digits'),
mobileNumber_principal: yup
.string()
.required('Mobile number is required')
.matches(/^[0-9]+$/, 'Mobile number must be digits only')
.max(15, 'Mobile number must be at most 15 digits'),
corporate_name: yup
.string()
.required('Corporate name is required')
.max(100, 'Corporate name must be at most 100 characters long'),
industry_xid: yup
.number()
.nullable()
.typeError("Industry ID must be a number")
.positive("Industry ID must be a positive number")
.required('Industry ID is required'),
ISDcode_corporate: yup
.string()
.required('ISD code corporate is required')
.matches(/^\+\d{1,3}$/, 'ISD code corporate must be in the format + followed by 1 to 3 digits'),
mobileNumber_corporate: yup
.string()
.required('Mobile number corporate is required')
.matches(/^[0-9]+$/, 'Mobile number corporate must be digits only')
.max(15, 'Mobile number corporate must be at most 15 digits'),
logo_path_file_name: yup
.string()
.nullable()
.notRequired(),
cin_number: yup
.string()
.max(20, "CIN number must be at most 20 digits"),
pancard_number: yup
.string()
.max(20, "CIN number must be at most 20 digits"),
gst_number: yup
.string()
.max(20, "CIN number must be at most 20 digits"),
gst_file_path_name: yup
.string()
.nullable()
.notRequired(),
pancard_file_path_name: yup
.string()
.nullable()
.notRequired(),
opted_for_expence: yup.boolean().default(false).required('Expence is required'),
opted_for_gifting: yup.boolean().default(false).required('Gifting is required'),
opted_for_benefit: yup.boolean().default(false).required('Benefit is required'),
directors: yup.array().of(
yup.object().shape({
director_name: yup
.string()
.required('User name is required')
.max(100, 'User name must be at most 100 characters long'),
emailAddress: yup
.string()
.email("Invalid email director address")
.required("Email address is required")
.max(100, "Email address can be at most 100 characters long"),
ISDcode: yup
.string()
.required('ISD code corporate is required')
.matches(/^\+\d{1,3}$/, 'ISD code corporate must be in the format + followed by 1 to 3 digits'),
mobileNumber: yup
.string()
.required('Mobile number corporate is required')
.matches(/^[0-9]+$/, 'Mobile number corporate must be digits only')
.max(15, 'Mobile number corporate must be at most 15 digits'),
pancard_file_path_name: yup
.string()
.notRequired(),
}).optional()
)
}),
}

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

@@ -0,0 +1,3 @@
module.exports = {
iciciBankAPIVaildation: require('./icici')
}

2022
yarn.lock Normal file

File diff suppressed because it is too large Load Diff