Skip to content

Commit

Permalink
ft(accomodation): like/unlike an accommodation
Browse files Browse the repository at this point in the history
- add new table/model `AccommodationLike`
- add controller to like/unlike an accommodation
- add endpoint to like/unlike an accommodation
- `PATCH: /accommodation/:accommodationId/like

[Finishes (#167727755)]
  • Loading branch information
Mcdavid95 committed Sep 10, 2019
1 parent 1faf5a6 commit db53763
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 10 deletions.
26 changes: 24 additions & 2 deletions src/controllers/accommodationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import DbServices from '../services/dbServices';


const {
Accommodation, BookAccomodation, User, Request
Accommodation, BookAccomodation, User, Request, AccommodationLike
} = models;
const { create, getById } = DbServices;
const { create, getById, getOrCreate } = DbServices;
const { serverError } = messages;

/**
Expand Down Expand Up @@ -93,4 +93,26 @@ const bookAccommodation = async (req, res) => {
}
};

/**
* @function likeAccommodation
* @param {Object} req - server request
* @param {Object} res - server response
* @returns {Object} - custom response
*/
export const likeAccommodation = async (req, res) => {
const { decoded: { id }, params: { accommodationId } } = req;
try {
const options = { where: { userId: id, accommodationId }, defaults: { liked: true } };
const [feedback, created] = await getOrCreate(AccommodationLike, options);
if (!created) {
feedback.liked = !feedback.liked;
const savedFeedback = await feedback.save();
return response(res, 202, 'success', savedFeedback);
}
return response(res, 201, 'success', feedback);
} catch (error) {
return response(res, 500, 'error', { message: serverError });
}
};

export { createAccommodation, bookAccommodation };
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('AccommodationLikes', {
id: {
allowNull: false,
primaryKey: true,
type: Sequelize.UUID,
},
liked: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: true
},
userId: {
type: Sequelize.UUID,
references: {
model: 'Users',
key: 'id',
},
},
accommodationId: {
type: Sequelize.UUID,
references: {
model: 'Accommodation',
key: 'id',
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('AccommodationLikes');
}
};
31 changes: 31 additions & 0 deletions src/middlewares/accommodationMiddlewares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import response from '../utils/response';
import messages from '../utils/messages';
import DbServices from '../services/dbServices';
import models from '../models';

const { Accommodation } = models;
const { getById } = DbServices;

/**
* @method checkAccommodationId
* @param {object} req request object
* @param {object} res request object
* @param {function} next next function
* @returns {object} custom response
* @description checks if accommodationId passed to params is valid
*/
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();
}
return next();
} catch (error) {
return response(res, 500, 'error', { message: messages.serverError });
}
};
28 changes: 28 additions & 0 deletions src/models/accommodationLike.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default (sequelize, DataTypes) => {
const AccommodationLike = sequelize.define('AccommodationLike', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
liked: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
}
}, {});
AccommodationLike.associate = (models) => {
// associations can be defined here
AccommodationLike.belongsTo(models.Accommodation, {
foreignKey: 'accommodationId',
as: 'accommodation'
});

AccommodationLike.belongsTo(models.User, {
foreignKey: 'userId',
as: 'user'
});
};
return AccommodationLike;
};
84 changes: 79 additions & 5 deletions src/routes/api/accommodation.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createAccommodation, bookAccommodation } from '../../controllers/accommodationController';
import { createAccommodation, bookAccommodation, likeAccommodation } from '../../controllers/accommodationController';
import { checkToken } from '../../middlewares/userMiddlewares';
import validate from '../../middlewares/validator';
import { accommodationSchema, bookAccommodationSchema } from '../../validation/accommodationSchema';
import { accommodationSchema, bookAccommodationSchema, accommodationIdSchema } from '../../validation/accommodationSchema';
import { checkAccommodationId } from '../../middlewares/accommodationMiddlewares';
import validator from '../../middlewares/validator';

const accommodationRoute = (router) => {
router.route('/accommodation')
Expand Down Expand Up @@ -92,7 +93,7 @@ const accommodationRoute = (router) => {
* security:
* - bearerAuth: []
*/
.post(checkToken, validate(accommodationSchema), createAccommodation);
.post(checkToken, validator(accommodationSchema), createAccommodation);
};


Expand Down Expand Up @@ -200,7 +201,80 @@ const bookAccommodationRoute = (router) => {
* security:
* - bearerAuth: []
*/
.post(checkToken, validate(bookAccommodationSchema), bookAccommodation);
.post(checkToken, validator(bookAccommodationSchema), bookAccommodation);

/**
* @swagger
* components:
* schemas:
* AccommodationLike:
* properties:
* id:
* type: string
* readOnly: true
* liked:
* type: boolean
* createdAt:
* type: string
* format: date-time
* readOnly: true
* updateAt:
* type: string
* format: date-time
* readOnly: true
* ErrorResponse:
* properties:
* status:
* type: string
* example: error
* data:
* type: string
*/
router.route('/accommodations/:accommodationId/like')
/**
* @swagger
* /api/v1/accommodations/{accommodationId}/like:
* patch:
* tags:
* - Accommodation
* description: Users like and unlike
* parameters:
* - in: path
* name: accommodationId
* schema:
* type: string
* format: uuid
* required: true
* produces:
* - application/json
* responses:
* 201:
* description: Feedback recorded
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: object
* data:
* $ref: '#/components/schemas/AccommodationLike'
* 400:
* description: Input validation error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
* 500:
* description: Internal Server error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
* security:
* - bearerAuth: []
*/
.patch(checkToken, validator(accommodationIdSchema), checkAccommodationId, likeAccommodation);
};

export {
Expand Down
10 changes: 10 additions & 0 deletions src/services/dbServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ const DbServices = {
return model.findAll(options);
},

/**
* @param {object} model model /table
* @param {object} options query options
* @returns {Promise} Promise resolved or rejected
* @description gets all items that fit the criteria and returns rows and count
*/
getOrCreate(model, options) {
return model.findOrCreate(options);
},

/**
* Database create service funcion
* @param {Object} model - Defined model
Expand Down
6 changes: 5 additions & 1 deletion src/test/mockData/accommodationMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@ const inValidBookingDate = {
checkIn: '2019-09-09',
checkOut: '2019-09-06'
};
const accommodationId = '122a0d86-8b78-4bb8-b28f-8e5f7811c452';
const wrongAccommodationId = '127a0d86-8b78-4bb8-b28f-8e5f7811c452';

export {
validAccommodationDetail,
inValidAccommodationDetail,
validBookingDetails,
inValidBookingDetails,
inValidBookingDate,
userId
userId,
accommodationId,
wrongAccommodationId
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
import { generateToken } from '../../utils/authHelper';
import {
validAccommodationDetail, inValidAccommodationDetail, userId,
validBookingDetails, inValidBookingDetails, inValidBookingDate
validBookingDetails, inValidBookingDetails, inValidBookingDate,
accommodationId, wrongAccommodationId
} from '../mockData/accommodationMock';

let id;
Expand Down Expand Up @@ -135,4 +136,33 @@ describe('Create Accommodation', () => {
});
});
});

describe('PATCH /accommodations/:accommodationId/like', () => {
const likeAccommodationEndpoint = `${BACKEND_BASE_URL}/accommodations/${accommodationId}/like`;
const wrongLikeAccommodationEndpoint = `${BACKEND_BASE_URL}/accommodations/${wrongAccommodationId}/like`;

it('should return 201 when an accommodation is successfully liked for the first time', async () => {
const response = await chai.request(app)
.patch(likeAccommodationEndpoint)
.set('authorization', token);
const { status, body: { data: { liked } } } = response;
expect(status).to.equal(201);
expect(liked).to.equal(true);
});

it('should return 202 when an accommodation is successfully unliked', async () => {
const { status, body: { data: { liked } } } = await chai.request(app)
.patch(likeAccommodationEndpoint)
.set('authorization', token);
expect(status).to.equal(202);
expect(liked).to.equal(false);
});

it('should return 404 when an accommodationId is wrong', async () => {
const { status } = await chai.request(app)
.patch(wrongLikeAccommodationEndpoint)
.set('authorization', token);
expect(status).to.equal(404);
});
});
});
7 changes: 6 additions & 1 deletion src/validation/accommodationSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ const bookAccommodationSchema = Joi.object({
children: JoiValidator.validateNumber().min(0)
});

const accommodationIdSchema = Joi.object({
accommodationId: JoiValidator.validateString().uuid().required()
});

export {
accommodationSchema,
bookAccommodationSchema
bookAccommodationSchema,
accommodationIdSchema
};

0 comments on commit db53763

Please sign in to comment.