diff --git a/migrations/20180223063545-changetype-transaction-id.js b/migrations/20180223063545-changetype-transaction-id.js new file mode 100644 index 0000000..a2119ad --- /dev/null +++ b/migrations/20180223063545-changetype-transaction-id.js @@ -0,0 +1,23 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => { + queryInterface.changeColumn( + 'transactions', + 'transactionId', + { + type: Sequelize.STRING, + }, + ); + }, + + down: (queryInterface, Sequelize) => { + queryInterface.changeColumn( + 'transactions', + 'transactionId', + { + type: Sequelize.STRING, + }, + ); + }, +}; diff --git a/routes/index.js b/routes/index.js index 31177a0..1a704f4 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,7 +1,10 @@ const ping = require('./ping'); -const userRegistration = require('./userRegister'); + +const { route: userRegistration } = require('./userRegister'); const userLogin = require('./userLogin'); +const send = require('./send'); +const request = require('./request'); const auth = require('./auth'); const history = require('./history'); -module.exports = [].concat(ping, userRegistration, userLogin, auth, history); +module.exports = [].concat(ping, userRegistration, userLogin, auth, history, send, request); diff --git a/routes/request.js b/routes/request.js new file mode 100644 index 0000000..e14d874 --- /dev/null +++ b/routes/request.js @@ -0,0 +1,58 @@ +const Models = require('../models'); +const Joi = require('joi'); + +const requestSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('request created successfully'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + }, +}; +const requestPayloadValidation = Joi.object({ + toId: Joi.number().integer().min(1).example(1), + amount: Joi.number().integer().min(0).example(500), + reason: Joi.string().example('food'), +}); +const route = [ + { + method: 'POST', + path: '/transaction/request', + config: { + tags: ['api'], + description: 'request money', + notes: 'request money from another user', + plugins: { + 'hapi-swagger': requestSwagger, + }, + validate: { + payload: requestPayloadValidation, + }, + auth: 'jwt', + }, + handler: (request, response) => { + const toId = request.payload.toId; + const amt = request.payload.amount; + const currentUserId = request.auth.credentials.userId; + const reason = request.payload.reason; + // create transaction + Models.transactions.create({ + transactionId: `${currentUserId}_${toId}_${new Date()}`, + fromId: currentUserId, + toId, + amount: amt, + reason, + status: 'PENDING', + timeStamp: new Date(), + createdAt: new Date(), + updatedAt: new Date(), + }).then(() => { + response({ statusCode: 201, message: 'transaction added' }); + }); + }, + }]; + +module.exports = route; diff --git a/routes/send.js b/routes/send.js new file mode 100644 index 0000000..bb59bca --- /dev/null +++ b/routes/send.js @@ -0,0 +1,82 @@ +const Models = require('../models'); +const Joi = require('joi'); + +const sendSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('request created successfully'), + }).label('Result'), + }, + 400: { description: 'Bad Request' }, + }, +}; +const sendPayloadValidation = Joi.object({ + toId: Joi.number().integer().min(1).example(1), + amount: Joi.number().integer().min(0).example(500), + reason: Joi.string().example('food'), +}); +const getUserBalance = userId => new Promise((resolve) => { + Models.userDetails.findOne({ where: { userId } }) + .then((item) => { + resolve(item.balance); + }); +}); + +const route = [ + { + method: 'POST', + path: '/transaction/send', + config: { + tags: ['api'], + description: 'send money', + notes: 'send money from another user', + plugins: { + 'hapi-swagger': sendSwagger, + }, + validate: { + payload: sendPayloadValidation, + }, + auth: 'jwt', + }, + handler: (request, response) => { + const toId = request.payload.toId; + const amt = request.payload.amount; + const currentUserId = request.auth.credentials.userId; + const reason = request.payload.reason; + // console.log(`${toId} ${amt}${currentUserId}${reason}`); + getUserBalance(currentUserId).then((balance) => { + if (amt > balance) { + response('insufficient balance'); + } else { + const futureBalance = balance - amt; + // deduct balance from fromId + Models.userDetails.update( + { balance: futureBalance }, + { where: { userId: currentUserId } }, + ).then(() => { + // create transaction + console.log('yup coming in transaction database'); + Models.transactions.create({ + transactionId: `${currentUserId}_${toId}_${new Date()}`, + fromId: currentUserId, + toId, + amount: amt, + reason, + status: 'PENDING', + timeStamp: new Date(), + createdAt: new Date(), + updatedAt: new Date(), + }); + }).then(() => { + response({ statusCode: 201, message: 'transaction added' }); + }); + } + }); + }, + + }, +]; + +module.exports = route; diff --git a/seeders/20180214174923-additional-users.js b/seeders/20180214174923-additional-users.js new file mode 100644 index 0000000..739a206 --- /dev/null +++ b/seeders/20180214174923-additional-users.js @@ -0,0 +1,16 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('userDetails', [{ + userId: 4, + phone: '1234567890', + accountNo: '0987654322', + firstName: 'Rachel', + lastName: 'A', + balance: 5000, + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('userDetails', null, {}), +}; diff --git a/tests/routes/request.test.js b/tests/routes/request.test.js new file mode 100644 index 0000000..88399d3 --- /dev/null +++ b/tests/routes/request.test.js @@ -0,0 +1,63 @@ +const server = require('../../server'); + +describe('request validation', () => { + test('Test for successful POST request', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.result.statusCode).toBe(201); + done(); + }); + }); + + test('Test for unsuccessful POST request if toId is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 'm', amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + test('Test for unsuccessful POST request if amount is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: '500m', reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + + + test('Test for unsuccessful POST request if reason is not a string', (done) => { + const request = { + method: 'POST', + url: '/transaction/request', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 22 }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); +}); diff --git a/tests/routes/send.test.js b/tests/routes/send.test.js new file mode 100644 index 0000000..1bb2229 --- /dev/null +++ b/tests/routes/send.test.js @@ -0,0 +1,63 @@ +const server = require('../../server'); + +describe('request validation', () => { + test('Test for successful POST request', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.result.statusCode).toBe(201); + done(); + }); + }); + + test('Test for unsuccessful POST request if toId is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 'm', amount: 500, reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + test('Test for unsuccessful POST request if amount is not a number', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: '500m', reason: 'food' }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); + + + test('Test for unsuccessful POST request if reason is not a string', (done) => { + const request = { + method: 'POST', + url: '/transaction/send', + credentials: { + userId: 4, + }, + payload: JSON.stringify({ toId: 2, amount: 500, reason: 22 }), + }; + server.inject(request, (response) => { + expect(response.statusCode).toBe(400); + done(); + }); + }); +});