Skip to content

Commit

Permalink
added new checks before creating a review:
Browse files Browse the repository at this point in the history
- check if the rating field provded exists
- check if there are no dupicates in fields reviews
- check if user provided field reviews count match the rating fields count
  • Loading branch information
TresorRw committed May 8, 2024
1 parent 2bfa091 commit f9d9ea0
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 51 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint:fix": "eslint . --fix",
"build": "babel src --out-dir dist --source-maps inline --copy-files",
"start": "babel-node dist/app.js",
"test": "cross-env NODE_ENV=test npm run db:migrate && npm run db:reset && cross-env NODE_ENV=test PORT=4300 mocha --require @babel/register --exit 'src/tests/**/*.test.js'",
"test": "cross-env NODE_ENV=test npm run db:migrate && cross-env NODE_ENV=test npm run db:reset && cross-env NODE_ENV=test PORT=4300 mocha --require @babel/register --exit 'src/tests/**/*.test.js'",
"test:coverage": " nyc mocha --coverage --recursive --require @babel/polyfill --require @babel/register */tests/*.js --timeout 500000000 --exit",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"db:migrate": "sequelize db:migrate",
Expand Down
1 change: 1 addition & 0 deletions src/database/config/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const sequelize = new Sequelize(String(url), {
min: 0,
max: 5,
},
logging: NODE_ENV === "development",
logQueryParameters: NODE_ENV === "development",
benchmark: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ module.exports = {
key: "id",
},
},
isReviewd: {
type: Sequelize.BOOLEAN,
defaultValue: false,
},
type: {
type: Sequelize.STRING,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ module.exports = {
key: "id",
},
},
overallReviewId: {
type: Sequelize.UUID,
references: {
model: "tbl_overall_reviews",
key: "id",
},
},
ratings: {
type: Sequelize.INTEGER,
allowNull: false,
Expand Down
7 changes: 7 additions & 0 deletions src/database/models/fieldReview.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ const FieldReviewModel = () => {
key: "id",
},
},
overallReviewId: {
type: DataTypes.UUID,
references: {
model: "tbl_overall_reviews",
key: "id",
},
},
ratings: {
type: DataTypes.INTEGER,
allowNull: false,
Expand Down
4 changes: 0 additions & 4 deletions src/database/models/overallReview.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ const OverallReviewModel = () => {
key: "id",
},
},
isReviewd: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
type: {
type: DataTypes.STRING,
},
Expand Down
4 changes: 4 additions & 0 deletions src/database/relationships/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,16 @@ export const associate = () => {

DB.RatingCategory.hasMany(DB.RatingField, {
foreignKey: "categoryId",
as: "ratingFields",
});

DB.RatingField.belongsTo(DB.RatingCategory, {
foreignKey: "categoryId",
as: "ratingCategory",
});

DB.OverallReview.hasMany(DB.FieldReview, {
as: "fieldReviews",
foreignKey: "overallReviewId",
});

Expand All @@ -98,6 +101,7 @@ export const associate = () => {

DB.FieldReview.belongsTo(DB.RatingField, {
foreignKey: "ratingFieldId",
as: "ratingField",
onDelete: "CASCADE",
});
};
107 changes: 81 additions & 26 deletions src/restful/controllers/reviewControllers.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,77 @@
/* eslint-disable no-constant-condition */
/* eslint-disable no-useless-catch */
import Response from "../../system/helpers/Response";
import { reviewerType } from "../../system/utils/riviewerType";
import ReviewService from "../../services/reviewServices";
import UserService from "../../services/userService";
import ReviewCycleService from "../../services/reviewCycleServices";
import RatingFieldService from "../../services/ratingFieldService";
import { overallReviewSchema } from "../../system/validators";
import { safeParse } from "valibot";
import { filterValidationError } from "../../system/utils";

export default class ReviewControllers {
static async create(req, res) {
const { id: reviewerId } = req.user;
const validationResults = safeParse(overallReviewSchema, req.body);
if (validationResults.issues) {
return Response.error(res, 400, {
message: "Please provide all required fields",
errors: filterValidationError(validationResults.issues),
});
}
if (validationResults.output.fieldReviews.length === 0) {
return Response.error(res, 400, {
message: "Please provide at least one field review",
});
}

const {
revieweeId,
reviewCycleId,
fieldReviews: fieldReviewsData,
} = validationResults.output;

// Checking if ratingFieldId is not in the array of field reviews more than once
const duplicatesExist = fieldReviewsData.filter((currentReview, index) =>
fieldReviewsData.some(
(otherReview, i) =>
otherReview.ratingFieldId === currentReview.ratingFieldId &&
i !== index
)
);
if (duplicatesExist.length > 0) {
return Response.error(res, 400, {
message: "You have duplicate ratingFieldIds in your field reviews",
});
}

try {
const { id: reviewerId } = req.user;
const { revieweeId, description, ratings, reviewCycleId } = req.body;
const allRatingFields = await RatingFieldService.findAllRatingFields();

// if number of rating fields does not match with user provided rating fields
if (allRatingFields.length !== fieldReviewsData.length) {
return Response.error(res, 400, {
message: "Please provide all ratingFieldIds required for a review",
});
}

// check if user provided ratingFields that are not in the database
const misMatches = fieldReviewsData
.filter(
(fieldReview) =>
!allRatingFields.some(
(ratingField) => ratingField.id === fieldReview.ratingFieldId
)
)
.map((fieldReview) => fieldReview.ratingFieldId);

if (misMatches.length > 0) {
return Response.error(res, 400, {
message: `We ca't find these ratingFieldIds: ${misMatches.join(
", "
)}`,
});
}

const selectedReviewer = await ReviewService.findReviewer(
reviewerId,
Expand Down Expand Up @@ -66,34 +127,28 @@ export default class ReviewControllers {
type,
});
if (reviewMade) {
return Response.error(res, 401, {
message: "review have been made",
return Response.error(res, 409, {
message: "Review have been made",
});
}
let ratingz = type === "manager review" ? Number(ratings) : null;
ReviewService.create({
revieweeId,
reviewerId,
description,

const overallReview = await ReviewService.createReview({
...validationResults.output,
type,
ratingz,
reviewCycleId,
})
.then((review) => {
return Response.success(res, 200, {
message: "review created successfully",
data: review,
});
})
.catch((error) => {
return Response.error(res, 403, {
message: "review failed to be created",
error: error.message,
});
});
reviewerId,
});
const { id } = overallReview;
const fieldReviews = await ReviewService.createFieldReviews(
fieldReviewsData,
id
);
return Response.success(res, 201, {
message: "Review created successfully",
data: { ...overallReview.dataValues, fieldReviews },
});
} catch (error) {
return Response.error(res, 500, {
message: "server error",
message: "Problem while creating review",
error: error.message,
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/services/ratingCategoryService.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default class RatingCategoryService {
include: [
{
model: RatingField,
as: "ratingFields",
attributes: ["id", "name"],
},
],
Expand Down Expand Up @@ -50,6 +51,7 @@ export default class RatingCategoryService {
include: [
{
model: RatingField,
as: "ratingFields",
attributes: ["id", "name"],
},
],
Expand Down
63 changes: 47 additions & 16 deletions src/services/reviewServices.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,48 @@
/* eslint-disable no-useless-catch */
import { Op } from "sequelize";
import db from "./../database";
const { Review, Reviewer, User } = db;
const { OverallReview, Reviewer, User, FieldReview, RatingField } = db;

export default class ReviewService {
/**
* Creates a new message.
* @param {object} param details of a message.
* @returns {object} users new message.
*/

static async create(param) {
const review = await Review.create(param);
return review;
static async createReview(params) {
const { comment, reviewerId, revieweeId, reviewCycleId, type } = params;
try {
const overallReview = await OverallReview.create({
comment,
reviewerId,
revieweeId,
reviewCycleId,
type,
});
return overallReview;
} catch (error) {
throw error;
}
}

static async createFieldReviews(params, overallReviewId) {
try {
const fieldReviews = await FieldReview.bulkCreate(
params.map((fieldReview) => ({
...fieldReview,
overallReviewId,
}))
);
return fieldReviews;
} catch (error) {
throw error;
}
}

static async findONE(param) {
const Reviews = await Review.findOne({
const Reviews = await OverallReview.findOne({
where: param,
});
return Reviews;
}

static async findAll() {
const Reviews = await Review.findAll({
const Reviews = await OverallReview.findAll({
include: [
{
model: User,
Expand All @@ -35,18 +54,30 @@ export default class ReviewService {
as: "reviewee",
attributes: ["displayName", "email", "role"],
},
{
model: FieldReview,
as: "fieldReviews",
attributes: ["id", "ratings"],
include: [
{
model: RatingField,
as: "ratingField",
attributes: ["name"],
},
],
},
],
});
return Reviews;
}

static async findById(id) {
const Review = await Review.findByPk(id);
const Review = await OverallReview.findByPk(id);
return Review;
}

static async delete(id) {
const review = await Review.destroy(id);
const review = await OverallReview.destroy(id);
return review;
}

Expand Down Expand Up @@ -171,7 +202,7 @@ export default class ReviewService {
}

static async getReviews(developerId, reviewCycleId) {
const givenReviews = await Review.findAll({
const givenReviews = await OverallReview.findAll({
where: {
reviewCycleId,
reviewerId: developerId,
Expand All @@ -194,7 +225,7 @@ export default class ReviewService {
],
});

const receivedReviews = await Review.findAll({
const receivedReviews = await OverallReview.findAll({
where: {
reviewCycleId,
revieweeId: developerId,
Expand Down
15 changes: 15 additions & 0 deletions src/system/validators/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,18 @@ export const ratingFieldSchema = v.object({
});

export const ratingCategorySchema = v.pick(ratingFieldSchema, ["name"]);

export const overallReviewSchema = v.object({
comment: v.string([v.minLength(1, "Comment should not be empty.")]),
revieweeId: v.uuid("Invalid Reviewee ID."),
reviewCycleId: v.uuid("Invalid Review Cycle ID."),
fieldReviews: v.array(
v.object({
ratingFieldId: v.uuid("Invalid Rating Field ID."),
ratings: v.number([
v.minValue(1, "Rating should be greater than or equal to 1."),
v.maxValue(5, "Rating should be less than or equal to 5."),
]),
})
),
});

0 comments on commit f9d9ea0

Please sign in to comment.