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

Add Advanced reviews/rating APIs #988

Merged
merged 2 commits into from
Nov 10, 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
123 changes: 123 additions & 0 deletions backend/controllers/rent/ReviewControllers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const { validationResult } = require("express-validator");
const RentProduct = require("../../model/rent/rentProduct");
const winston = require("winston");

// Logger configuration
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
level: 'info',
}),
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
],
});

// Controller for submitting a review
exports.submitReview = async (req, res) => {
const { productId } = req.params;
const { rating, comment } = req.body;
const { userId } = req.user; // Assuming user is authenticated and userId is available in req.user

// Validation check for rating and comment
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

try {
const product = await RentProduct.findById(productId);
if (!product) {
logger.error(`Product with id ${productId} not found`);
return res.status(404).json({ msg: "Product not found" });
}

// Create new review object
const newReview = { userId, rating, comment };

// Add review to the product
product.reviews.push(newReview);

// Update product rating based on the new reviews
await product.updateProductRating();

// Save the product with the new review
await product.save();

logger.info(`Review submitted successfully for product ${productId}`);
res.status(201).json({ msg: "Review submitted successfully" });
} catch (error) {
logger.error(`Error submitting review: ${error.message}`);
res.status(500).json({ msg: "Server Error", error: error.message });
}
};

// Controller for fetching all reviews of a product with pagination and sorting
exports.getReviews = async (req, res) => {
const { productId } = req.params;
const { page = 1, limit = 10, order_by = "createdAt" } = req.query;

try {
const product = await RentProduct.findById(productId);
if (!product) {
logger.error(`Product with id ${productId} not found`);
return res.status(404).json({ msg: "Product not found" });
}

// Aggregate and fetch reviews based on pagination and sorting
const reviews = await RentProduct.aggregate([
{ $match: { _id: productId } },
{ $unwind: "$reviews" },
{ $sort: { [`reviews.${order_by}`]: -1 } },
{ $skip: (page - 1) * limit },
{ $limit: limit },
{ $project: { reviews: 1 } },
]);

if (reviews.length === 0) {
logger.warn(`No reviews found for product ${productId}`);
return res.status(404).json({ msg: "No reviews found" });
}

res.json(reviews);
} catch (error) {
logger.error(`Error fetching reviews: ${error.message}`);
res.status(500).json({ msg: "Server Error", error: error.message });
}
};

// Controller for fetching rating distribution of a product
exports.getRatingDistribution = async (req, res) => {
const { productId } = req.params;

try {
const product = await RentProduct.findById(productId);
if (!product) {
logger.error(`Product with id ${productId} not found`);
return res.status(404).json({ msg: "Product not found" });
}

// Aggregate to count the number of ratings for each score (1-5)
const ratingDistribution = await RentProduct.aggregate([
{ $match: { _id: productId } },
{ $unwind: "$reviews" },
{ $group: { _id: "$reviews.rating", count: { $sum: 1 } } },
{ $project: { rating: "$_id", count: 1, _id: 0 } },
{ $sort: { rating: 1 } },
]);

// Fill in missing ratings (e.g., if no 1-star ratings, we still show 0)
const distribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
ratingDistribution.forEach((entry) => {
distribution[entry.rating] = entry.count;
});

res.json(distribution);
} catch (error) {
logger.error(`Error fetching rating distribution: ${error.message}`);
res.status(500).json({ msg: "Server Error", error: error.message });
}
};
2 changes: 2 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const rentalRoutes = require('./routes/rent/rentalRoutes');
const adminOrderRoutes = require('./routes/rent/adminOrderRoutes');
const adminUserControlRoutes = require('./routes/rent/adminUserControlRoutes');
const analyticsRoutes = require('./routes/rent/analyticsRoutes');
const reviewRoutes = require('./routes/rent/reviewRoutes');

const ratingRoutes = require('./routes/rent/ratingRoutes');

Expand Down Expand Up @@ -59,6 +60,7 @@ app.use('/api', rentalRoutes);
app.use('/api', adminOrderRoutes);
app.use('/api/admin', adminUserControlRoutes);
app.use('/api', analyticsRoutes);
app.use('/api', reviewRoutes);

app.use('/api', ratingRoutes);

Expand Down
131 changes: 69 additions & 62 deletions backend/model/rent/rentProduct.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,78 @@
const mongoose = require('mongoose');
const mongoose = require("mongoose");

const reviewSchema = new mongoose.Schema({
rentalId: String,
rating: { type: Number, required: true, min: 0, max: 5 },
comment: String,
});

const rentProductSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true }, // Base price for the product (purchase price)
image: { type: String, required: true },
category: { type: [String], required: true },


availabilityStatus: {
type: String,
enum: ['available', 'rented', 'maintenance'],
default: 'available',
},

rentalPricePerDay: {
type: Number,
required: true,
min: 0, // Minimum daily rental price
},

rentalDurationOptions: {
type: [String],
required: true,
enum: ['hourly', 'daily', 'weekly', 'monthly'],
}, // Available rental durations

maxRentalDuration: {
type: Number,
required: true,
min: 1, // Minimum rental period (e.g., in days, weeks, etc.)
},

depositAmount: {
type: Number,
default: 0,
const reviewSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
rating: { type: Number, required: true, min: 0, max: 5 },
comment: { type: String },
},
{ timestamps: true }
);

rentalTerms: {
type: String,
maxlength: 500,
const rentProductSchema = new mongoose.Schema(
{
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
image: { type: String, required: true },
category: { type: [String], required: true },
availabilityStatus: {
type: String,
enum: ["available", "rented", "maintenance"],
default: "available",
},
rentalPricePerDay: {
type: Number,
required: true,
min: 0,
},
rentalDurationOptions: {
type: [String],
required: true,
enum: ["hourly", "daily", "weekly", "monthly"],
},
maxRentalDuration: {
type: Number,
required: true,
min: 1,
},
depositAmount: {
type: Number,
default: 0,
},
rentalTerms: {
type: String,
maxlength: 500,
},
rentedQuantity: {
type: Number,
default: 0,
},
reviews: [reviewSchema],
rating: { type: Number, default: 0, min: 0, max: 5 },
},

rentedQuantity: {
type: Number,
default: 0,
},


reviews: [reviewSchema],


rating: { type: Number, default: 0, min: 0, max: 5 },
}, { timestamps: true });
{ timestamps: true }
);

// Method to update product rating based on reviews
rentProductSchema.methods.updateProductRating = function() {
const totalRating = this.reviews.reduce((sum, review) => sum + review.rating, 0);
const averageRating = this.reviews.length ? totalRating / this.reviews.length : 0;
rentProductSchema.methods.updateProductRating = async function () {
const totalRating = this.reviews.reduce(
(sum, review) => sum + review.rating,
0
);
const averageRating = this.reviews.length
? totalRating / this.reviews.length
: 0;
this.rating = averageRating;
return this.save();
await this.save();
};

module.exports = mongoose.models.RentProduct || mongoose.model('RentProduct', rentProductSchema);
const RentProduct =
mongoose.models.RentProduct ||
mongoose.model("RentProduct", rentProductSchema);

module.exports = RentProduct;
Loading
Loading