Давайте углубимся в обзор наших буткемпов

давайте сначала начнем, собрав все обзоры и обзоры по буткемпам

создайте models/Review.js

const mongoose = require('mongoose');
const ReviewSchema = new mongoose.Schema({
  title: {
    type: String,
    trim: true,
    required: [true, 'Please add a title for the review'],
    maxlength: 100
  },
  text: {
    type: String,
    required: [true, 'Please add some text']
  },
  rating: {
    type: Number,
    min: 1,
    max: 10,
    required: [true, 'Please add a rating between 1 and 10']
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  bootcamp: {
    type: mongoose.Schema.ObjectId,
    ref: 'Bootcamp',
    required: true
  },
  user: {
    type: mongoose.Schema.ObjectId,
    ref: 'User',
    required: true
  }
});
module.exports = mongoose.model('Review', ReviewSchema);

и controllers/reviews.js

const ErrorResponse = require('../utils/errorResponse');
const asyncHandler = require('../middleware/async');
const Review = require('../models/Review');
const Bootcamp = require('../models/Bootcamp');
// @desc      Get reviews
// @route     GET /api/v1/reviews
// @route     GET /api/v1/bootcamps/:bootcampId/reviews
// @access    Public
exports.getReviews = asyncHandler(async (req, res, next) => {
  if (req.params.bootcampId) {
    const reviews = await Review.find({ bootcamp: req.params.bootcampId });
return res.status(200).json({
      success: true,
      count: reviews.length,
      data: reviews
    });
  } else {
    res.status(200).json(res.advancedResults);
  }
});

и в routes/reviews.js

const express = require('express');
const {
  getReviews
} = require('../controllers/reviews');
const Review = require('../models/Review');
const router = express.Router({ mergeParams: true });
const advancedResults = require('../middleware/advancedResults');
const { protect, authorize } = require('../middleware/auth');
router
  .route('/')
  .get(
    advancedResults(Review, {
      path: 'bootcamp',
      select: 'name description'
    }),
    getReviews
  );
module.exports = router;

и обновите весь routes/bootcamps.js этим

const express = require('express');
const {
  getBootcamps,
  getBootcamp,
  createBootcamp,
  updateBootcamp,
  deleteBootcamp,
  getBootcampsInRadius,
  bootcampPhotoUpload,
} = require('../controllers/bootcamps');
const Bootcamp = require('../models/Bootcamp');
const advancedResults = require('../middleware/advancedResults');
//include other resourse router
const courseRouter = require('./courses');
const reviewRouter = require('./reviews');
const router = express.Router();
const { protect,authorize } = require('../middleware/auth');
router.use('/:bootcampId/courses', courseRouter);
router.use('/:bootcampId/reviews', reviewRouter);
router.route('/radius/:zipcode/:distance').get(getBootcampsInRadius);
router
  .route('/')
  .get(advancedResults(Bootcamp, 'courses'), getBootcamps)
  .post(protect,authorize('publisher','admin'), createBootcamp);
router.route('/:id/photo').put(protect, authorize('publisher','admin'),bootcampPhotoUpload);
router
  .route('/:id')
  .get(getBootcamp)
  .put(protect, authorize('publisher','admin'),updateBootcamp)
  .delete(protect, authorize('publisher','admin'),deleteBootcamp);
module.exports = router;

и включите маршруты в server.js.

получить единый обзор и обновить сеялку

включите это как reviews.json в seeder.js для заполнения базы данных

[
 {
  "_id": "5d7a514b5d2c12c7449be020",
  "title": "Learned a ton!",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "8",
  "bootcamp": "5d713995b721c3bb38c1f5d0",
  "user": "5c8a1d5b0190b214360dc033"
 },
 {
  "_id": "5d7a514b5d2c12c7449be021",
  "title": "Great bootcamp",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "10",
  "bootcamp": "5d713995b721c3bb38c1f5d0",
  "user": "5c8a1d5b0190b214360dc034"
 },
 {
  "_id": "5d7a514b5d2c12c7449be022",
  "title": "Got me a developer job",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "7",
  "bootcamp": "5d713a66ec8f2b88b8f830b8",
  "user": "5c8a1d5b0190b214360dc035"
 },
 {
  "_id": "5d7a514b5d2c12c7449be023",
  "title": "Not that great",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "4",
  "bootcamp": "5d713a66ec8f2b88b8f830b8",
  "user": "5c8a1d5b0190b214360dc036"
 },
 {
  "_id": "5d7a514b5d2c12c7449be024",
  "title": "Great overall experience",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "7",
  "bootcamp": "5d725a037b292f5f8ceff787",
  "user": "5c8a1d5b0190b214360dc037"
 },
 {
  "_id": "5d7a514b5d2c12c7449be025",
  "title": "Not worth the money",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "5",
  "bootcamp": "5d725a037b292f5f8ceff787",
  "user": "5c8a1d5b0190b214360dc038"
 },
 {
  "_id": "5d7a514b5d2c12c7449be026",
  "title": "Best instructors",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "10",
  "bootcamp": "5d725a1b7b292f5f8ceff788",
  "user": "5c8a1d5b0190b214360dc039"
 },
 {
  "_id": "5d7a514b5d2c12c7449be027",
  "title": "Was worth the investment",
  "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec viverra feugiat mauris id viverra. Duis luctus ex sed facilisis ultrices. Curabitur scelerisque bibendum ligula, quis condimentum libero fermentum in. Aenean erat erat, aliquam in purus a, rhoncus hendrerit tellus. Donec accumsan justo in felis consequat sollicitudin. Fusce luctus mattis nunc vitae maximus. Curabitur semper felis eu magna laoreet scelerisque",
  "rating": "7",
  "bootcamp": "5d725a1b7b292f5f8ceff788",
  "user": "5c8a1d5b0190b214360dc040"
 }
]

добавьте этот фрагмент кода в controllers/reviews.js

// @desc      Get single review
// @route     GET /api/v1/reviews/:id
// @access    Public
exports.getReview = asyncHandler(async (req, res, next) => {
    const review = await Review.findById(req.params.id).populate({
      path: 'bootcamp',
      select: 'name description'
    });
  
    if (!review) {
      return next(
        new ErrorResponse(`No review found with the id of ${req.params.id}`, 404)
      );
    }
  
    res.status(200).json({
      success: true,
      data: review
    });
  });

а также включить маршруты в routes/reviews.js.

router
  .route('/:id')
  .get(getReview);

теперь давайте напишем функцию для добавления отзыва

поскольку пользователь может оставить только 1 отзыв на любой буткемп, мы добавляем его в models/Review.js

// Prevent user from submitting more than one review per bootcamp
ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true });

