스키마 정의
review 스키마를 정의합니다
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ReviewSchema = new Schema({
body: {
type: String,
required: true
},
rating: {
type: String,
required: true
}
});
module.exports = mongoose.model('Review', ReviewSchema);
campground 스키마를 수정합니다. 스키마를 정의하는 부분에서 아래 부분을 추가하면 됩니다.
reviews: [
{
type: Schema.Types.ObjectId,
ref: 'Review'
}
]
리뷰 작성 폼 만들기
show.ejs에서 아래 코드를 추가합니다.
<div class="col-4">
<div class="card">
<div class="card-body">
<form action="/campgrounds/<%= camp.id %>/reviews" method="POST" class="was-validated">
<label for="rating" class="fw-medium form-label fs-5">Rating</label>
<input type="range" class="form-range" min="1" max="5" step="1" id="rating"
name="review[rating]" required>
<label for="body" class="form-label fw-medium fs-5">Review</label>
<br>
<textarea name="review[body]" id="body" class="form-control" cols="30" rows="3"
required></textarea>
<div class="invalid-feedback">
내용을 입력해주세요.
</div>
<div class="text-end mt-2">
<button class="btn btn-success">Submit</button>
</div>
</form>
</div>
</div>
</div>
app.js를 수정합니다.
app.post('/campgrounds/:id/reviews', validateReview, catchAsync(async (req, res) => {
const review = new Review(req.body.review);
const camp = await Campground.findById(req.params.id);
await review.save();
camp.reviews.push(review);
await camp.save();
res.redirect(`/campgrounds/${req.params.id}`);
}))
해당 캠핑장에 리뷰를 생성하는 코드입니다. 일단 validateReview는 무시합니다. catchAsync 함수는 비동기함수에서 발생하는 에러를 잡아줍니다.
유효성 검사 추가
클라이언트측에서 발생할 수 있는 유효성 검사는 다 막아뒀지만 postman같은 곳에서 들어오는 요청에도 유효성 검사를 해야합니다.
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();
}
}
이상한 요청이 들어오면 네가 잘못했다는 400 에러를 날려줍니다.
리뷰 표시하기
리뷰 작성 폼 밑에 작성된 리뷰들을 띄어보겠습니다.
먼저 show.ejs에 아래 코드를 추가합니다.
<% for(let review of camp.reviews) {%>
<div class="card mt-3">
<div class="card-body">
<p class="card-title fs-5 fw-medium">
Rating: <%= review.rating %>
</p>
<p>
<%= review.body %>
</p>
<form action="/campgrounds/<%= camp.id %>/reviews/<%= review.id %>?_method=DELETE"
method="POST">
<div class="text-end mt-2">
<button class="btn btn-danger">Delete</button>
</div>
</form>
</div>
</div>
<% } %>
app.js에 아래 코드를 추가합니다.
app.get('/campgrounds/:id', catchAsync(async (req, res) => {
const camp = await Campground.findById(req.params.id).populate('reviews');
res.render('show', { camp });
}))
findById뒤에 populate를 추가하였습니다. 캠핑장의 리뷰 배열에는 리뷰의 id들 밖에 없었지만 populate를 써주어 리뷰 배열에 리뷰의 모든 정보가 다 들어가게 됩니다.
리뷰 삭제하기
리뷰를 지우기를 클릭하면 해당 라우트로 delete 요청을 합니다.
<form action="/campgrounds/<%= camp.id %>/reviews/<%= review.id %>?_method=DELETE"
method="POST">
<div class="text-end mt-2">
<button class="btn btn-danger">Delete</button>
</div>
</form>
app.delete('/campgrounds/:id/reviews/:reviewId', catchAsync(async (req, res) => {
const { id, reviewId } = req.params;
await Campground.findByIdAndUpdate(id, { $pull: { reviews: reviewId } });
await Review.findByIdAndDelete(reviewId);
res.redirect(`/campgrounds/${id}`);
}))
$pull은 배열에 있는 특정 값을 지워줍니다. 캠핑장 리뷰 배열에서 삭제하려는 리뷰의 id를 먼저 삭제하고, 이후에 삭제하려는 리뷰를 review 집합에서 삭제합니다.
캠핑장 삭제하기
캠핑장을 삭제하면 그 캠핑장의 리뷰는 어떻게 될까요. 아무도 찾지 못하게 덩그러니 남아있으면 안되기때문에 미들웨어 post를 사용해줍니다.
캠핑장을 삭제할 때 사용한 몽구스 함수의 이름은 findByIdandDelete이지만 사용되는 미들웨어는 findOneandDelete입니다. 왜냐하면
몽구스 공식 문서에서 그렇다고했기 때문입니다.
CampgroundSchema.post('findOneAndDelete', async function (doc) {
if (doc) {
await Review.deleteMany({
id: { $in: doc.reviews }
})
}
})
이제 캠핑장을 삭제하면 그 캠핑장에 작성되어있던 모든 리뷰도 함께 사라집니다.
'웹 프로그래밍' 카테고리의 다른 글
[JS] 로그인 상태 유지 (2) | 2023.12.01 |
---|---|
[Yelpcamp 프로젝트] flash (0) | 2023.11.29 |
[Express] 세션 (0) | 2023.11.17 |
[JS] 쿠키 (0) | 2023.11.17 |
[Express] 라우터 (0) | 2023.11.17 |