tourafrika
tourafrika

Reputation: 15

req.query.countries Returns Empty Array on Filter

Please help. Still relatively new with Nodejs and Angular. I have the below backend code on Nodejs and I am not able to filter the data by country from MongoDB through req.query.countries I can't for the life of me understand what is wrong here. Thanks in advance for any clues, pointers or solution.

My backend code is as follows tourController.js file

exports.getToursByCountry = catchAsync(async (req, res, next) => {
let filter = {};
  if (req.query.countries) {
    console.log(req.query.countries);
    filter = { country: req.query.countries.split(',') };
  }

  console.log(filter);

  const tourList = await Tour.find(filter).populate('country');
  console.log(tourList);

  if (!tourList) {
    next(new AppError('Sorry could not retrieve tours by country', 400));
  }

  res.status(200).json({
    status: 'success',
    results: tourList.length,
    data: {
      data: tourList
    }
  });
});

tourModel.js file

const mongoose = require('mongoose');
const slugify = require('slugify');
// const User = require('./userModel');

const tourSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, 'A tour must have a name'],
      unique: true,
      trim: true,
      maxlength: [
        120,
        'The tour name must have less or equal than 120 characters'
      ],
      minlength: [
        10,
        'The tour name must have more or equal than 10 characters'
      ]
    },
    slug: String,
    country: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Country'
    },
    duration: {
      type: Number,
      required: [true, 'A tour must have a duration'],
      validate: {
        validator: val => {
          //'this' only works for the new posting of tour and not on update
          return val < 22;
        },
        message: 'The duration {VALUE} days should be below the 22 days'
      }
    },
    maxGroupPair: {
      type: Number,
      required: [true, 'A tour must have a maximum group pair size']
    },
    singleSupplement: {
      type: Number,
      required: [true, 'A tour must have a single supplment specified']
    },
    difficulty: {
      type: String,
      required: [true, 'A tour must have a difficulty'],
      enum: {
        values: ['easy', 'medium', 'difficult'],
        message: 'Difficulty is either: easy, medium nor difficult'
      },
      match: [
        new RegExp(/^[a-zA-Z\s]+$/),
        '{VALUE} is not valid. Please use only letters'
      ]
    },
    ratingsAverage: {
      type: Number,
      min: [1, 'Rating must be above 1.0'],
      max: [5, 'Rating must be below 5.0'],
      set: val => Math.round(val * 10) / 10
    },
    ratingsQuantity: {
      type: Number,
      default: 0
    },
    price: {
      type: Number,
      required: [true, 'A tour must have a price']
    },
    priceDiscount: {
      type: Number,
      validate: {
        validator: function(val) {
          //'this' only works for the new posting of tour and not on update
          return val < this.price;
        },
        message: 'Discount price {VALUE} should be below the regular price'
      }
    },
    overview: {
      type: String,
      trim: true,
      required: [true, 'A tour must have an overview']
    },
    imageCover: {
      type: String,
      required: [true, 'A tour must have a cover image']
    },
    images: [String],
    minimumAge: {
      type: String,
      required: [true, 'A tour must have a minimum age'],
      maxlength: [
        10,
        'The minimum age must have less or equal than 10 characters'
      ]
    },
    dateCreated: {
      type: Date,
      default: Date.now
    },
    availabilityDates: [String],
    isFeatured: {
      type: Boolean,
      default: false
    },
    secretTour: {
      type: Boolean,
      default: false
    },
    departureLocation: {
      // GeoJSON
      type: {
        type: String,
        default: 'Point',
        enum: ['Point']
      },
      coordinates: [Number],
      town: String,
      decription1: String,
      decription2: String
    },
    locations: [
      {
        type: {
          type: String,
          default: 'Point',
          enum: ['Point']
        },
        Area: String,
        coordinates: [Number],
        image: String,
        overnight: String,
        Meals: String,
        description: String,
        day: [Number]
      }
    ],
    guides: [
      {
        type: mongoose.Schema.ObjectId,
        ref: 'User'
      }
    ]
  },
  {
    toJSON: { virtuals: true },
    toObject: { virtuals: true }
  }
);

// tourSchema.index({ price: 1 });
tourSchema.index({ price: 1, ratingsAverage: -1 });
tourSchema.index({ slug: 1 });
tourSchema.index({ departureLocation: '2dsphere' });

tourSchema.virtual('id').get(function() {
  return this._id.toHexString();
});

tourSchema.set('toJSON', {
  virtuals: true
});

tourSchema.virtual('durationWeek').get(function() {
  return this.duration / 7;
});

// Virtual populate - this approach does not persist the infomation to the database
tourSchema.virtual('reviews', {
  ref: 'Review',
  foreignField: 'tour',
  localField: '_id'
});

//DOCUMENT MIDDLEWARE: runs before .save() and .create() & not on .insertMany
tourSchema.pre('save', function(next) {
  this.slug = slugify(this.name, { lower: true });
  next();
});


//QUERY MIDDLEWARE
tourSchema.pre(/^find/, function(next) {
  this.find({ secretTour: { $ne: true } });

  this.start = Date.now();
  next();
});

tourSchema.pre(/^find/, function(next) {
  //This will work on find method available in mongoose. 'this' calls populate on the current query
  this.populate({
    path: 'guides',
    select: '-__v -email -phone -street -apartment -suburb -zipCode -code -city'
  });

  this.populate({
    path: 'country',
    select: '-__v'
  });

  next();
});

tourSchema.post(/^find/, function(docs, next) {
  console.log(`Query took ${Date.now() - this.start} milliseconds`);
  // console.log(docs);
  next();
});

//AGGREGATION MIDDLEWARE
tourSchema.pre('aggregate', function(next) {
  if (!(this.pipeline().length > 0 && '$geoNear' in this.pipeline()[0])) {
    this.pipeline().unshift({
      $match: { secretTour: { $ne: true } }
    });
  }

  console.log(this.pipeline());
  next();
});

const Tour = mongoose.model('Tour', tourSchema);

module.exports = Tour;

tourRoutes.js route file

router.route('/').get(tourController.getToursByCountry);

Upvotes: 0

Views: 114

Answers (1)

tourafrika
tourafrika

Reputation: 15

I have resolved this. My seed json data imported into MongoDB under the field of country was a string in my database instead of an ObjectId.

Upvotes: 1

Related Questions