Skip to content

Commit

Permalink
feat(accommodation): implement accommodation rating
Browse files Browse the repository at this point in the history
- create ratings model and migrations
- create route and implement controller
- create rating services
- write test for new endpoint

[Finishes #167727757]
  • Loading branch information
jsbuddy committed Sep 15, 2019
1 parent b61f6dd commit b724161
Show file tree
Hide file tree
Showing 15 changed files with 496 additions and 50 deletions.
13 changes: 10 additions & 3 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
{
"presets": [
"@babel/preset-env"
],
],
"plugins": [
"@babel/plugin-transform-runtime"
]
"@babel/plugin-transform-runtime"
],
"env": {
"test": {
"plugins": [
"istanbul"
]
}
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Reviewed by Hound](http://img.shields.io/badge/Reviewed%20By-Hound-%23a874d1)](https://houndci.com) [![Maintainability](https://api.codeclimate.com/v1/badges/b9d017718c7824fa2fc0/maintainability)](https://codeclimate.com/github/andela/storm-backend/maintainability) [![Build Status](https://travis-ci.org/andela/storm-backend.svg?branch=develop)](https://travis-ci.org/andela/storm-backend) [![Test Coverage](https://api.codeclimate.com/v1/badges/b9d017718c7824fa2fc0/test_coverage)](https://codeclimate.com/github/andela/storm-backend/test_coverage)
[![Reviewed by Hound](http://img.shields.io/badge/Reviewed%20By-Hound-%23a874d1)](https://houndci.com) [![Maintainability](https://api.codeclimate.com/v1/badges/b9d017718c7824fa2fc0/maintainability)](https://codeclimate.com/github/andela/storm-backend/maintainability) [![Build Status](https://travis-ci.org/andela/storm-backend.svg?branch=develop)](https://travis-ci.org/andela/storm-backend) [![Coverage Status](https://coveralls.io/repos/github/andela/storm-backend/badge.svg)](https://coveralls.io/github/andela/storm-backend)
Barefoot Nomad - Making company travel and accomodation easy and convinient.
=======

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
},
"devDependencies": {
"@babel/node": "^7.5.5",
"babel-plugin-istanbul": "^5.2.0",
"bcrypt": "^3.0.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
Expand Down Expand Up @@ -104,6 +105,8 @@
],
"exclude": [
"src/test/**"
]
],
"sourceMap": false,
"instrument": false
}
}
46 changes: 36 additions & 10 deletions src/controllers/accommodationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import models from '../models';
import response from '../utils/response';
import messages from '../utils/messages';
import DbServices from '../services/dbServices';
import { createRating, getAverageRatingByAccommodation } from '../services/ratingService';

const {
Accommodation, BookAccomodation, User, Request, AccomodationFeedback, AccommodationLike
Expand Down Expand Up @@ -137,14 +138,10 @@ const accomodationFeedback = async (req, res) => {
const {
message
} = req.body;
const accommodation = await getById(Accommodation, accommodationId, {});
if (!accommodation) return response(res, 404, 'error', { message: messages.notExistAccommodation });
await create(AccomodationFeedback, { accommodationId, userId, message });
return response(res, 200, 'success', { message: accommodationFeedbackPosted });
} catch (error) {
return response(res, 500, 'error', {
message: serverError,
});
return response(res, 500, 'error', { message: error.message });
}
};

Expand All @@ -157,16 +154,45 @@ const getByDestinationCity = async (req, res) => {
try {
const { destinationCity } = req.params;
const options = { where: { city: { [Op.iLike]: `%${destinationCity}%` } } };
const accommodation = await getAllRecord(Accommodation, options);
let accommodations = await getAllRecord(Accommodation, options);

accommodations = await Promise.all(
accommodations.map(async (accommodation) => {
accommodation.dataValues.rating = await getAverageRatingByAccommodation(accommodation);
return accommodation;
})
);

return response(res, 200, 'success', accommodations);
} catch (error) {
return response(res, 500, 'error', { message: error.message });
}
};

/**
* @method rateAccommodation
* @param {object} req request object
* @param {object} res request object
* @returns {object} custom response
* @description rates an accomodation
*/
const rateAccommodation = async (req, res) => {
try {
const {
params: { accommodationId },
body: { value },
decoded: { id: userId },
accommodation
} = req;
await createRating(userId, accommodationId, value);
accommodation.dataValues.rating = await getAverageRatingByAccommodation(accommodation);
return response(res, 201, 'success', accommodation);
} catch (error) {
return response(res, 500, 'error', {
message: serverError,
});
return response(res, 500, 'error', { message: error.message });
}
};

