MongoDB find overlapping date range on sub-documents - node.js

Supposing I have this data:
[{
"name": "New Training1",
"participants": ["5d2eca379b0d361b18d2f3d0", "5d31290c21729014a0bdd0ba"],
"schedule": [{
"start": "2019-10-07T12:00:00.000Z",
"end": "2019-10-07T14:00:00.000Z"
}]
}, {
"name": "New Training2",
"participants": ["5d2eca379b0d361b18d2f3d0"],
"schedule": [{
"start": "2019-10-07T14:00:00.000Z",
"end": "2019-10-07T15:00:00.000Z"
}]
}, {
"name": "New Training3",
"participants": ["5d31290c21729014a0bdd0ba"],
"schedule": [{
"start": "2019-10-07T14:00:00.000Z",
"end": "2019-10-07T16:00:00.000Z"
}]
}]
What I want to do is when I attempt to add the participant with the id of 5d2eca379b0d361b18d2f3d0 to the training New Training3, the app will throw an error indicating that there was a conflict on the schedule and will return the conflicting schedule. Based on the data above the system must return this training as it has a conflicting schedule:
{
"name": "New Training2",
"participants": ["5d2eca379b0d361b18d2f3d0"],
"schedule": [{
"start": "2019-10-07T14:00:00.000Z",
"end": "2019-10-07T15:00:00.000Z"
}
}
This is what I have so far.
Training model:
const mongoose = require('mongoose');
const ScheduleSchema = require('../models/schedule.model').schema;
const TrainingSchema = new mongoose.Schema(
{
name: {
type: String,
unique: true,
required: true
},
participants: [{ type: mongoose.Schema.ObjectId, ref: 'Participant' }],
schedule: [ScheduleSchema]
},
{
versionKey: false,
timestamps: true
}
);
module.exports = mongoose.model('Training', TrainingSchema);
Partcipant model:
const mongoose = require('mongoose');
const ParticipantSchema = new mongoose.Schema(
{
name: {
type: String,
required: true
}
}
);
Schedule model
const mongoose = require('mongoose');
const ScheduleSchema = new mongoose.Schema(
{
start: {
type: Date,
required: true
},
end: {
type: Date,
required: true
}
},
{
versionKey: false,
timestamps: false
}
);
module.exports = mongoose.model('Schedule', ScheduleSchema);
Function to get trainings with overlapping schedules if I add a praticpant using participantId to a training with the given trainingId:
const model = require('../models/training.model');
exports.participantScheduleOverlaps = async (trainingId, participantId) => {
return new Promise((resolve, reject) => {
model.find(
);
});
};
I need help building a MongoDB query to find the overlapping schedule. Thanks.

Try this one.
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const util = require("util")
mongoose.connect("mongodb://localhost/mongoose-doc", { useNewUrlParser: true })
const ScheduleSchema = new Schema(
{
start: {
type: Date,
required: true
},
end: {
type: Date,
required: true
}
},
{
versionKey: false,
timestamps: false
}
);
const TrainingSchema = new Schema(
{
name: {
type: String,
unique: true,
required: true
},
participants: [{ type: mongoose.Schema.ObjectId, ref: 'Participant' }],
schedule: [ScheduleSchema]
},
{
versionKey: false,
timestamps: true
}
);
const TrainingModel = mongoose.model("Training", TrainingSchema);
const ParticipantSchema = new Schema(
{
name: {
type: String,
required: true
}
}
);
const ParticipantModel = mongoose.model("Participant", ParticipantSchema)
async function participantScheduleOverlaps(trainingId, participantId) {
try {
const trainingWeFound = await TrainingModel.aggregate([
{
$match:{
_id: mongoose.Types.ObjectId(trainingId)
}
},
{
$unwind: "$schedule"
}
]).exec()
const otherTrainingModules = await TrainingModel.aggregate(
[
{
$match:{
$and:[
{
_id:{
$ne: mongoose.Types.ObjectId(trainingId)
}
},
{
participants: {
$in: [mongoose.Types.ObjectId(participantId)]
}
}
]
}
},
{
$unwind: "$schedule"
}
]
).exec()
const overlapping = otherTrainingModules.filter((otherelem) => {
return trainingWeFound.filter(foundelem => {
(
Date.parse(otherelem.start) < Date.parse(foundelem.start)
&&
Date.parse(foundelem.start) < Date.parse(otherelem.end)
) || (
Date.parse(otherelem.start) < Date.parse(foundelem.end)
&&
Date.parse(foundelem.end) < Date.parse(otherelem.end)
)
})
})
console.log("overlapping", overlapping)
} catch(error){
console.log(error)
}
}
participantScheduleOverlaps("5d395604eb41824b5feb9c84", "5d31290c21729014a0bdd0ba")

This is my solution for anyone who might have the same or related problem:
exports.participantScheduleOverlaps = async (trainingId, participantId) => {
return new Promise((resolve, reject) => {
model.findById(trainingId, (error, item) => {
const result = {
overlap: false,
ranges: []
};
if (error) {
reject(utils.buildErrObject(422, error.message));
}
if (item) {
for (const schedule of item.schedule) {
model.find(
{
_id: { $ne: trainingId },
participants: { $eq: participantId },
'schedule.end': { $gt: schedule.start },
'schedule.start': { $lt: schedule.end }
},
(err, items) => {
if (err) {
reject(utils.buildErrObject(422, err.message));
}
if (items.length > 0) {
result.overlap = true;
result.ranges.push(
...items.map(data => {
return data.schedule;
})
);
}
resolve(result);
}
);
}
} else {
resolve(result);
}
});
});
};

Related

Find if User Exist in array mongodb and Nodejs

I have this opportunity model that has this field likes, which is an array of users. How do I check first if the user exist already in that array and if they do I pull them and if they don't I push them back I am building a like creteria for posts
Here is my opportunity model
const mongoose = require("mongoose");
const OpportunityModel = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
content: {
type: String,
required: true,
trim: true,
},
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
image: [
{
type: Object,
},
],
share_with_thoughts: {
type: mongoose.Schema.Types.ObjectId,
ref: "Shares",
},
comments: {
type: mongoose.Schema.Types.ObjectId,
ref: "Comment",
},
is_opportunity_applied: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
});
const Opportunity = mongoose.model("Opportunity", OpportunityModel);
module.exports = Opportunity;
What I tried doing but did not work
const likeOrUnlikeOpportunity = expressAsyncHandler(async (req, res) => {
let userId = req.user._id;
let opportunityId = req.params.opportunityId;
let isUserExist = await Opportunity.find({
$and: [{ _id: opportunityId }, { likes: { $elemMatch: { $eq: userId } } }],
}).populate("user", "user_id user_name");
if (isUserExist.length > 0) {
const unliked = await Opportunity.findByIdAndUpdate(
opportunityId,
{
$pull: { users: userId },
},
{ new: true }
).populate("user", "user_id user_name");
if (!unliked) {
res.status(500).send({ ErrMessaage: "an error occured" });
} else {
res.status(200).json(unliked);
}
} else {
const added = await Opportunity.findByIdAndUpdate(
opportunityId,
{
$push: { users: userId },
},
{ new: true }
).populate("user", "user_id user_name");
if (!added) {
res.status(500).send({ ErrMessaage: "an error occured" });
} else {
res.status(200).json(added);
}
}
});
In this case, the action should be dynamic [like | unlike] function, You don't have to chain the function since the like and unlike function/api can't be called at the same time.
Make Your Query Conditional;
Just make sure you have a way to identify between like and unlike.
const {like, postId} = req.body;
let query = {[`${'$' + (like ? 'push' : 'pull')}`]: {likes: userId}};
//assumes that you have the post id
// you can decide not to wait for it to update, just to be sure it did update
await post.findByIdAndUpdate(postId, query).exec()
I hope this helps.

