From 95092d74c337d29f0b27deb773a4b719985534a2 Mon Sep 17 00:00:00 2001 From: Abishek Aditya Date: Fri, 23 Feb 2018 11:46:23 +0530 Subject: [PATCH 1/6] created the models, migrations and seeder --- migrations/20180223033914-create-contacts.js | 30 +++++++++++++++++ models/contacts.js | 11 +++++++ seeders/20180223055734-demo-contacts.js | 34 ++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 migrations/20180223033914-create-contacts.js create mode 100644 models/contacts.js create mode 100644 seeders/20180223055734-demo-contacts.js diff --git a/migrations/20180223033914-create-contacts.js b/migrations/20180223033914-create-contacts.js new file mode 100644 index 0000000..7617b0f --- /dev/null +++ b/migrations/20180223033914-create-contacts.js @@ -0,0 +1,30 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('contacts', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER + }, + friendId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('contacts'); + } +}; \ No newline at end of file diff --git a/models/contacts.js b/models/contacts.js new file mode 100644 index 0000000..3d04b81 --- /dev/null +++ b/models/contacts.js @@ -0,0 +1,11 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + var contacts = sequelize.define('contacts', { + userId: DataTypes.INTEGER, + friendId: DataTypes.INTEGER + }, {}); + contacts.associate = function(models) { + // associations can be defined here + }; + return contacts; +}; \ No newline at end of file diff --git a/seeders/20180223055734-demo-contacts.js b/seeders/20180223055734-demo-contacts.js new file mode 100644 index 0000000..3e13d7e --- /dev/null +++ b/seeders/20180223055734-demo-contacts.js @@ -0,0 +1,34 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('contacts', [ + { + userId: 1, + friendId: 2, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + userId: 1, + friendId: 3, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 2, + friendId: 1, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 2, + friendId: 3, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 3, + friendId: 2, + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('contacts', null, {}), +}; From f6440c1f7f4d7290be04c11c5c6ab2f5383678a6 Mon Sep 17 00:00:00 2001 From: Abishek Aditya Date: Fri, 23 Feb 2018 12:58:50 +0530 Subject: [PATCH 2/6] Added joi and swagger for add contacts --- package.json | 7 ++- routes/contacts.js | 86 +++++++++++++++++++++++++++++++++++ routes/index.js | 3 +- server.js | 24 +++++++++- tests/routes/contacts.test.js | 47 +++++++++++++++++++ 5 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 routes/contacts.js create mode 100644 tests/routes/contacts.test.js diff --git a/package.json b/package.json index 2cc95b9..ccd8764 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,13 @@ "hapi": "^16.6.2", "hapi-auth-basic": "^4.2.0", "hapi-auth-jwt2": "^4.0.0", - "joi": "^13.1.2", "jsonwebtoken": "^8.1.1", "jwt-decode": "^2.2.0", "pg": "^6.4.2", - "sequelize": "^4.32.6" + "hapi-swagger": "^7.10.0", + "inert": "^4.2.1", + "joi": "^13.1.2", + "sequelize": "^4.32.6", + "vision": "^4.1.1" } } diff --git a/routes/contacts.js b/routes/contacts.js new file mode 100644 index 0000000..934ff86 --- /dev/null +++ b/routes/contacts.js @@ -0,0 +1,86 @@ +const model = require('../models'); +const Joi = require('joi'); + +const contactSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.array().example([{ userId: 1, name: 'John_Doe' }, { userId: 2, name: 'Jane_Doe' }]), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const headerValidation = Joi.object({ + authorization: Joi.string(), +}).unknown(); + +const contactAddSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Added a friend'), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const contactAddValidation = Joi.object({ + friendId: Joi.string().example('2'), +}); + +module.exports = [{ + method: 'GET', + path: '/contacts', + config: { + tags: ['api'], + description: 'get contacts of the users', + notes: 'find all the contacts the user has added', + plugins: { + 'hapi-swagger': contactSwagger, + }, + validate: { headers: headerValidation }, + }, + handler: (request, reply) => { + model.contacts.findAll({ + where: + { userId: (request.auth.credentials.userId) }, + }).then((result) => { + const resultArrPromise = []; + result.forEach(({ friendId }) => { + resultArrPromise.push(model.users.findOne({ where: { userId: friendId } })); + }); + + const detailsArr = []; + Promise.all(resultArrPromise).then((resultArr) => { + resultArr.forEach(({ userName, userId }) => { + detailsArr.push({ + name: userName, + id: userId, + }); + }); + + reply(detailsArr); + }); + }); + }, +}, { + method: 'POST', + path: '/contacts', + config: { + tags: ['api'], + description: 'add a contacts for the users', + notes: 'inerst into database the id of the contact', + plugins: { + 'hapi-swagger': contactAddSwagger, + }, + validate: { headers: headerValidation, payload: contactAddValidation }, + }, + handler: (request, reply) => { + reply('wo0hoo'); + }, +}]; diff --git a/routes/index.js b/routes/index.js index 08911be..115a8e7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,5 +2,6 @@ const ping = require('./ping'); const userRegistration = require('./userRegister'); const userLogin = require('./userLogin'); const auth = require('./auth'); +const contacts = require('./contacts'); -module.exports = [].concat(ping, userRegistration, userLogin, auth); +module.exports = [].concat(ping, userRegistration, userLogin, auth, contacts); diff --git a/server.js b/server.js index 43551a6..5278d1e 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,10 @@ const Jwt = require('hapi-auth-jwt2'); const validate = require('./validate'); +const Inert = require('inert'); +const Vision = require('vision'); +const HapiSwagger = require('hapi-swagger'); + const server = new Hapi.Server(); server.connection({ @@ -12,7 +16,25 @@ server.connection({ host: 'localhost', }); -server.register(Jwt); +const options = { + info: { + title: 'Wallet Documentation', + version: '1.0', + }, +}; + +server.register([ + Jwt, + Inert, + Vision, + { + register: HapiSwagger, + options, + }], (err) => { + if (err) { + throw err; + } +}); server.auth.strategy('jwt', 'jwt', { key: secret, diff --git a/tests/routes/contacts.test.js b/tests/routes/contacts.test.js new file mode 100644 index 0000000..c4a16d5 --- /dev/null +++ b/tests/routes/contacts.test.js @@ -0,0 +1,47 @@ +const server = require('../../server'); +const Jwt = require('jsonwebtoken'); +const secret = require('../../secret'); + +const userId = 1; +const userName = 'John_Doe'; + +describe('GET contacts list', () => { + test('should get 401 status code if passed without auth', (done) => { + const request = { + method: 'GET', + url: '/contacts', + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(401); + done(); + }); + }); + + test('should get 200 status code if passed with auth', (done) => { + const request = { + method: 'GET', + url: '/contacts', + credentials: { userId, userName }, + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(200); + done(); + }); + }); + + test('should get array of objects on success', (done) => { + const request = { + method: 'GET', + url: '/contacts', + credentials: { userId, userName }, + }; + server.inject(request, (reply) => { + expect.assertions(3); + expect(reply.result).toBeInstanceOf(Array); + expect(reply.result[0]).toHaveProperty('id'); + expect(reply.result[0]).toHaveProperty('name'); + done(); + }); + }); +}); + From 5fef77cb26f6bddd4428ad22575cac373e7cd86a Mon Sep 17 00:00:00 2001 From: Abishek Aditya Date: Fri, 23 Feb 2018 13:32:52 +0530 Subject: [PATCH 3/6] added tests and code for adding a friend --- routes/contacts.js | 20 ++++++++-- tests/routes/contacts.test.js | 71 ++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/routes/contacts.js b/routes/contacts.js index 934ff86..74cd0ba 100644 --- a/routes/contacts.js +++ b/routes/contacts.js @@ -25,12 +25,13 @@ const contactAddSwagger = { message: Joi.string().example('Added a friend'), }).label('Result'), }, + 400: { description: 'Bad Request' }, 401: { description: 'Unauthorized' }, }, }; const contactAddValidation = Joi.object({ - friendId: Joi.string().example('2'), + friendId: Joi.number().example(2), }); module.exports = [{ @@ -73,7 +74,7 @@ module.exports = [{ path: '/contacts', config: { tags: ['api'], - description: 'add a contacts for the users', + description: 'add a contact for the current user', notes: 'inerst into database the id of the contact', plugins: { 'hapi-swagger': contactAddSwagger, @@ -81,6 +82,19 @@ module.exports = [{ validate: { headers: headerValidation, payload: contactAddValidation }, }, handler: (request, reply) => { - reply('wo0hoo'); + const { friendId } = request.payload; + const { userId } = request.auth.credentials; + + if (friendId === userId) { + reply({ message: 'Can\'t add yourself' }).code(400); + } + + model.users.findOne({ where: { userId: friendId } }).then((result) => { + if (result === null) { + reply({ message: 'User doesn\'t exist' }).code(400); + } else { + model.contacts.findOrCreate({ where: { userId, friendId }, defaults: { userId, friendId } }).then(() => reply({ message: 'Successfully added' }).code(200)); + } + }); }, }]; diff --git a/tests/routes/contacts.test.js b/tests/routes/contacts.test.js index c4a16d5..317cd33 100644 --- a/tests/routes/contacts.test.js +++ b/tests/routes/contacts.test.js @@ -1,6 +1,4 @@ const server = require('../../server'); -const Jwt = require('jsonwebtoken'); -const secret = require('../../secret'); const userId = 1; const userName = 'John_Doe'; @@ -45,3 +43,72 @@ describe('GET contacts list', () => { }); }); +describe('POST a new contact to the contact list', () => { + test('should get 401 status code if passed without auth', (done) => { + const request = { + method: 'POST', + url: '/contacts', + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(401); + done(); + }); + }); + + test('should get 200 status code if passed with auth', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 2 }, + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(200); + done(); + }); + }); + + + test('should get Successfully on adding a friend', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 2 }, + }; + server.inject(request, (reply) => { + expect(reply.result.message).toEqual('Successfully added'); + done(); + }); + }); + + test('should get error if passing user\'s own id', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: userId }, + }; + server.inject(request, (reply) => { + expect.assertions(2); + expect(reply.result.message).toEqual('Can\'t add yourself'); + expect(reply.statusCode).toEqual(400); + done(); + }); + }); + + test('should get error if passsing non-existent id', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 23 }, + }; + server.inject(request, (reply) => { + expect.assertions(2); + expect(reply.result.message).toEqual('User doesn\'t exist'); + expect(reply.statusCode).toEqual(400); + done(); + }); + }); +}); From 2f4a891bc8bf644774cf52590a6f448b640be36d Mon Sep 17 00:00:00 2001 From: Abishek Aditya Date: Fri, 23 Feb 2018 11:46:23 +0530 Subject: [PATCH 4/6] created the models, migrations and seeder --- migrations/20180223033914-create-contacts.js | 30 +++++++++++++++++ models/contacts.js | 11 +++++++ seeders/20180223055734-demo-contacts.js | 34 ++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 migrations/20180223033914-create-contacts.js create mode 100644 models/contacts.js create mode 100644 seeders/20180223055734-demo-contacts.js diff --git a/migrations/20180223033914-create-contacts.js b/migrations/20180223033914-create-contacts.js new file mode 100644 index 0000000..7617b0f --- /dev/null +++ b/migrations/20180223033914-create-contacts.js @@ -0,0 +1,30 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('contacts', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER + }, + friendId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('contacts'); + } +}; \ No newline at end of file diff --git a/models/contacts.js b/models/contacts.js new file mode 100644 index 0000000..3d04b81 --- /dev/null +++ b/models/contacts.js @@ -0,0 +1,11 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + var contacts = sequelize.define('contacts', { + userId: DataTypes.INTEGER, + friendId: DataTypes.INTEGER + }, {}); + contacts.associate = function(models) { + // associations can be defined here + }; + return contacts; +}; \ No newline at end of file diff --git a/seeders/20180223055734-demo-contacts.js b/seeders/20180223055734-demo-contacts.js new file mode 100644 index 0000000..3e13d7e --- /dev/null +++ b/seeders/20180223055734-demo-contacts.js @@ -0,0 +1,34 @@ + + +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.bulkInsert('contacts', [ + { + userId: 1, + friendId: 2, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + userId: 1, + friendId: 3, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 2, + friendId: 1, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 2, + friendId: 3, + createdAt: new Date(), + updatedAt: new Date(), + }, { + userId: 3, + friendId: 2, + createdAt: new Date(), + updatedAt: new Date(), + }], {}), + + down: (queryInterface, Sequelize) => queryInterface.bulkDelete('contacts', null, {}), +}; From 6b3956dd5c0c68e1a45688d9ab486a34d9b73e01 Mon Sep 17 00:00:00 2001 From: Abishek Aditya Date: Fri, 23 Feb 2018 12:58:50 +0530 Subject: [PATCH 5/6] Added joi and swagger for add contacts --- routes/contacts.js | 86 +++++++++++++++++++++++++++++++++++ routes/index.js | 3 +- tests/routes/contacts.test.js | 47 +++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 routes/contacts.js create mode 100644 tests/routes/contacts.test.js diff --git a/routes/contacts.js b/routes/contacts.js new file mode 100644 index 0000000..934ff86 --- /dev/null +++ b/routes/contacts.js @@ -0,0 +1,86 @@ +const model = require('../models'); +const Joi = require('joi'); + +const contactSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.array().example([{ userId: 1, name: 'John_Doe' }, { userId: 2, name: 'Jane_Doe' }]), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const headerValidation = Joi.object({ + authorization: Joi.string(), +}).unknown(); + +const contactAddSwagger = { + responses: { + 200: { + description: 'Success', + schema: Joi.object({ + message: Joi.string().example('Added a friend'), + }).label('Result'), + }, + 401: { description: 'Unauthorized' }, + }, +}; + +const contactAddValidation = Joi.object({ + friendId: Joi.string().example('2'), +}); + +module.exports = [{ + method: 'GET', + path: '/contacts', + config: { + tags: ['api'], + description: 'get contacts of the users', + notes: 'find all the contacts the user has added', + plugins: { + 'hapi-swagger': contactSwagger, + }, + validate: { headers: headerValidation }, + }, + handler: (request, reply) => { + model.contacts.findAll({ + where: + { userId: (request.auth.credentials.userId) }, + }).then((result) => { + const resultArrPromise = []; + result.forEach(({ friendId }) => { + resultArrPromise.push(model.users.findOne({ where: { userId: friendId } })); + }); + + const detailsArr = []; + Promise.all(resultArrPromise).then((resultArr) => { + resultArr.forEach(({ userName, userId }) => { + detailsArr.push({ + name: userName, + id: userId, + }); + }); + + reply(detailsArr); + }); + }); + }, +}, { + method: 'POST', + path: '/contacts', + config: { + tags: ['api'], + description: 'add a contacts for the users', + notes: 'inerst into database the id of the contact', + plugins: { + 'hapi-swagger': contactAddSwagger, + }, + validate: { headers: headerValidation, payload: contactAddValidation }, + }, + handler: (request, reply) => { + reply('wo0hoo'); + }, +}]; diff --git a/routes/index.js b/routes/index.js index 31177a0..a33ab8d 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,5 +3,6 @@ const userRegistration = require('./userRegister'); const userLogin = require('./userLogin'); const auth = require('./auth'); const history = require('./history'); +const contacts = require('./contacts'); -module.exports = [].concat(ping, userRegistration, userLogin, auth, history); +module.exports = [].concat(ping, userRegistration, userLogin, auth, history, contacts); diff --git a/tests/routes/contacts.test.js b/tests/routes/contacts.test.js new file mode 100644 index 0000000..c4a16d5 --- /dev/null +++ b/tests/routes/contacts.test.js @@ -0,0 +1,47 @@ +const server = require('../../server'); +const Jwt = require('jsonwebtoken'); +const secret = require('../../secret'); + +const userId = 1; +const userName = 'John_Doe'; + +describe('GET contacts list', () => { + test('should get 401 status code if passed without auth', (done) => { + const request = { + method: 'GET', + url: '/contacts', + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(401); + done(); + }); + }); + + test('should get 200 status code if passed with auth', (done) => { + const request = { + method: 'GET', + url: '/contacts', + credentials: { userId, userName }, + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(200); + done(); + }); + }); + + test('should get array of objects on success', (done) => { + const request = { + method: 'GET', + url: '/contacts', + credentials: { userId, userName }, + }; + server.inject(request, (reply) => { + expect.assertions(3); + expect(reply.result).toBeInstanceOf(Array); + expect(reply.result[0]).toHaveProperty('id'); + expect(reply.result[0]).toHaveProperty('name'); + done(); + }); + }); +}); + From eef922e799b9a344f7282979272a8f80ae799cf2 Mon Sep 17 00:00:00 2001 From: Abishek Aditya Date: Fri, 23 Feb 2018 13:32:52 +0530 Subject: [PATCH 6/6] added tests and code for adding a friend --- routes/contacts.js | 20 ++++++++-- tests/routes/contacts.test.js | 71 ++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/routes/contacts.js b/routes/contacts.js index 934ff86..74cd0ba 100644 --- a/routes/contacts.js +++ b/routes/contacts.js @@ -25,12 +25,13 @@ const contactAddSwagger = { message: Joi.string().example('Added a friend'), }).label('Result'), }, + 400: { description: 'Bad Request' }, 401: { description: 'Unauthorized' }, }, }; const contactAddValidation = Joi.object({ - friendId: Joi.string().example('2'), + friendId: Joi.number().example(2), }); module.exports = [{ @@ -73,7 +74,7 @@ module.exports = [{ path: '/contacts', config: { tags: ['api'], - description: 'add a contacts for the users', + description: 'add a contact for the current user', notes: 'inerst into database the id of the contact', plugins: { 'hapi-swagger': contactAddSwagger, @@ -81,6 +82,19 @@ module.exports = [{ validate: { headers: headerValidation, payload: contactAddValidation }, }, handler: (request, reply) => { - reply('wo0hoo'); + const { friendId } = request.payload; + const { userId } = request.auth.credentials; + + if (friendId === userId) { + reply({ message: 'Can\'t add yourself' }).code(400); + } + + model.users.findOne({ where: { userId: friendId } }).then((result) => { + if (result === null) { + reply({ message: 'User doesn\'t exist' }).code(400); + } else { + model.contacts.findOrCreate({ where: { userId, friendId }, defaults: { userId, friendId } }).then(() => reply({ message: 'Successfully added' }).code(200)); + } + }); }, }]; diff --git a/tests/routes/contacts.test.js b/tests/routes/contacts.test.js index c4a16d5..317cd33 100644 --- a/tests/routes/contacts.test.js +++ b/tests/routes/contacts.test.js @@ -1,6 +1,4 @@ const server = require('../../server'); -const Jwt = require('jsonwebtoken'); -const secret = require('../../secret'); const userId = 1; const userName = 'John_Doe'; @@ -45,3 +43,72 @@ describe('GET contacts list', () => { }); }); +describe('POST a new contact to the contact list', () => { + test('should get 401 status code if passed without auth', (done) => { + const request = { + method: 'POST', + url: '/contacts', + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(401); + done(); + }); + }); + + test('should get 200 status code if passed with auth', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 2 }, + }; + server.inject(request, (reply) => { + expect(reply.statusCode).toEqual(200); + done(); + }); + }); + + + test('should get Successfully on adding a friend', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 2 }, + }; + server.inject(request, (reply) => { + expect(reply.result.message).toEqual('Successfully added'); + done(); + }); + }); + + test('should get error if passing user\'s own id', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: userId }, + }; + server.inject(request, (reply) => { + expect.assertions(2); + expect(reply.result.message).toEqual('Can\'t add yourself'); + expect(reply.statusCode).toEqual(400); + done(); + }); + }); + + test('should get error if passsing non-existent id', (done) => { + const request = { + method: 'POST', + url: '/contacts', + credentials: { userId, userName }, + payload: { friendId: 23 }, + }; + server.inject(request, (reply) => { + expect.assertions(2); + expect(reply.result.message).toEqual('User doesn\'t exist'); + expect(reply.statusCode).toEqual(400); + done(); + }); + }); +});