Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buyer should be able to provide a review#89 #104

Merged
merged 1 commit into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions src/__test__/review.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import request from 'supertest';
import app from '../app';
import {
afterAllHook,
beforeAllHook,
getBuyerToken,
getVendorToken,
} from './testSetup';
beforeAll(beforeAllHook);
afterAll(afterAllHook);

describe('Review controller test', () => {
let buyerToken: string;
let vendorToken: string;
let productId: number;
let categoryId: number;

beforeAll(async () => {
buyerToken = await getBuyerToken();
vendorToken = await getVendorToken();
});

it('should return review product has successfully', async () => {
const categoryData = {
name: 'Category4',
description: 'category description',
};

const categoryResponse = await request(app)
.post('/api/v1/category')
.set('Authorization', `Bearer ${vendorToken}`)
.send(categoryData);

categoryId = categoryResponse.body.data.id;

const productData = {
name: 'New Product Two',
image: 'new_product.jpg',
gallery: [],
shortDesc: 'This is a new product',
longDesc: 'Detailed description of the new product',
categoryId: categoryId,
quantity: 10,
regularPrice: 5,
salesPrice: 4,
tags: ['tag1', 'tag2'],
type: 'Simple',
isAvailable: true,
};

const responseProduct = await request(app)
.post('/api/v1/product')
.set('Authorization', `Bearer ${vendorToken}`)
.send(productData);


productId = responseProduct.body.data.id;
const reviewBody = {content:'good', rating:5, productId}
const responseReview = await request(app)
.post('/api/v1/review')
.set('Authorization', `Bearer ${buyerToken}`)
.send(reviewBody);
expect(responseReview.statusCode).toEqual(201);
expect(responseReview.body.message).toEqual('Review created successfully');
expect(responseReview.body.review).toBeDefined();
}),

it('should return 404 if product is not found', async () => {
const reviewBody = {content:'good', rating:5, productId:99}
const responseReview = await request(app)
.post('/api/v1/review')
.set('Authorization', `Bearer ${buyerToken}`)
.send(reviewBody);
expect(responseReview.statusCode).toEqual(404);
expect(responseReview.body.message).toEqual('Product not found');
}),
it('should return 200 Ok to get all reviews ', async () => {
const responseReview = await request(app)
.get('/api/v1/review')
.set('Authorization', `Bearer ${buyerToken}`)
expect(responseReview.statusCode).toEqual(200);
expect(responseReview.body.reviews).toBeDefined();
}),
it('should return 409 if the review exist', async () => {
const reviewBody = {content:'good', rating:5, productId}
const responseReview = await request(app)
.post('/api/v1/review')
.set('Authorization', `Bearer ${buyerToken}`)
.send(reviewBody)
expect(responseReview.statusCode).toEqual(409);
expect(responseReview.body.message).toEqual('you are already reviewed the product');

})
})
4 changes: 2 additions & 2 deletions src/config/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ const staging = {
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
name: process.env.DB_NAME,
name: process.env.DB_NAME_DEV,
dushimeemma marked this conversation as resolved.
Show resolved Hide resolved
};
const production = {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
name: process.env.DB_NAME,
name: process.env.DB_NAME_DEV,
dushimeemma marked this conversation as resolved.
Show resolved Hide resolved
};

const config: {
Expand Down
9 changes: 8 additions & 1 deletion src/controller/productController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,15 @@ export const getProduct = errorHandler(async (req: Request, res: Response) => {
vendor: {
firstName: true,
},
reviews:{
content:true,
rating:true,
user:{
firstName:true
}
},
},
relations: ['category', 'vendor'],
relations: ['category', 'vendor','reviews'],
});

if (!product) {
Expand Down
91 changes: 91 additions & 0 deletions src/controller/reviewController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Request, Response } from 'express';
import { Review } from '../database/models/reviewEntity';
import Product from '../database/models/productEntity';
import dbConnection from '../database';
import errorHandler from '../middlewares/errorHandler';
import UserModel from '../database/models/userModel';

const productRepository = dbConnection.getRepository(Product);
const reviewRepository = dbConnection.getRepository(Review);
const userRepository = dbConnection.getRepository(UserModel);