Nodejs express populate sub array data

I have relative field and in relative I have subRelatives and it continues like nested array. Mean in subRelatives I have subRelatives and some time its 10 times continues process.
My code
router.get(`/userTree/:id`, async (req, res) => {
const userTrees = await Tree.find({createdBy: req.params.id})
.populate(
["createdBy",
{
path: "relatives",
populate: {
path: "subRelatives",
populate: {
path: "subRelatives",
populate: "subRelatives",
},
},
}
]);
if (!userTrees) {
res.status(500).json({success: false});
}
res.send({success: true, data: userTrees});
});
I have added populate but it populate first 2 sub relative and then shows MongooseIds only without populating. So I need to added manually some more populate methods so it will run but its crashing because of lot of data now.
and data look like this.
{
"success": true,
"data": {
"_id": "62dad5c6aff2337dc84d9b40",
"treeName": "test1",
"createdBy": {
"_id": "62d8619cebd6543477c5b7d8",
"userName": "test1",
"userEmail": "test1#gmail.com",
"userFamilyTrees": [
"62d8c713547ba80854d89d59"
]
},
"relatives": [
{
"_id": "62dad5c7aff2337dc84d9b44",
"firstName": "tesads",
"subRelatives": [
{
"_id": "62db1cf186b7012ed9937517",
"firstName": "asdasd",
"subRelatives": []
},
{
"_id": "62db1d0d86b7012ed9937522",
"firstName": "asd",
"subRelatives": []
},
{
"_id": "62dc24c15e6f5ea436cce14b",
"firstName": "julia",
"subRelatives": [
{
"_id": "62dc24c15e6f5ea436cce14b",
"firstName": "julia",
"subRelatives": [
"62dc253bd2119bea52f4f9af"
]
}
]
},
{
"_id": "62dc24fcd2119bea52f4f99d",
"firstName": "julia",
"subRelatives": []
}
]
}
]
}
}
This is my Tree Schema
const mongoose = require('mongoose')
const treeSchema = new mongoose.Schema({
treeName: {
type: String,
required: true
}, image: {
type: String,
default: ''
},
treePrivacy: {
type: Boolean,
required: true
},
treeNote: {
type: String,
default: ""
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true,
},
createDate: {
type: Date,
default: Date.now,
},
relatives: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'relatives',
},],
usersInTree: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
},],
media: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'media',
},]
});
treeSchema.virtual('treeID').get(function () {
return this._id.toHexString();
});
treeSchema.set('toJSON', {
virtuals: true
})
exports.Tree = mongoose.model('trees', treeSchema)
exports.treeSchema = treeSchema;
This is relative Schema
const mongoose = require('mongoose')
const relativeSchema = new mongoose.Schema({
firstName: {
type: String,
},
lastName: {
type: String,
}, image: {
type: String,
},
relativeEmail: {
type: String,
},
relativeType: {
type: Number,
},
// relative grandfather0, father1, mother2, wife3, sister4, brother5, child6
treeID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true,
},
subRelatives: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'relatives',
}],
parentRelative: {
type: mongoose.Schema.Types.ObjectId,
ref: 'relatives',
},
userID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'relatives',
required: false
}
});
relativeSchema.virtual('relativeId').get(function () {
return this._id.toHexString();
});
relativeSchema.set('toJSON', {
virtuals: true
})
exports.Relatives = mongoose.model('relatives', relativeSchema)
exports.relativeSchema = relativeSchema;
This is post api for tree
router.post('/createTree', uploadOptions.single('image'), async (req, res) => {
const file = req.file;
if (!file) return res.status(400).send('No image in the request');
const fileName = file.filename;
const basePath = `${req.protocol}://${req.get('host')}/public/uploads/`;
var userintree = [];
const userExist = await User.findById(req.body.createdBy);
if (!userExist) return res.status(400).send({ success: false, message: 'UserID is not correct' })
userintree.push(req.body.createdBy);
let createtree = new Tree({
treeNote: req.body.treeNote,
treeName: req.body.treeName,
treePrivacy: req.body.treePrivacy,
createdBy: req.body.createdBy,
image: `${basePath}${fileName}`,
usersInTree: userintree
});
createtree = await createtree.save();
if (!createtree) return res.status(400).send({ success: false, message: 'Issue to create a tree' })
userExist.userFamilyTrees.push(createtree._id.toHexString())
const user = await User.findByIdAndUpdate(
req.body.createdBy,
{
userFamilyTrees: userExist.userFamilyTrees,
$push: {
usersInTree: req.body.createdBy
}
},
{ new: true }
)
if (user) res.status(200).send({ success: true, message: 'Tree Created.!,', data: createtree })
});
and post API for relative
router.post('/addRelative', uploadOptions.single('image'), async (req, res) => {
const file = req.file;
if (!file) return res.status(400).send('No image in the request');
const fileName = file.filename;
const basePath = `${req.protocol}://${req.get('host')}/public/uploads/`;
console.log(fileName); console.log(basePath);
console.log(req.body);
let createRelative = new Relatives({
firstName: req.body.firstName,
lastName: req.body.lastName,
relativeEmail: req.body.relativeEmail,
relativeType: req.body.relativeType,
treeID: req.body.treeID,
subRelatives: req.body.subRelatives,
parentRelative: req.body.parentRelative, image: `${basePath}${fileName}`,
});
const treeExist = await Tree.findById(req.body.treeID);
if (!treeExist) return res.status(400).send({ success: false, message: 'TreeID is not correct' })
createRelative = await createRelative.save();
if (!createRelative)
return res.status(400).send({ success: false, message: 'Something Went Wrong.!,' })
treeExist.relatives.push(createRelative._id.toHexString())
const tree = await Tree.findByIdAndUpdate(
req.body.treeID,
{
relatives: treeExist.relatives
},
{ new: true }
)
if (req.body.parentRelative) {
console.log(req.body.parentRelative)
const parent = await Relatives.findById(
req.body.parentRelative
);
// console.log(parent)
// console.log(parent)
parent.subRelatives.push(createRelative)
const user = await Relatives.findByIdAndUpdate(
req.body.parentRelative,
{
subRelatives: parent.subRelatives,
},
{ new: true }
)
// console.log(user)
if (!user) return res.status(400).send({ success: false, message: 'Something Went Wrong.!,' })
// res.send(ser);
}
res.status(200).send({ success: true, message: 'Relative Created Created.!,', data: createRelative })
});
Populate data like this
.populate(
["createdBy",
{
path: "relatives",
populate: {
path: "subRelatives",
model: "SubRelative",
},
}
]);
I've assumed that the model name refaring to subRelative is SubRelative
mongoose does support nested population, it's just that you need to specify the model field as the treeSchema does not have access to all other schema during run time. It looks like this:
const userTrees = await Tree.find({createdBy: req.params.id})
.populate(
[
"createdBy",
{
path: "relatives",
populate: {
path: "subRelatives",
model: 'relatives',
},
}
]);
mongoose does some optimizations to the query, but considering you know the exact structure you can reduce db calls and improve performance if you do this yourself instead of using populate.

