Initial source code import

This commit is contained in:
2022-02-13 06:31:11 +00:00
commit 5819796c82
26 changed files with 807 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules
package-lock.json
*/upload/*
!*/upload/.gitkeep
+103
View File
@@ -0,0 +1,103 @@
# Send SMS
Simple API gateway to send SMS messages through Clickatell.
## Description
Node.js project to utilise PayLid/IRAS registered API account from Clickatell.
Exposes RESTful interface to send SMS messages.
## Getting Started
### Dependencies
* designed to run on CentOS 6.10 & node.js v11.15.0
* express.js framework
* no database interface since recent pg-native will not compile on CentOS 6.10
### Installing
* git clone git@gitlab.com:paylidproductteam/sms-api.git
* npm install
### Executing program
* To run API in development mode, please use "dev" script from package.json
* Start by
```
npm run dev
```
### Calling API
```
curl -X POST http://10.10.20.101:4100/v1/sms/send \
-H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoidHJ1ZSIsImlhdCI6MTYzMDY3NTIxMH0.9aqqaT1h7k_JUqUT3cwOvB0RvK51sQuuQuw6-o-6ufc' \
-H 'Content-Type: application/json' \
-d '{"number":"16784574356","text":"test from SMS API"}'
```
### Getting Message Delivery Status
```
curl -X POST http://10.10.20.101:4100/v1/sms/status \
-H 'authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc0FkbWluIjoidHJ1ZSIsImlhdCI6MTYzMDY3NTIxMH0.9aqqaT1h7k_JUqUT3cwOvB0RvK51sQuuQuw6-o-6ufc' \
-H 'Content-Type: application/json' \
-d '{"apiMessageId":"c7bfb6061000c8805e2cde9a5bdc9139"}'
```
## Run Under Supervisor
Append the following configuration to /etc/supervisord.conf
```
[program:sms_api]
command=/home/developer/oameye/sms-api/start.sh
directory=/home/developer/oameye/sms-api
process_name=%(program_name)s
numprocs=1
umask=022
priority=1
user=oameye
group=dev
autostart=true
autorestart=true
redirect_stderr=true
log_stdout=true
log_stderr=true
stdout_logfile=/var/log/supervisor/sms_api.log
```
## Run on Windows
Download https://nssm.cc/release/nssm-2.24.zip and install into node directory.
Usage https://nssm.cc/usage
```
nssm install SmsApiService C:\AutoMedSys\sms-api\start.bat
nssm set SmsApiService AppDirectory C:\AutoMedSys\sms-api
nssm set SmsApiService DisplayName "SMS API Service"
nssm set SmsApiService Description "SMS RESTful API for Clickatell gateway"
nssm set SmsApiService Start SERVICE_AUTO_START
nssm set SmsApiService AppExit Default Restart
nssm set SmsApiService AppRestartDelay 0
nssm set SmsApiService AppStdout C:\AutoMedSys\sms-api\service.log
nssm set SmsApiService AppStderr C:\AutoMedSys\sms-api\service.log
nssm start SmsApiService
```
## Authors
Contributors names and contact info
Anatolii Okhotnikov
[@acidumirae](https://gitlab.com/acidumirae)
## Version History
* 0.3
* Windows service startup instruction
* Make middleware optional
* 0.2
* Business logic for clickatell SMS API send/status
* See [commit change]() or See [release history]()
* 0.1
* Initial commit with express.js API skeleton
+32
View File
@@ -0,0 +1,32 @@
{
"dev": {
"name": "Paylid SMS API - Development Mode",
"port": 4100,
"mode": "development",
"protocol": "http",
"serverUrl": "localhost",
"serverUrlWebUrlLink": "localhost:4200/",
"database": {
"port": 5432,
"host": "localhost",
"user": "iras",
"password": "iras",
"database": "iras"
},
"email": {
"host": "smtp.gmail.com",
"port": "587",
"username": "support_test2@paylid.com",
"password": "may12002"
},
"clickatell": {
"phone_from": "12014925256",
"base_url": "https://api.clickatell.com/rest",
"api_key": "cZcCI12HIYIWa5SXZfYcSFTsFNKh5uw1yn3YXoct38cS54qON2xEJDxEajCPxUDKekKRE2sN9bRT3o.S"
},
"middleware": {
"enabled": true,
"secret": "secret"
}
}
}
+32
View File
@@ -0,0 +1,32 @@
{
"production": {
"name": "Paylid SMS API - Production Mode",
"port": 4300,
"mode": "production",
"protocol": "http",
"serverUrl": "",
"serverUrlWebUrlLink": "",
"database": {
"port": 5432,
"host": "localhost",
"user": "iras",
"password": "iras",
"database": "iras"
},
"email": {
"host": "smtp.gmail.com",
"port": "587",
"username": "support@paylid.com",
"password": "may12002"
},
"clickatell": {
"phone_from": "12014925256",
"base_url": "https://api.clickatell.com/rest",
"api_key": "cZcCI12HIYIWa5SXZfYcSFTsFNKh5uw1yn3YXoct38cS54qON2xEJDxEajCPxUDKekKRE2sN9bRT3o.S"
},
"middleware": {
"enabled": true,
"secret": "secret"
}
}
}
+32
View File
@@ -0,0 +1,32 @@
{
"staging": {
"name": "Project Name - Staging Mode",
"port": 4500,
"mode": "staging",
"protocol": "http",
"serverUrl": "",
"serverUrlWebUrlLink": "",
"database": {
"port": 5432,
"host": "localhost",
"user": "iras",
"password": "iras",
"database": "iras"
},
"email": {
"host": "smtp.gmail.com",
"port": "587",
"username": "support_test2@paylid.com",
"password": "may12002"
},
"clickatell": {
"phone_from": "12014925256",
"base_url": "https://api.clickatell.com/rest",
"api_key": "cZcCI12HIYIWa5SXZfYcSFTsFNKh5uw1yn3YXoct38cS54qON2xEJDxEajCPxUDKekKRE2sN9bRT3o.S"
},
"middleware": {
"enabled": true,
"secret": "secret"
}
}
}
+17
View File
@@ -0,0 +1,17 @@
module.exports = {
apps: [{
name: 'PROJECT_NAME',
script: "./src/server.js",
instances: 1,
watch: false,
env_dev: {
NODE_ENV: 'development'
},
env_staging: {
NODE_ENV: 'staging'
},
env_production: {
NODE_ENV: 'production'
}
}],
};
+7
View File
@@ -0,0 +1,7 @@
{
"ignore": [
"node_modules/*",
".tmp/*",
".git/*"
]
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "demo",
"version": "1.0.0",
"description": "Demo Structure",
"main": "index.js",
"scripts": {
"dev": "cross-env NODE_ENV=dev nodemon --inspect=5001 --config nodemon.js src/server.js",
"staging": "cross-env NODE_ENV=staging nodemon --inspect=5001 --config nodemon.js src/server.js",
"production": "cross-env NODE_ENV=production nodemon --inspect=5001 --config nodemon.js src/server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"array-refactor": "^1.0.10",
"async": "3.2.0",
"body-parser": "1.19.0",
"chalk": "4.1.0",
"config": "3.3.3",
"cors": "2.8.5",
"ejs": "3.1.5",
"eslint-config-airbnb": "18.2.1",
"express": "4.17.1",
"express-status-monitor": "^1.3.3",
"express-validator": "6.9.2",
"glob": "7.1.6",
"jsonwebtoken": "8.5.1",
"nodemailer": "6.4.17"
},
"devDependencies": {
"cross-env": "7.0.3",
"eslint": "7.17.0",
"eslint-config-airbnb-base": "14.2.1",
"eslint-config-standard": "16.0.2",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "5.0.0",
"nodemon": "2.0.7"
}
}
+117
View File
@@ -0,0 +1,117 @@
// 40X - Client Side Error
// 50X - Server Side Error
module.exports = {
ERROR_STATUS_ARRAY: [
{
status: "401",
message: "userId or privateKey is wrong.",
data: "userId or privateKey is wrong."
},
{
status: "402",
message: "Mandatory Parameter Empty.",
data: "Mandatory Parameter Empty."
},
{
status: "403",
message: "Mandatory Parameter Missing.",
data: "Mandatory Parameter Missing."
},
{
status: "404",
message: "User already registered.",
data: "User already registered."
},
{
status: "405",
message: "Your password is incorrect.",
data: "Your password is incorrect."
},
{
status: "406",
message: "Please verify your email.",
data: "Please verify your email."
},
{
status: "407",
message: "User Not Exist.",
data: "User Not Exist."
},
{
status: "408",
message: "Verification code was valid for 24 hours, Please click on resend.",
data: "Verification code was valid for 24 hours, Please click on resend."
},
{
status: "409",
message: "Verification code not exists, Please click on resend.",
data: "Verification code not exists, Please click on resend."
},
{
status: "410",
message: "User blocked by admin.",
data: "User blocked by admin."
},
{
status: "411",
message: "User already added.",
data: "User already added."
},
{
status: "412",
message: "Error in password generator.",
data: "Error in password generator."
},
{
status: "413",
message: "Verification code eror",
data: "verification code are not match please try again."
},
{
status: "414",
message: "Token Error",
data: "Invalid token found."
},
{
status: "415",
message: "Token not found",
data: "Token not found in request perameter."
},
{
status: "416",
message: "User not verified",
data: "Your are not verified."
},
{
status: "417",
message: "Authentication error.",
data: "You are not authorized to perform this action."
},
{
status: "501",
message: "Data Not Found.",
data: "Data Not Found."
},
{
status: "502",
message: "API Error",
data: "Error occured while processing API request."
},
{
status: "503",
message: "Database error",
data: "Database operation error."
},
{
status: "504",
message: "Mail error",
data: "Error occure while sending mail."
},
{
status: "505",
message: "Error in file deleteing",
data: "File deleteing error."
},
]
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
SMS: '/v1/sms',
GET_ERROR: '/v1/errors'
}
+36
View File
@@ -0,0 +1,36 @@
const nodemailer = require("nodemailer");
const config = require('config').get(mode);
let sendMail = (to, subject, html) => {
return new Promise((resolve, reject) => {
let transporter = nodemailer.createTransport({
host: config.email.host,
port: config.email.port,
secure: true, // true for 465, false for other ports
auth: {
user: config.email.username,
pass: config.email.password
}
});
let mailOptions = {
from: config.email.username,
to: to,
subject: subject,
html: html,
}
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
reject("There is error in mail:- " + error);
} else {
console.log("Mail has been sent to", to)
resolve("Mail Sent!!!");
}
});
})
};
module.exports = {
sendMail
}
+35
View File
@@ -0,0 +1,35 @@
let jwt = require('jsonwebtoken');
const reqResponse = require('./responseHandler');
const config = require('config').get(mode);
module.exports = {
checkToken
}
function checkToken(req, res, next) {
if (config.middleware.enabled) {
let token = req.headers['x-access-token'] || req.headers['authorization'];
if (token) {
let key = config.middleware.secret;
jwt.verify(token, key, {
ignoreExpiration: true
}, (err, decoded) => {
if (err) {
return res.status(414).send(reqResponse.errorResponse(414));
} else {
if (key === config.middleware.secret) {
decoded.isAdminUser = false;
} else {
decoded.isAdminUser = true;
}
req.decoded = decoded;
next();
}
});
} else {
return res.status(415).send(reqResponse.errorResponse(415));
}
} else {
next();
}
}
+42
View File
@@ -0,0 +1,42 @@
// 4XX status code related to client side error
// 5XX status code related to server side error
const getErrorStatus = require('../constant/ErrorData');
function findErrorMessage(status, errMessage) {
let data = getErrorStatus.ERROR_STATUS_ARRAY.find(v => v.status == status) || { error: 'There must be an error' };
if (errMessage) { // override default message
data['data'] = errMessage;
}
return data;
}
/**
* Success Reposnse.
* @param {number} status - Success response status
* @param {string} succMessage - Success response message
* @param {any} data - Success response custom data
*/
let successResponse = (status, succMessage, data) => {
return {
status,
message: succMessage,
data
}
}
/**
* Error Reposnse.
* @param {Response} res - Send error response
* @param {number} statusCode - Error Status Code
* @param {string} errMessage - Error response message
*/
let errorResponse = (statusCode, errMessage) => {
return findErrorMessage(statusCode, errMessage);
}
module.exports = {
errorResponse,
successResponse,
};
+8
View File
@@ -0,0 +1,8 @@
const glob = require('glob');
module.exports = (app) => {
glob(`${__dirname}/routes/**/*Routes.js`, {}, (er, files) => {
if (er) throw er;
files.forEach((file) => require(file)(app));
});
};
@@ -0,0 +1,16 @@
const reqResponse = require('../../cors/responseHandler');
const getErrorStatus = require('../../constant/ErrorData');
module.exports = {
getAllErrorData: async (req, res) => {
res.status(201).send(reqResponse.successResponse(201, "Fetch All Error Code", getErrorStatus.ERROR_STATUS_ARRAY));
},
getErrorDataByCode: (req, res) => {
let params = req.params;
let findErrorCode = getErrorStatus.ERROR_STATUS_ARRAY.find(c => c.status == params.code);
res.status(201).send(reqResponse.successResponse(201, "Fetch Error Code", findErrorCode));
}
}
@@ -0,0 +1,16 @@
const router = require('express').Router();
const ErrorResponseController = require('./ErrorResponseController');
const RouteConstant = require('../../constant/Routes');
module.exports = (app) => {
router.route('/getAllErrorData')
.get(ErrorResponseController.getAllErrorData);
router.route('/getErrorDataByCode/:code')
.get(ErrorResponseController.getErrorDataByCode);
app.use(
RouteConstant.GET_ERROR,
router
);
};
+42
View File
@@ -0,0 +1,42 @@
const SmsSendServices = require('../../services/SmsSendServices');
const reqResponse = require('../../cors/responseHandler');
const { validationResult } = require('express-validator');
module.exports = {
sendSms: async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(402).send(reqResponse.errorResponse(402));
}
let data = req.body;
let params = req.params;
let query = req.query;
try {
let result = await SmsSendServices.sendSms(data, params, query);
res.status(201).send(reqResponse.successResponse(201, "SMS Sending Report", result));
} catch (error) {
console.error(error);
res.status(502).send(reqResponse.errorResponse(502, error))
}
},
verifySms: (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(402).send(reqResponse.errorResponse(402));
}
let data = req.body;
let params = req.params;
let query = req.query;
SmsSendServices.verifySms(data, params, query)
.then((result) => {
res.status(201).send(reqResponse.successResponse(201, "SMS Status Report", result));
})
.catch((error) => {
console.error(error);
res.status(502).send(reqResponse.errorResponse(502, error));
})
}
}
+25
View File
@@ -0,0 +1,25 @@
const router = require('express').Router();
const SmsSendController = require('./SmsSendController');
const RouteConstant = require('../../constant/Routes');
const Middleware = require('../../cors/middleware').checkToken;
const Validation = require('../../validation/SmsValidation')
module.exports = (app) => {
router.route('/send')
.post(
Validation.send(),
SmsSendController.sendSms
);
router.route('/status')
.post(
Validation.verify(),
SmsSendController.verifySms
);
app.use(
RouteConstant.SMS,
Middleware,
router
);
};
+55
View File
@@ -0,0 +1,55 @@
const express = require('express');
const bodyParser = require('body-parser');
const chalk = require('chalk');
const cors = require('cors');
const app = express();
const pack = require('../package');
//const { Pool, Client } = require('pg');
const path = require('path');
// if NODE_ENV value not define then dev value will be assign
mode = process.env.NODE_ENV || 'dev';
// mode can be access anywhere in the project
const config = require('config').get(mode);
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
// use only when you want to see the metric related to express app
// app.use(require('express-status-monitor')());
require('./routes')(app);
const dir = path.join(__dirname, 'assets');
app.use('/upload', express.static(dir));
const start = () => (
app.listen(config.port, () => {
console.log(chalk.yellow('.......................................'));
console.log(chalk.green(config.name));
console.log(chalk.green(`Port:\t\t${config.port}`));
console.log(chalk.green(`Mode:\t\t${config.mode}`));
console.log(chalk.green(`App version:\t${pack.version}`));
console.log(chalk.green("database connection is established"));
console.log(chalk.yellow('.......................................'));
})
);
dbConnection = () => {
/*// PostgreSQL database connection start
const databaseConfig = config.database;
// con can be access anywhere in the project
con = new Pool(databaseConfig);
app.set('con', con);
//*/
app.set('config',config);
start();
}
dbConnection();
+90
View File
@@ -0,0 +1,90 @@
const https = require('https');
const config = require('config').get(mode);
module.exports = {
sendSms: async (data, params, query) => {
return new Promise((resolve, reject) => {
let urlstr = config.clickatell.base_url + '/message';
console.log("Clickatell " + urlstr);
const RestBody = JSON.stringify({
text: data.text,
to: [ data.number ],
from: config.clickatell.phone_from,
mo: 1
});
console.log( RestBody );
const options = {
hostname: 'api.clickatell.com',
port: 443,
path: '/rest/message',
method: 'POST',
headers: {
'X-Version': '1',
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'bearer ' + config.clickatell.api_key,
'Content-Length': RestBody.length
}
};
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`)
res.on('data', d => {
process.stdout.write(d);
const responseData = JSON.parse(d.toString('utf8'));
if (responseData.error) {
reject(responseData.error);
} else {
resolve({response: responseData});
}
});
});
req.on('error', error => {
console.error(error);
reject(error);
});
req.write(RestBody);
req.end();
});
},
verifySms: async (data, params, query) => {
return new Promise((resolve, reject) => {
let urlstr = config.clickatell.base_url + '/message/' + data.apiMessageId;
console.log("Clickatell " + urlstr);
const options = {
method: 'GET',
headers: {
'X-Version': '1',
'Accept': 'application/json',
'Authorization': 'bearer ' + config.clickatell.api_key
}
};
const req = https.request(urlstr, options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
const responseData = JSON.parse(d.toString('utf8'));
resolve({response: responseData});
});
});
req.on('error', (error) => {
console.error(error);
reject(error)
});
req.end();
});
},
}
+10
View File
@@ -0,0 +1,10 @@
const jwt = require('jsonwebtoken');
let token = jwt.sign({ isAdmin: 'true' }, 'secret');
// https://www.npmjs.com/package/jsonwebtoken
// iat: Math.floor(Date.now() / 1000) // issued now
// exp: Math.floor(Date.now() / 1000) + (60 * 60) // expires in 1 hr
console.log(token);
View File
+16
View File
@@ -0,0 +1,16 @@
const { body, check } = require('express-validator');
module.exports = {
send: () => {
return [
check("number", "Phone number is required!").not().isEmpty(),
check("text", "SMS text is required!").not().isEmpty(),
]
},
verify: () => {
return [
check("apiMessageId", "apiMessageId is required!").not().isEmpty(),
]
}
}
+6
View File
@@ -0,0 +1,6 @@
cd /d %~dp0
rem "C:\Program Files (x86)\nodejs\npm.cmd" run dev > app.log 2>&1
:loop
npx cross-env NODE_ENV=dev nodemon --inspect=5001 --config nodemon.js src/server.js
rem 1> app.log 2>&1
goto loop
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
#
cd /home/chiefsoft/NODES/sms-api
#export PATH="/usr/local/v11.15.0/bin:$PATH"
export NODE_ENV=dev
npm run dev
+16
View File
@@ -0,0 +1,16 @@
[program:sms_api]
command=/home/developer/oameye/sms-api/start.sh
directory=/home/developer/oameye/sms-api
process_name=%(program_name)s
numprocs=1
umask=022
priority=1
user=oameye
group=dev
autostart=true
autorestart=true
redirect_stderr=true
log_stdout=true
log_stderr=true
stdout_logfile=/var/log/supervisor/sms_api.log