Files
complete-node-bootcamp/4-natours/after-section-13/models/tourModel.js
Jonas Schmedtmann 7f81af0ddf Initial commit 🚀
2019-06-13 15:43:15 +01:00

192 lines
4.4 KiB
JavaScript

const mongoose = require('mongoose');
const slugify = require('slugify');
// const User = require('./userModel');
// const validator = require('validator');
const tourSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, 'A tour must have a name'],
unique: true,
trim: true,
maxlength: [40, 'A tour name must have less or equal then 40 characters'],
minlength: [10, 'A tour name must have more or equal then 10 characters']
// validate: [validator.isAlpha, 'Tour name must only contain characters']
},
slug: String,
duration: {
type: Number,
required: [true, 'A tour must have a duration']
},
maxGroupSize: {
type: Number,
required: [true, 'A tour must have a group size']
},
difficulty: {
type: String,
required: [true, 'A tour must have a difficulty'],
enum: {
values: ['easy', 'medium', 'difficult'],
message: 'Difficulty is either: easy, medium, difficult'
}
},
ratingsAverage: {
type: Number,
default: 4.5,
min: [1, 'Rating must be above 1.0'],
max: [5, 'Rating must be below 5.0'],
set: val => Math.round(val * 10) / 10 // 4.666666, 46.6666, 47, 4.7
},
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 points to current doc on NEW document creation
return val < this.price;
},
message: 'Discount price ({VALUE}) should be below regular price'
}
},
summary: {
type: String,
trim: true,
required: [true, 'A tour must have a description']
},
description: {
type: String,
trim: true
},
imageCover: {
type: String,
required: [true, 'A tour must have a cover image']
},
images: [String],
createdAt: {
type: Date,
default: Date.now(),
select: false
},
startDates: [Date],
secretTour: {
type: Boolean,
default: false
},
startLocation: {
// GeoJSON
type: {
type: String,
default: 'Point',
enum: ['Point']
},
coordinates: [Number],
address: String,
description: String
},
locations: [
{
type: {
type: String,
default: 'Point',
enum: ['Point']
},
coordinates: [Number],
address: 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({ startLocation: '2dsphere' });
tourSchema.virtual('durationWeeks').get(function() {
return this.duration / 7;
});
// Virtual populate
tourSchema.virtual('reviews', {
ref: 'Review',
foreignField: 'tour',
localField: '_id'
});
// DOCUMENT MIDDLEWARE: runs before .save() and .create()
tourSchema.pre('save', function(next) {
this.slug = slugify(this.name, { lower: true });
next();
});
// tourSchema.pre('save', async function(next) {
// const guidesPromises = this.guides.map(async id => await User.findById(id));
// this.guides = await Promise.all(guidesPromises);
// next();
// });
// tourSchema.pre('save', function(next) {
// console.log('Will save document...');
// next();
// });
// tourSchema.post('save', function(doc, next) {
// console.log(doc);
// next();
// });
// QUERY MIDDLEWARE
// tourSchema.pre('find', function(next) {
tourSchema.pre(/^find/, function(next) {
this.find({ secretTour: { $ne: true } });
this.start = Date.now();
next();
});
tourSchema.pre(/^find/, function(next) {
this.populate({
path: 'guides',
select: '-__v -passwordChangedAt'
});
next();
});
tourSchema.post(/^find/, function(docs, next) {
console.log(`Query took ${Date.now() - this.start} milliseconds!`);
next();
});
// AGGREGATION MIDDLEWARE
// tourSchema.pre('aggregate', function(next) {
// this.pipeline().unshift({ $match: { secretTour: { $ne: true } } });
// console.log(this.pipeline());
// next();
// });
const Tour = mongoose.model('Tour', tourSchema);
module.exports = Tour;