How to add object into an array which is a value of a key within an object?

I have a user model like this:
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
courses: {
type: Object,
required: true
}
})
And a course model like this:
const courseSchema = new mongoose.Schema({
course_code: {
type: String,
required: true
},
course_title: {
type: String,
required: true
}
})
app.js file is here:
app.post('/enroll/:id', async function (req, res) {
const courseWithID = await Course.findById(req.params.id)
const course = {"course_code": courseWithID.course_code, "course_title": courseWithID.course_title}
const userID = req.user.id
await User.findByIdAndUpdate(userID, {"courses": course}, {new: true, runValidators: true, useFindAndModify: false})
res.redirect('/dashboard')
})
After first execution of app.js, I get this result:
{
"name": "Person",
"courses": {
"course_code": "123",
"course_title": "ABC"
}
}
After second execution of app.js, I get this result:
{
"name": "Person",
"courses": {
"course_code": "456",
"course_title": "DEF"
}
}
But after second execution of app.js, I want this result:
{
"name": "Person",
"courses": [
{
"course_code": "123",
"course_title": "ABC"
},
{
"course_code": "456",
"course_title": "DEF"
}
]
}
How Can I do that?
Quick fixes,
change courses type Object to an Array
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
courses: {
type: Array,
required: true
}
})
add $push before courses because we need to push object in array
const courseWithID = await Course.findById(req.params.id);
const course = {
"course_code": courseWithID.course_code,
"course_title": courseWithID.course_title
};
const userID = req.user.id;
await User.findByIdAndUpdate(userID,
{
"$push": { "courses": course }
},
{new: true, runValidators: true, useFindAndModify: false}
)

