From 2b4a01a525e0d0d73670ac5e1742769a826babc3 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Fri, 23 Feb 2024 16:40:46 -0500 Subject: [PATCH] first commit --- .env | 2 + Dockerfile | 13 ++++ api/controller.js | 51 +++++++++++++ api/routes.js | 16 +++++ app/db.js | 57 +++++++++++++++ app/index.js | 10 +++ app/logger.js | 61 ++++++++++++++++ docker-compose.yml | 18 +++++ package.json | 35 +++++++++ server.js | 12 ++++ service/jobs.js | 175 +++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 450 insertions(+) create mode 100644 .env create mode 100644 Dockerfile create mode 100644 api/controller.js create mode 100644 api/routes.js create mode 100644 app/db.js create mode 100644 app/index.js create mode 100644 app/logger.js create mode 100644 docker-compose.yml create mode 100644 package.json create mode 100644 server.js create mode 100644 service/jobs.js diff --git a/.env b/.env new file mode 100644 index 0000000..4b80416 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +WRENCHJOB_PORT=3000 +WRENCHJOB_POSTGRE_URL='postgresql://wrenchboard:wrenchboard@10.20.30.60:5432/wrenchboard' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..24c04a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM node:erbium + +# Create app directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install app dependencies +COPY package.json /usr/src/app/ +RUN npm install + +COPY . /usr/src/app + +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/api/controller.js b/api/controller.js new file mode 100644 index 0000000..976d0d2 --- /dev/null +++ b/api/controller.js @@ -0,0 +1,51 @@ +'use strict'; + +const properties = require('../package.json') +const transaction = require('../service/transaction'); +const transfer = require('../service/transfer'); +const account = require('../service/account'); + +var controllers = { + about: function(req, res) { + var aboutInfo = { + name: properties.name, + version: properties.version + } + res.json(aboutInfo); + }, + postTransfer: function(req, res) { + transfer.create(req, res, function(err, dist) { + if (err) { + res.send(err); + } + res.json(dist); + }); + }, + getStatus: function(req, res) { + transfer.get(req, res, function(err, dist) { + if (err) { + res.send(err); + } + res.json(dist); + }); + }, + verifyTransaction: function(req, res) { + transaction.verify(req, res, function(err, dist) { + if (err) { + res.send(err); + } + res.json(dist); + }); + }, + verifyAccount: function(req, res) { + account.verify(req, res, function(err, dist) { + if (err) { + res.send(err); + } + res.json(dist); + }); + }, +}; + +module.exports = controllers; + diff --git a/api/routes.js b/api/routes.js new file mode 100644 index 0000000..58ddab9 --- /dev/null +++ b/api/routes.js @@ -0,0 +1,16 @@ +'use strict'; + +const controller = require('./controller'); + +module.exports = function(app) { + app.route('/about') + .get(controller.about); + app.route('/create') + .post(controller.postTransfer); + app.route('/status/:id') + .get(controller.getStatus); + app.route('/verifyTransaction/:id') + .get(controller.verifyTransaction); + app.route('/verify') + .post(controller.verifyAccount); +}; \ No newline at end of file diff --git a/app/db.js b/app/db.js new file mode 100644 index 0000000..d08ddec --- /dev/null +++ b/app/db.js @@ -0,0 +1,57 @@ +/** + * @file + * Configures the database connection + */ + +const { Pool, Client } = require('pg'); +const url = require('url'); +const logger = require('./logger'); +//const connectionString = 'postgresql://wrenchboard:wrenchboard@10.10.10.23:5432/wrenchboard'; +const connectionString = process.env.POSTGRE_URL.replace(/'/g, ''); +const params = url.parse(connectionString); +const auth = params.auth.split(':'); +const config = { + user: auth[0], + password: auth[1], + host: params.hostname, + port: params.port, + database: params.pathname.split('/')[1], + ssl: false +}; + +const postgres = new Pool(config); + +postgres.on('connect', client => { + logger.info('Connected to Database'); +}); + +postgres.on('acquire', client => { + logger.info('Client is checked out from the DB connection pool'); +}); + +postgres.on('remove', client => { + logger.info('Client is closed & removed from the DB connection pool'); +}); + +postgres.on('error', (err, client) => { + logger.error(err); +}); + +// Connect to PostgreSQL +postgres.connect((err, client, release) => { + logger.info(connectionString); + if (err) { + logger.error('Error acquiring client', err.stack); + return null; + } + client.query('SELECT NOW()', (err, result) => { + release(); + if (err) { + logger.error('Error executing query', err.stack); + return nul; + } + logger.info(result.rows); + }); +}); + +module.exports = postgres; \ No newline at end of file diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..b659952 --- /dev/null +++ b/app/index.js @@ -0,0 +1,10 @@ +/** + * @file + * Set up the app + */ +var _ = require('underscore'); +var db = require('./db'); + +module.exports = function () { + db(); +}; \ No newline at end of file diff --git a/app/logger.js b/app/logger.js new file mode 100644 index 0000000..5b052f6 --- /dev/null +++ b/app/logger.js @@ -0,0 +1,61 @@ +/** + * @file + * Defines logger for the microservice + */ + +var winston = require('winston'); +var Papertrail = require('winston-papertrail').Papertrail; +var serviceIdentifier = require('../package.json').name; + +var host = process.env.LOG_SERVICE_HOST; +var port = process.env.LOG_SERVICE_PORT; + +/** + * Class to provide logging facilities for the microservice + * @constructor + */ + +var Logger = function () { + + // Try to route logs to third party service + if (host && port) { + // Define our transport + var transport = new Papertrail({ + levels: { + debug: 0, + info: 1, + error: 3 + }, + colors: { + debug: 'blue', + info: 'green', + error: 'red' + }, + host, + port, + json: true, + colorize: true, + logFormat: (level, message) => { + return `[${serviceIdentifier}] ${level} : ${message}`; + } + }); + + // Handle exceptions + transport.exceptionsLevel = 'error'; + winston.handleExceptions(transport); + } + + this.info = winston.info; + this.error = winston.error; + this.debug = winston.debug; +}; + +module.exports = new Logger(); + + +/** + * @typedef {Object} Logger + * @property {function} info - Log in level `info` + * @property {function} error - Log in level `error` + * @property {function} debug - Log in level `debug` + */ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..402ae3e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + wrenchboard-jobs-micro: + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + # image: registry.chiefsoft.net/flutterwave-transfer-micro:latest + volumes: + - ./:/app + - '/app/node_modules' + ports: + - 9086:3000 + environment: + - PORT=${WRENCHJOB_PORT} + - WRENCHJOB_POSTGRE_URL=${FLUTTERWAVE_POSTGRE_URL} +volumes: + src: \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c9ad8d1 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "wrenhboard-job-micro", + "version": "0.0.4", + "description": "A microservice to handle wrenchboard job listing", + "main": "server.js", + "scripts": { + "generate-docs": "jsdoc ./api/*.js ./api/**/*.js ./app/*.js ./app/**/*.js ./service/*.js ./service/**/*.js -d docs", + "lint": "eslint ./ --ext .js", + "start": "node server.js", + "start:dev": "nodemon server.js", + "test": "npm run lint" + }, + "author": "WrenchBoard Support ", + "dependencies": { + "axios": "^0.24.0", + "body-parser": "^1.19.0", + "express": "^4.17.1", + "openapi-types": "^10.0.0", + "pg": "8.7.1", + "pg-pool": "^3.5.1", + "request": "^2.88.2", + "swagger-autogen": "^2.17.2", + "swagger-jsdoc": "^6.1.0", + "swagger-ui-express": "^4.3.0", + "underscore": "^1.8.3", + "url": "^0.11.0", + "winston": "^2.3.1", + "winston-papertrail": "^1.0.4" + }, + "devDependencies": { + "eslint": "^1.10.3", + "jsdoc": "^3.4.3", + "nodemon": "^1.11.0" + } + } \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..b5fd14c --- /dev/null +++ b/server.js @@ -0,0 +1,12 @@ +const express = require('express'); +const logger = require('./app/logger'); +const port = process.env.PORT || 3000; + +const app = express(); + +const routes = require('./api/routes'); +routes(app); + +app.listen(port, "0.0.0.0", function() { + logger.info('***** Server started on port: ' + port + ' *****'); +}); diff --git a/service/jobs.js b/service/jobs.js new file mode 100644 index 0000000..30f6852 --- /dev/null +++ b/service/jobs.js @@ -0,0 +1,175 @@ +'use strict'; + +const axios = require('axios'); +const request = require('request'); +const db = require('../app/db') +const logger = require('../app/logger'); + +// https://developer.flutterwave.com/reference/create-a-transfer +const flutterwaveConfigBuffer = new Buffer(process.env.FLUTTERWAVE_API, 'base64'); +const flutterwaveConfig = JSON.parse(flutterwaveConfigBuffer.toString('ascii')); + +var transfer = { + create: function(req, res, next) { + var url = flutterwaveConfig.BaseApiUrl + '/v3/transfers'; + var headersOpt = { + "Content-Type": "application/json", + "Authorization": "Bearer " + flutterwaveConfig.SecretKey, + }; + var data = { + "account_bank": req.body.account_bank, + "account_number": req.body.account_number, + "amount": req.body.amount, + "narration": req.body.narration, + "currency": req.body.currency, + "reference": req.body.reference, + "debit_currency": req.body.debit_currency + }; + var options = { + headers: headersOpt + }; + logger.info(url); + logger.info(options); + logger.info(data); + /* + CREATE TABLE flutterwave ( + id bigserial, + money_transfer_id int NOT NULL REFERENCES money_transfer(id), + request json NOT NULL, + response json NOT NULL, + created timestamp DEFAULT NOW(), + PRIMARY KEY(id) + ); + */ + // Save request in the DB + var queryText = 'INSERT INTO flutterwave(money_transfer_id, request) VALUES($1, $2) RETURNING id' + db.query(queryText, [req.body.money_transfer_id, data], function(dbErr, result) { + if (dbErr) { + logger.info('Failed to save request in the DB for money_transfer_id =', req.body.money_transfer_id); + logger.info(dbErr); + res.send({ + status: "failure", + message: "Failed to save request in the DB!", + details: dbErr.message, + code: null, + data: data + }); + } else { + var flutterwave_id = result.rows[0].id; + logger.info('Request saved int the DB for money_transfer_id =', flutterwave_id); + logger.info(result.rows); + // https://blog.logrocket.com/5-ways-to-make-http-requests-in-node-js/ + // https://stackoverflow.com/questions/53846709/how-do-i-use-axios-within-expressjs + axios.post(url, data, options) + .then(response => { + //res.json(response); + logger.info('-------------------------------'); + logger.info(response.data); + logger.info('-------------------------------'); + var json = { + status: response.status, + statusText: response.statusText, + data: response.data + }; + queryText = 'UPDATE flutterwave SET response=$1 WHERE id=$2'; + db.query(queryText, [json, flutterwave_id], function(dbErr, result) { + if (dbErr) { + logger.info('Failed to save response in the DB for money_transfer_id =', flutterwave_id); + logger.info(dbErr); + } else { + logger.info('Response saved int the DB for money_transfer_id =', flutterwave_id); + logger.info('Updated row count:', result.rowCount); + } + }); + res.json(json); + }) + .catch(err => { + // https://stackoverflow.com/questions/45017822/catching-error-body-using-axios-post + var errorMessage = err.message; + logger.error(err.message); + //logger.error(err.stack); + //logger.error(err.response.data); + //logger.error(err.response.headers); + if ( err.request ) { + console.log(err.request); + // err.request.response + errorMessage = err.request; + } + if( err.response ) { + console.log(err.response.data); // => the response payload + errorMessage = err.response.data; + } + queryText = 'UPDATE flutterwave SET response=$1 WHERE id=$2'; + db.query(queryText, [errorMessage, flutterwave_id], function(dbErr, result) { + if (dbErr) { + logger.info('Failed to save error response in the DB for money_transfer_id =', flutterwave_id); + logger.info(dbErr); + } else { + logger.info('Error response saved int the DB for money_transfer_id =', flutterwave_id); + logger.info('Updated row count:', result.rowCount); + } + }); + res.send({ + status: "failure", + message: "Invalid response", + details: errorMessage, + code: typeof err.response !== 'undefined' ? err.response.status : null, + data: typeof err.response !== 'undefined' ? err.response.data : null + }); + }); + } + }); + // Check DB time stamp (integrity check) + db.query('SELECT NOW()', (err, res) => { + if (err) { + throw err; + } + logger.info('time:', res.rows[0]) + }); + }, + get: function(req, res, next) { + var url = flutterwaveConfig.BaseApiUrl + '/v3/transfers/' + req.params.id; + var headersOpt = { + "Authorization": "Bearer " + flutterwaveConfig.SecretKey, + }; + var options = { + headers: headersOpt + }; + logger.info(url); + axios.get(url, options) + .then(response => { + //res.json(response); + logger.info('-------------------------------'); + logger.info(response.data); + logger.info('-------------------------------'); + var json = { + status: response.status, + statusText: response.statusText, + data: response.data + }; + res.json(json); + }) + .catch(err => { + logger.error(err.message); + //logger.error(err.stack); + logger.error(err.response.data); + //logger.error(err.response.headers); + res.send({ + status: "failure", + message: "Invalid response", + details: err.message, + code: err.response.status, + data: err.response.data + }); + }); + // TODO: Do we save the response in the DB? I assume: YES + db.query('SELECT NOW()', (err, res) => { + if (err) { + throw err; + } + logger.info('time:', res.rows[0]) + }); + } +}; + +module.exports = transfer;