и добавьте этот фрагмент кода в conrollers/reviews.js.

// @desc      Add review
// @route     POST /api/v1/bootcamps/:bootcampId/reviews
// @access    Private
exports.addReview = asyncHandler(async (req, res, next) => {
    req.body.bootcamp = req.params.bootcampId;
    req.body.user = req.user.id;
  
    const bootcamp = await Bootcamp.findById(req.params.bootcampId);
  
    if (!bootcamp) {
      return next(
        new ErrorResponse(
          `No bootcamp with the id of ${req.params.bootcampId}`,
          404
        )
      );
    }
  
    const review = await Review.create(req.body);
  
    res.status(201).json({
      success: true,
      data: review
    });
  });

и обновите маршруты в routes/reviews.js.

router
  .route('/')
  .get(
    advancedResults(Review, {
      path: 'bootcamp',
      select: 'name description'
    }),
    getReviews
    )
    .post(protect, authorize('user', 'admin'), addReview);

теперь нам нужно рассчитать средний рейтинг, данный буткемпу

перейдите в models/Review.js и добавьте этот код над module.exports = ….

// Call getAverageCost after save
ReviewSchema.post('save', async function() {
  await this.constructor.getAverageRating(this.bootcamp);
});
// Call getAverageCost before remove
ReviewSchema.post('remove', async function() {
  await this.constructor.getAverageRating(this.bootcamp);
});

теперь давайте напишем код для обновления и удаления отзывов

перейдите к controllers/reviews.js

// @desc      Update review
// @route     PUT /api/v1/reviews/:id
// @access    Private
exports.updateReview = asyncHandler(async (req, res, next) => {
    let review = await Review.findById(req.params.id);
  
    if (!review) {
      return next(
        new ErrorResponse(`No review with the id of ${req.params.id}`, 404)
      );
    }
  
    // Make sure review belongs to user or user is admin
    if (review.user.toString() !== req.user.id && req.user.role !== 'admin') {
      return next(new ErrorResponse(`Not authorized to update review`, 401));
    }
  
    review = await Review.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true
    });
  
    res.status(200).json({
      success: true,
      data: review
    });
  });
  
  // @desc      Delete review
  // @route     DELETE /api/v1/reviews/:id
  // @access    Private
  exports.deleteReview = asyncHandler(async (req, res, next) => {
    const review = await Review.findById(req.params.id);
  
    if (!review) {
      return next(
        new ErrorResponse(`No review with the id of ${req.params.id}`, 404)
      );
    }
  
    // Make sure review belongs to user or user is admin
    if (review.user.toString() !== req.user.id && req.user.role !== 'admin') {
      return next(new ErrorResponse(`Not authorized to update review`, 401));
    }
  
    await review.remove();
  
    res.status(200).json({
      success: true,
      data: {}
    });
  });

и обновите весь код routes/reviews.js этим

const express = require('express');
const {
  getReviews,
  getReview,
  addReview,
  updateReview,
  deleteReview
} = require('../controllers/reviews');
const Review = require('../models/Review');
const router = express.Router({ mergeParams: true });
const advancedResults = require('../middleware/advancedResults');
const { protect, authorize } = require('../middleware/auth');
router
  .route('/')
  .get(
    advancedResults(Review, {
      path: 'bootcamp',
      select: 'name description'
    }),
    getReviews
    )
    .post(protect, authorize('user', 'admin'), addReview);
router
  .route('/:id')
  .get(getReview)
  .put(protect, authorize('user', 'admin'), updateReview)
  .delete(protect, authorize('user', 'admin'), deleteReview);;
module.exports = router;

формат отправки почтового запроса - переходить по таким маршрутам

{{URL}}/api/v1/bootcamps/5f7cb8d814a9866e474b4940/reviews

и сделайте почтовый запрос в таком формате

{
"title":"great bcdootcamp",
"text":"i learnexcxcd a lot",
"rating":10
}

так что да, это мы сделали crud для обзоров