Update name in nested object array

I'm new to MongoDB using angular as frontend. I'm trying to update a name in nested object array.
My Schema is as follows:
const mongoose = require("mongoose");
const projectDragDropSchema = mongoose.Schema({
_idProject: mongoose.Schema.Types.ObjectId,
projectTitle: { type: String, required: true },
boards: [
{
_idBoard: mongoose.Schema.Types.ObjectId,
boardTitle: { type: String, required: false },
cards: [
{
type: new mongoose.Schema(
{
cardId: { type: mongoose.Schema.Types.ObjectId, required: true },
cardTitle: { type: String, required: false },
}
// { minimize: false }
),
required: false,
},
],
required: false,
},
],
});
module.exports = mongoose.model("ProjectDragDrop", projectDragDropSchema);
I'm trying to update the cardTitle.
I have written the multiple updates to it, but unable to find the correct one.
The Router:
router.patch(
"/updateProjectBoardCardName/:_idProject/:_id",
projectBoardsCards.updateCardName
);
The code:
exports.updateCardName = (req, res) => {
const idProject = req.params._idProject;
const boardID = req.params._id;
projectDragDropSchema
.update(
{ _idProject: idProject, "boards._id": boardID },
{ cards: { $elemMatch: { _id: req.body.params } } },
{ $set: { "cards.$.cardTitle": req.body.params } }
)
.exec()
.then((result) => {
console.log(result);
res.status(200).json(result);
})
.catch((err) => {
console.log(err);
res.status(500).json({
error: err,
});
});
};
Thanks in advance.

