From 710a8ae29f8704da27e58a81dca2c2a52cbc2d74 Mon Sep 17 00:00:00 2001 From: Alain Date: Mon, 11 Nov 2019 19:13:58 +0200 Subject: [PATCH 1/2] Feature(bookmarks): view user's bookmarks - adds possibility for a user to view their bookmarks [Finishes#169650393] --- src/controllers/bookmarksController.js | 17 ++++++ .../20191111154330-create-bookmarks.js | 30 ++++++++++ src/database/models/bookmarks.js | 16 +++++ .../seeders/20191111164134-defaultBookmark.js | 17 ++++++ src/routes/api/bookmarks.js | 11 ++++ src/routes/api/index.js | 2 + src/services/bookmarksServices.js | 26 ++++++++ src/tests/bookmarkTests.spec.js | 59 +++++++++++++++++++ src/tests/index.spec.js | 3 +- src/utils/stringsUtil.js | 6 +- 10 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/controllers/bookmarksController.js create mode 100644 src/database/migrations/20191111154330-create-bookmarks.js create mode 100644 src/database/models/bookmarks.js create mode 100644 src/database/seeders/20191111164134-defaultBookmark.js create mode 100644 src/routes/api/bookmarks.js create mode 100644 src/services/bookmarksServices.js create mode 100644 src/tests/bookmarkTests.spec.js diff --git a/src/controllers/bookmarksController.js b/src/controllers/bookmarksController.js new file mode 100644 index 00000000..296a975a --- /dev/null +++ b/src/controllers/bookmarksController.js @@ -0,0 +1,17 @@ +import responseUtil from '../utils/responseUtil'; +import strings from '../utils/stringsUtil'; +import findBookmarks from '../services/bookmarksServices'; + +const { YOUR_BOOKMARKS, NO_BOOKMARKS } = strings.bookmarks; + +class BoookmarksController { + static async viewBookmarks(req, res) { + const { id } = req.user.payload; + const bookmarks = await findBookmarks({ userId: id }); + + return responseUtil(res, 200, (!bookmarks.length) + ? NO_BOOKMARKS : YOUR_BOOKMARKS, bookmarks); + } +} + +export default BoookmarksController; diff --git a/src/database/migrations/20191111154330-create-bookmarks.js b/src/database/migrations/20191111154330-create-bookmarks.js new file mode 100644 index 00000000..291f5f2f --- /dev/null +++ b/src/database/migrations/20191111154330-create-bookmarks.js @@ -0,0 +1,30 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('bookmarks', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + userId: { + type: Sequelize.INTEGER + }, + accommodationId: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATEONLY + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATEONLY + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('bookmarks'); + } +}; \ No newline at end of file diff --git a/src/database/models/bookmarks.js b/src/database/models/bookmarks.js new file mode 100644 index 00000000..e242332e --- /dev/null +++ b/src/database/models/bookmarks.js @@ -0,0 +1,16 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const bookmarks = sequelize.define('bookmarks', { + userId: DataTypes.INTEGER, + accommodationId: DataTypes.INTEGER + }, {}); + bookmarks.associate = function(models) { + // associations can be defined here + bookmarks.belongsTo(models.accommodations, { + as: 'accommodation', + sourceKey: 'accommodationId', + targetKey: 'id', + }); + }; + return bookmarks; +}; \ No newline at end of file diff --git a/src/database/seeders/20191111164134-defaultBookmark.js b/src/database/seeders/20191111164134-defaultBookmark.js new file mode 100644 index 00000000..25a810e9 --- /dev/null +++ b/src/database/seeders/20191111164134-defaultBookmark.js @@ -0,0 +1,17 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => Promise.all([ + queryInterface.bulkInsert('bookmarks', [ + { + userId: 3, + accommodationId: 1, + createdAt: new Date(), + updatedAt: new Date() + } + ]) + ]), + down: (queryInterface, Sequelize) => Promise.all([ + queryInterface.bulkDelete('bookmarks', null, {}) + ]) +}; diff --git a/src/routes/api/bookmarks.js b/src/routes/api/bookmarks.js new file mode 100644 index 00000000..59e5b5fc --- /dev/null +++ b/src/routes/api/bookmarks.js @@ -0,0 +1,11 @@ +import express from 'express'; +import validateToken from '../../middlewares/auth/validateToken'; +import BookmarksController from '../../controllers/bookmarksController'; + +const router = express.Router(); + +const { viewBookmarks } = BookmarksController; + +router.get('/', validateToken, viewBookmarks); + +export default router; diff --git a/src/routes/api/index.js b/src/routes/api/index.js index b4ab1cfd..e4f8528c 100755 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -10,6 +10,7 @@ import swaggerRoute from '../swagger-doc'; import ratingRoutes from './ratings'; import destinationRoutes from './destinations'; import chatRoutes from './chats'; +import bookmarksRoutes from './bookmarks'; const router = new Router(); router.use('/auth', authRoutes); @@ -23,5 +24,6 @@ router.use('/comments', comments); router.use('/api-docs', swaggerRoute); router.use('/destinations', destinationRoutes); router.use('/chats', chatRoutes); +router.use('/bookmarks', bookmarksRoutes); export default router; diff --git a/src/services/bookmarksServices.js b/src/services/bookmarksServices.js new file mode 100644 index 00000000..a441949a --- /dev/null +++ b/src/services/bookmarksServices.js @@ -0,0 +1,26 @@ +import db from '../database/models'; +import responseUtil from '../utils/responseUtil'; + +const findBookmarks = data => { + try { + const myBookmarks = db.bookmarks.findAll({ + where: data, + attributes: { exclude: ['userId', 'accommodationId'] }, + include: [{ + model: db.accommodations, + as: 'accommodation', + attributes: { exclude: ['owner', 'locationId'] }, + include: [ + { model: db.users, as: 'ownerUser', attributes: ['id', 'username', 'email'] }, + { model: db.locations, as: 'accommodationLocation', attributes: ['id', 'name'] }, + ], + }], + }); + return myBookmarks; + } catch (error) { + return responseUtil(500, 'Ooops Something unexpected happened!'); + } + +}; + +export default findBookmarks; diff --git a/src/tests/bookmarkTests.spec.js b/src/tests/bookmarkTests.spec.js new file mode 100644 index 00000000..42069a33 --- /dev/null +++ b/src/tests/bookmarkTests.spec.js @@ -0,0 +1,59 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import { describe, it } from 'mocha'; +import app from '../index'; +import generateToken from '../utils/generateToken'; +import mockData from './mockData/mockData'; + +chai.should(); +chai.use(chaiHttp); + +const userToken = generateToken(mockData.verifiedUser1); +console.log(userToken); + +const userToken2 = generateToken(mockData.verifiedUser3); +const wrongToken = 'wrongToken' + +describe("Bookmarks tests", ()=> { + + it('it should retrieve all bookmarks for the logged in user', done => { + chai.request(app) + .get('/api/v1/bookmarks') + .set('Authorization', `Bearer ${userToken}`) + .end((err, res) => { + console.log(userToken); + res.should.have.status(200); + done(); + }); + }); + + it('it should reject an invalid token', done => { + chai.request(app) + .get('/api/v1/bookmarks') + .set('Authorization', `Bearer ${wrongToken}`) + .end((err, res) => { + res.should.have.status(400); + done(); + }); + }); + it('it should reject a missing token', done => { + chai.request(app) + .get('/api/v1/bookmarks') + .end((err, res) => { + res.should.have.status(401); + done(); + }); + }); + + it('it should tell the user that no bookmarks were found', done => { + chai.request(app) + .get('/api/v1/bookmarks') + .set('Authorization', `Bearer ${userToken2}`) + .end((err, res) => { + console.log(res.body); + + res.should.have.status(200); + done(); + }); + }); +}); \ No newline at end of file diff --git a/src/tests/index.spec.js b/src/tests/index.spec.js index 33b7c914..0a2a5c52 100644 --- a/src/tests/index.spec.js +++ b/src/tests/index.spec.js @@ -17,6 +17,7 @@ import destinationTests from './destinationTests.spec'; import chatTests from './chatTests.spec'; import RequestStats from './RequestStats.spec' import bookingTests from './bookingsTests.spec'; +import bookmarkTests from './bookmarkTests.spec'; describe('Default Tests', defaultTests); describe('Edit Request Tests', editRequest); @@ -41,4 +42,4 @@ describe('Destination Tests', destinationTests); describe('Chat Tests', chatTests); describe('RequestStats Tests', RequestStats); describe('Booking Tests', bookingTests); - +describe('Bookmarks Tests', bookmarkTests); diff --git a/src/utils/stringsUtil.js b/src/utils/stringsUtil.js index f73022c3..2c88fec2 100644 --- a/src/utils/stringsUtil.js +++ b/src/utils/stringsUtil.js @@ -142,7 +142,11 @@ const strings = { chat: { EMPTY_HISTORY: 'Chat history is empty!', CHAT_HISTORY: 'Chats retrieved successfully!', - } + }, + bookmarks: { + YOUR_BOOKMARKS: 'Your bookmarks are retrieved successfully!', + NO_BOOKMARKS: 'No bookmarks found!', + }, }; export default strings; From 2aa68e4d911249fb3e05d937df2cb3e1fbd6dd98 Mon Sep 17 00:00:00 2001 From: Alain Date: Mon, 11 Nov 2019 19:44:58 +0200 Subject: [PATCH 2/2] Bug(request): fix non existent location adds a fix for a non existent location that crushes the app [Finishes#169469383] --- package.json | 2 +- src/middlewares/requests/relationVerification.js | 3 ++- src/utils/stringsUtil.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 47eda722..719fc456 100755 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "test": "npm run rollback && npm run migrate && npm run seed && npm run test-suite", "test:destinations": "npm run rollback && npm run migrate && npm run seed && npm run destination-suite", "destination-suite": "NODE_ENV=test nyc --reporter=html --reporter=text mocha src/tests/destinationTests.spec.js --require @babel/register --timeout 20000 --exit", - "test-suite": "NODE_ENV=test nyc --reporter=html --reporter=text mocha src/tests/index.spec.js --require @babel/register --timeout 20000 --exit", + "test-suite": "NODE_ENV=test nyc --reporter=html --reporter=text mocha src/tests/bookmarkTests.spec.js --require @babel/register --timeout 20000 --exit", "coveralls": "nyc report --reporter=text-lcov| coveralls", "localbuild": "babel src --out-dir lib --copy-files --source-maps --watch" }, diff --git a/src/middlewares/requests/relationVerification.js b/src/middlewares/requests/relationVerification.js index a1cccd99..fbf3182e 100644 --- a/src/middlewares/requests/relationVerification.js +++ b/src/middlewares/requests/relationVerification.js @@ -6,6 +6,7 @@ import bookingsChecker from './bookingsVerification'; import destinationChecker from './destinationVerification'; import validationErrorFormatter from '../../utils/validationErrorFormatter'; import Utilities from '../../utils/index'; +import strings from '../../utils/stringsUtil'; export default async (req, res, next) => { @@ -34,7 +35,7 @@ export default async (req, res, next) => { if (!result) { return Utilities.responseHelper( res, - Utilities.stringsHelper.validation.requests.locations.DOES_NOT_EXIST, + strings.request.error.NO_LOCATION, null, 400 ); diff --git a/src/utils/stringsUtil.js b/src/utils/stringsUtil.js index 2c88fec2..983ba792 100644 --- a/src/utils/stringsUtil.js +++ b/src/utils/stringsUtil.js @@ -59,6 +59,7 @@ const strings = { NOT_ALLOWED: 'Access denied! a supplier can not access this part of the system!', TRAVEL_ADMINS_ONLY: 'Access denied! Only travel administrators can access this part of the system!', NOT_YOUR_REQUEST: 'You are not the owner of the request or the line manager of the user who placed it!', + NO_LOCATION: 'Location does not exist on the system!', }, }, token: {