export const createReview = errorHandler(async (req: Request, res: Response) => {
const { content, rating, productId } = req.body;
const userId = req.user!.id;

const product = await productRepository.findOne({
where:{
id:productId
}
});
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}

const user = await userRepository.findOne({
where: {
id: userId,
},
select: {
id: true,
},
});

const existingReview = await reviewRepository.findOne({
where:{
user:{
id:userId
},
product:{
id:productId
}
}
})

if(existingReview){
return res.status(409).json({ message: 'you are already reviewed the product' });
}

const newReview = new Review();
newReview.content= content;
newReview.rating=parseInt(rating) ;
newReview.user = user!;
newReview.product = product;

const review = await reviewRepository.save(newReview);

const reviews = await reviewRepository.find({
where:{
product:{
id:productId
}
},
relations:['user','product']
});
let totalRating = 0;
for (const review of reviews) {
totalRating += review.rating;
}
product.averageRating = Number((totalRating / reviews.length).toPrecision(2));


await productRepository.save(product);

return res.status(201).json({ message: 'Review created successfully', review });
});


export const getReviews = errorHandler(async (req: Request, res: Response) => {
const reviews = await reviewRepository.find({
select:{
user:{
firstName:true
},
product:{
name:true
}
},
relations:['user','product']
})
return res.status(200).json({ reviews });
});
8 changes: 8 additions & 0 deletions src/database/models/productEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
OneToMany,
} from 'typeorm';
import Category from './categoryEntity';
import UserModel from './userModel';
import { Review } from './reviewEntity';

@Entity()
export default class Product {
Expand Down Expand Up @@ -49,6 +51,12 @@ export default class Product {

@Column({ default: true })
isAvailable: boolean;

@Column('float',{ default:0})
averageRating: number;

@OneToMany(() => Review, review => review.product)
reviews: Review[];

@ManyToOne(() => UserModel, { onDelete: 'CASCADE' })
vendor: UserModel;
Expand Down
22 changes: 22 additions & 0 deletions src/database/models/reviewEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import Product from './productEntity';
import User from './userModel';

@Entity()
export class Review {
@PrimaryGeneratedColumn()
id: number;

@Column()
content: string;

@Column()
rating: number;

@ManyToOne(() => User, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
user: User;

@ManyToOne(() => Product, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
product: Product;
}
66 changes: 66 additions & 0 deletions src/docs/reviewDocs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @swagger
* /api/v1/review:
* post:
* summary: Create a new review
* tags: [Reviews]
* consumes:
* - application/json
* produces:
* - application/json
* parameters:
* - in: body
* name: review
* description: The review object
* required: true
* schema:
* type: object
* properties:
* content:
* type: string
* description: The content of the review
* rating:
* type: integer
* description: The rating of the review (1 to 5)
* productId:
* type: integer
* description: The ID of the product being reviewed
* example:
* content: "This product is amazing!"
* rating: 5
* productId: 50
* responses:
* '201':
* description: Review created successfully
* schema:
* type: object
* properties:
* message:
* type: string
* description: A success message
* review:
* '400':
* description: Bad request, check the request body
* '404':
* description: Product not found
* '409':
* description: User has already reviewed the product
*/

/**
* @swagger
* /api/v1/review:
* get:
* summary: Get reviews by user
* tags: [Reviews]
* security:
* - bearerAuth: []
* responses:
* '200':
* description: Reviews retrieved successfully
* schema:
* type: object
* properties:
* reviews:
* type: array
*/
3 changes: 3 additions & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import buyerRoutes from './buyerRoutes';
import cartRoutes from '../routes/cartRoutes';
import couponRouter from './couponRoute';
import chekoutRoutes from './checkoutRoutes';
import reviewRoute from './reviewRoutes';

const router = Router();

router.use('/user', userRouter);
Expand All @@ -17,5 +19,6 @@ router.use('/buyer', buyerRoutes);
router.use('/cart', cartRoutes);
router.use('/coupons', couponRouter);
router.use('/checkout', chekoutRoutes);
router.use('/review',reviewRoute)

export default router;
11 changes: 11 additions & 0 deletions src/routes/reviewRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import {createReview ,getReviews} from '../controller/reviewController';
import { Router } from 'express';
import { IsLoggedIn } from '../middlewares/isLoggedIn';
import { checkRole } from '../middlewares/authorize';

const reviewRoute = Router();
reviewRoute.use(IsLoggedIn , checkRole(['Buyer']))
reviewRoute.route('/').post( createReview).get(getReviews)

export default reviewRoute;
Loading