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/package.json b/package.json index b529fc1..ef9adc9 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,12 @@ "hapi": "^16.6.2", "hapi-auth-basic": "^4.2.0", "hapi-auth-jwt2": "^4.0.0", - "hapi-swagger": "^7.10.0", - "inert": "^4.2.1", - "joi": "^13.1.2", "jsonwebtoken": "^8.1.1", "jwt-decode": "^2.2.0", "pg": "^6.4.2", + "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..74cd0ba --- /dev/null +++ b/routes/contacts.js @@ -0,0 +1,100 @@ +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'), + }, + 400: { description: 'Bad Request' }, + 401: { description: 'Unauthorized' }, + }, +}; + +const contactAddValidation = Joi.object({ + friendId: Joi.number().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 contact for the current user', + notes: 'inerst into database the id of the contact', + plugins: { + 'hapi-swagger': contactAddSwagger, + }, + validate: { headers: headerValidation, payload: contactAddValidation }, + }, + handler: (request, reply) => { + 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/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/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, {}), +}; diff --git a/tests/routes/contacts.test.js b/tests/routes/contacts.test.js new file mode 100644 index 0000000..317cd33 --- /dev/null +++ b/tests/routes/contacts.test.js @@ -0,0 +1,114 @@ +const server = require('../../server'); + +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(); + }); + }); +}); + +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(); + }); + }); +});