How to do a elasticsearch query search from NodeJS using wildcard and space

I have set of products indexed in elasticsearch. I`m searching for "title" on my schema.
When I search "fre" or "fresh" I see a result.
But when I search for "small fresh" I don't see any result.
Is it possible to use wildcard with spaces ?
I added es_indexed: "not_analyzed" but no luck.
This is my Product Schema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
var mongoosastic = require("mongoosastic");
const deepPopulate = require("mongoose-deep-populate")(mongoose);
var Owner = require("./user");
var Category = require("./category");
var Reviews = require("./review");
const ProductSchema = new Schema(
{
category: {
type: Schema.Types.ObjectId,
ref: "Category",
es_indexed: true,
es_type: "nested",
es_include_in_parent: true
},
owner: {
type: Schema.Types.ObjectId,
ref: "User",
es_indexed: true,
es_type: "nested",
es_include_in_parent: true
},
reviews: [
{
type: Schema.Types.ObjectId,
ref: "Review",
es_indexed: true,
es_type: "nested",
es_include_in_parent: true
}
],
image: { type: String, es_indexed: true },
title: {
type: String,
es_indexed: "not_analyzed"
},
description: { type: String, es_indexed: true },
price: { type: Number, es_indexed: true },
crated: { type: Date, default: Date.now, es_indexed: true }
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
}
);
ProductSchema.virtual("averageRating").get(function() {
var rating = 0;
if (this.reviews.length == 0) {
rating = 0;
} else {
this.reviews.map(review => {
rating += review.rating;
});
rating = rating / this.reviews.length;
}
return rating;
});
ProductSchema.plugin(deepPopulate);
ProductSchema.plugin(mongoosastic, {
populate: [{ path: "category" }, { path: "owner" }, { path: "reviews" }]
});
let Model = mongoose.model("Product", ProductSchema);
Model.createMapping(function(err, mapping) {
if (err) {
console.log("error creating mapping (you can safely ignore this)");
console.log(err);
} else {
console.log("mapping created!");
console.log(mapping);
}
});
var stream = Model.synchronize();
var count = 0;
stream.on("data", (err, doc) => {
console.log(doc);
count++;
});
stream.on("close", () => console.log("indexed " + count + " documents!"));
stream.on("error", err => console.log(err));
Model.SyncToAlgolia();
Model.SetAlgoliaSettings({
searchableAttributes: ["title"]
});
module.exports = Model;
This is my Search function
async function search(frompage) {
let fullString = "*" + "small fresh" + "*";
let startsFrom = frompage * 10;
console.log(fullString);
const response = await esClient.search({
index: "products",
type: "product",
from: startsFrom,
body: {
query: {
wildcard: {
title: fullString
}
}
}
});
return response;
}

Resources