增加评论模块
- Review Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const reviewSchema = new Schema({
body: String,
rating: Number
});
module.exports = mongoose.model('Review',reviewSchema);
与campground连接(在campgroundSchema加入)
reviews:[{
type:Schema.Types.ObjectId,
ref:'Review'
}]
- show.ejs 增加review 模块(有client-side validation)
- (记得这个validation在boilerplate里还有验证(script),且form-class名相同)
<form action="/campgrounds/<%=campground._id%>/reviews" method="post" class="mb-3 validated-form" novalidate>
<div class="mb-3">
<label class="form-label" for="rating">Rating</label>
<input class="form-range" type="range" name="review[rating]" id="rating" min="1" max="5">
</div>
<div class="mb-3">
<label class="form-label" for="body">Review</label>
<textarea class="form-control" name="review[body]" id="body" cols="30" rows="3" required></textarea>
</div>
<button class="btn btn-success">Submit</button>
</form>
+Joi验证
- Schema.js:
module.exports.reviewSchema = Joi.object({
review: Joi.object({
rating: Joi.number().required().min(1).max(5),
body: Joi.string().required()
}).required()
})
- app.js
const { campgroundSchema, reviewSchema } = require('./schemas.js');//joi
const validateReview = (req,res,next) => {
const {error} = reviewSchema.validate(req.body);
if (error) {
const msg = error.details.map(el => el.message).join(',');
throw new ExpressError(msg, 400);
} else {
next();
}
}
app.post('/campgrounds/:id/reviews', validateReview , async (req,res) => {
const campground = await Campground.findById(req.params.id);
const review = new Review(req.body.review);
campground.reviews.push(review);
await review.save();
await campground.save();
res.redirect(`/campgrounds/${campground._id}`);
})
Show Reviews
app.js 在show details里的 findById 加populate(‘reviews’),才能传入所有数据
//Show Detail
app.get('/campgrounds/:id', catchAsync( async (req,res) => {
const campground = await Campground.findById(req.params.id).populate('reviews');
res.render('campgrounds/show',{campground});
}));
show.ejs:
(两个‘div class=“col-6”’ 可以把布局分两半)
<div class="col-6">
<h2>Leave a review</h2>
<form action="/campgrounds/<%=campground._id%>/reviews" method="post" class="mb-3 validated-form" novalidate>
<div class="mb-3">
<label class="form-label" for="rating">Rating</label>
<input class="form-range" type="range" name="review[rating]" id="rating" min="1" max="5">
</div>
<div class="mb-3">
<label class="form-label" for="body">Review</label>
<textarea class="form-control" name="review[body]" id="body" cols="30" rows="3" required></textarea>
</div>
<button class="btn btn-success">Submit</button>
</form>
<% for (let review of campground.reviews) {%>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">
Rating: <%= review.rating %>
</h5>
<p class="card-text">
Review: <%= review.body %>
</p>
</div>
</div>
<% } %>
</div>
删除Review
show.ejs:
<form action="/campgrounds/<%=campground._id%>/reviews/<%=review._id%>?_method=DELETE" method="post">
<button class="btn btn-sm btn-danger">DELETE</button>
</form>
app.js:
这样删除review时 campground中的review索引也会被删除
//Delete Review
app.delete('/campgrounds/:id/reviews/:reviewId', catchAsync(async (req,res) => {
//res.send('Deleting!!')
const { id, reviewId } = req.params;
await Campground.findByIdAndUpdate(id, { $pull: { reviews: reviewId } });
//$pull:Mongo操作删除(先从campground中删除索引)
await Review.findByIdAndDelete(reviewId);//再从review中删除整个
res.redirect(`/campgrounds/${id}`);
}));
- 但是现在如果直接删除campgroud, 与之相关的review不会被删除
- 所以加一个中间件,当findByIdAndDelete被触发时,中间件会删除相对应的review
- 与findByIdAndDelete向对应的中间件为findOneAndDelete()(mongoose文档找到):
CampgroundSchema.post('findOneAndDelete', async (doc) => {
if(doc){
await review.deleteMany({
_id:{
$in: doc.reviews
}
})
}
})