export {
createAccommodation, bookAccommodation, accomodationFeedback,
likeAccommodation, getByDestinationCity
likeAccommodation, getByDestinationCity, rateAccommodation
};
45 changes: 45 additions & 0 deletions src/database/migrations/20190914085520-create-rating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Ratings', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID
},
value: {
allowNull: false,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: 'Users',
key: 'id',
},
},
accommodationId: {
type: Sequelize.UUID,
allowNull: false,
references: {
model: 'Accommodation',
key: 'id',
},
},
createdAt: {
allowNull: true,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW')
},
updatedAt: {
allowNull: true,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW')
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Ratings');
}
};
15 changes: 13 additions & 2 deletions src/database/seeders/20190819153902-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default {
id: '0ce36391-2c08-4703-bddb-a4ea8cccbbc5',
firstName: 'Manager',
lastName: 'Jude',
email: 'judecodes@gmail.com',
email: 'mj@gmail.com',
password: hashPassword('password'),
phoneNo: '2347032123404',
verified: true,
Expand All @@ -48,7 +48,7 @@ export default {
id: '83b2a3e7-9ba4-4d3f-b3a3-d31940ee2edc',
firstName: 'Requester',
lastName: 'Jude',
email: 'judecodes@yahoo.com',
email: 'rj@yahoo.com',
password: hashPassword('password'),
phoneNo: '2347032123409',
lineManager: '0ce36391-2c08-4703-bddb-a4ea8cccbbc5',
Expand Down Expand Up @@ -100,6 +100,17 @@ export default {
lineManager: '0ce36391-2c08-4703-bddb-a4ea8cccbbc5',
roleId: roles.ACCOMMODATION_SUPPLIER,
},
{
id: 'a2ff3c34-4b2b-449e-aa35-6587f56fdf99',
firstName: 'Supplier',
lastName: 'James',
email: 'unverifiedsupplier@gmail.com',
password: hashPassword('password'),
phoneNo: '2347032123432',
verified: false,
lineManager: '0ce36391-2c08-4703-bddb-a4ea8cccbbc5',
roleId: roles.ACCOMMODATION_SUPPLIER,
},
{
id: 'c1b0a0fc-7536-4152-8837-6a5348ba9566',
firstName: 'bruce',
Expand Down
12 changes: 5 additions & 7 deletions src/middlewares/accommodationMiddlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ const { getById } = DbServices;
export const checkAccommodationId = async (req, res, next) => {
try {
const { accommodationId } = req.params;
if (accommodationId) {
const accommodation = await getById(Accommodation, accommodationId, {});
if (!accommodation) {
return response(res, 404, 'error', { message: messages.notExistAccommodation });
}
return next();
const accommodation = await getById(Accommodation, accommodationId, {});
if (!accommodation) {
return response(res, 404, 'error', { message: messages.notExistAccommodation });
}
req.accommodation = accommodation;
return next();
} catch (error) {
return response(res, 500, 'error', { message: messages.serverError });
return response(res, 500, 'error', { message: error.message });
}
};
6 changes: 6 additions & 0 deletions src/models/accommodation.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,11 @@ module.exports = (sequelize, DataTypes) => {
}
});

Accommodation.associate = (models) => {
Accommodation.hasMany(models.Rating, {
foreignKey: 'accommodationId'
});
};

return Accommodation;
};
27 changes: 27 additions & 0 deletions src/models/rating.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default (sequelize, DataTypes) => {
const Rating = sequelize.define('Rating', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
value: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {});

Rating.associate = (models) => {
Rating.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
Rating.belongsTo(models.Accommodation, {
foreignKey: 'accommodationId',
as: 'accommodation'
});
};

return Rating;
};
79 changes: 76 additions & 3 deletions src/routes/api/accommodation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import validator from '../../middlewares/validator';
import { checkAccommodationId } from '../../middlewares/accommodationMiddlewares';
import {
createAccommodation, bookAccommodation, accomodationFeedback,
likeAccommodation, getByDestinationCity
likeAccommodation, getByDestinationCity, rateAccommodation
} from '../../controllers/accommodationController';
import checkBlacklist from '../../middlewares/blacklistMiddleware';
import {
accommodationSchema, bookAccommodationSchema, accomodationFeedbackSchema,
accommodationIdSchema, destinationCitySchema
accommodationIdSchema, destinationCitySchema, accommodationRatingSchema
} from '../../validation/accommodationSchema';
import authorize from '../../middlewares/authorizer';
import roles from '../../utils/roles';
Expand Down Expand Up @@ -48,6 +48,8 @@ const accommodationRoute = (router) => {
* type: array
* items:
* type: string
* rating:
* type: integer
* createdAt:
* type: string
* format: date-time
Expand Down Expand Up @@ -389,6 +391,76 @@ const bookAccommodationRoute = (router) => {
*/
.patch(checkToken, checkBlacklist, validator(accommodationIdSchema),
checkAccommodationId, likeAccommodation);

router.route('/accommodations/:accommodationId/rate')
/**
* @swagger
* components:
* schemas:
* Rating:
* properties:
* value:
* type: integer
* ErrorResponse:
* properties:
* status:
* type: string
* example: error
* data:
* type: string
*/
/**
* @swagger
* /api/v1/accommodations/{accommodationId}/rate:
* post:
* tags:
* - Accommodation
* description: Rate an accomodation
* parameters:
* - in: path
* name: accommodationId
* schema:
* type: string
* format: uuid
* required: true
* requestBody:
* description: Rating value
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Rating'
* produces:
* - application/json
* responses:
* 201:
* description: Rating recorded successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: object
* data:
* $ref: '#/components/schemas/accommodation'
* 404:
* description: Accommodation not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
* 500:
* description: Internal Server error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
* security:
* - bearerAuth: []
*/
.post(checkToken, checkBlacklist, validator(accommodationRatingSchema),
checkAccommodationId, rateAccommodation);
};

const accomodationFeedbackRoute = (router) => {
Expand Down Expand Up @@ -476,7 +548,8 @@ const accomodationFeedbackRoute = (router) => {
* security:
* - bearerAuth: []
*/
.post(checkToken, checkBlacklist, validator(accomodationFeedbackSchema), accomodationFeedback);
.post(checkToken, checkBlacklist, validator(accomodationFeedbackSchema),
checkAccommodationId, accomodationFeedback);
};

export {
Expand Down
Loading

0 comments on commit b724161

Please sign in to comment.