Improving Mongoose Schema for Course Subscription App - node.js

I am new to mongodb, and playing around with a self-project, where user can subscribe to 3-4 different courses that are predefined. Each course is 1 hour long course everyday, and students can subscribe for either 15, 30 or more days.
App will store information of the students, the course they subscribed for (how many) days and days they were present for the course.
This is my Mongoose Schema.
var mongoose = require('mongoose');
var schemaOptions = {
timestamps: true,
toJSON: {
virtuals: true
}
};
var courseSchema = new mongoose.Schema({
name: String
});
var studentSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true},
phone: String,
gender: String,
age: String,
city: String,
street: String,
picture: String,
course: [courseSchema],
subscriptionDays: Number,
daysPresent: [Date]
}, schemaOptions);
module.exports = mongoose.model('Student', studentSchema);
Here, course is any of 3-4 courses, one student can subscribe one or more course at same time. subscriptionDays is the number of days they subscribe to, and daysPresent are the days they took the course.
I am not sure if this is the right schema for my project, so far I was able to do this much.
Confusions with the schema are:
When student who is subscribed to two different courses arrives to the
institute, but takes only one class (course), then I do not think this
schema supports the case, for this I thought to modify courseSchema like this,
var courseSchema = new mongoose.Schema({
name: String,
subsriptionDays: Number,
daysPresent: [Date]
});
But, after doing this I am still confused to make changes on the data, like Date has to be inserted into the documents every time student attends for the course.
Second confusion is how will I update the data inside document every day, only the data that has to be inserted on daily basis is the Date inside days.
Can I get some guidance and suggestion from Mongo Experts? TIA

I think that you are basically on the right track with your second thoughts on extending the design. I would really only expand on that by also including a "reference" to the "Course" itself as opposed to just the information embedded on the schema.
As your usage case questions, then they are probably best addressed with a working example:
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.Types.ObjectId;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/school');
// Course model
const courseSchema = new Schema({
name: String,
info: String
});
const Course = mongoose.model('Course', courseSchema);
// Student Model
const studentCourseSchema = new Schema({
_id: { type: ObjectId, ref: 'Course' },
name: String,
subscriptionDays: Number,
daysPresent: [Date]
});
const studentSchema = new Schema({
name: String,
email: String,
courses: [studentCourseSchema]
});
studentSchema.index({ "email": 1 },{ "unique": true, "background": false });
const Student = mongoose.model('Student', studentSchema);
function logOutput(content) {
console.log( JSON.stringify( content, undefined, 2 ) )
}
async.series(
[
// Clear collections
(callback) =>
async.each(mongoose.models,
(model,callback) => model.remove({},callback),callback),
// Set up data
(callback) =>
async.parallel(
[
(callback) => Course.insertMany(
[
{ "name": "Course 1", "info": "blah blah blah" },
{ "name": "Course 2", "info": "fubble rumble" }
],
callback),
(callback) => Student.insertMany(
[
{ "name": "Bill", "email": "bill#example.com" },
{ "name": "Ted", "email": "ted#example.com" }
],
callback)
],
callback
),
// Give bill both courses
(callback) => {
async.waterfall(
[
(callback) => Course.find().lean().exec(callback),
(courses,callback) => {
courses = courses.map(
course => Object.assign(course,{ subscriptionDays: 5 }));
let ids = courses.map( c => c._id );
Student.findOneAndUpdate(
{ "email": "bill#example.com", "courses._id": { "$nin": ids } },
{ "$push": {
"courses": {
"$each": courses
}
}},
{ "new": true },
(err, student) => {
logOutput(student);
callback(err);
}
)
}
],
callback
)
},
// Attend one of bill's courses
(callback) => Student.findOneAndUpdate(
{ "email": "bill#example.com", "courses.name": 'Course 2' },
{ "$push": { "courses.$.daysPresent": new Date() } },
{ "new": true },
(err, student) => {
logOutput(student);
callback(err);
}
),
// Get Students .populate()
(callback) => Student.find().populate('courses._id')
.exec((err,students) => {
logOutput(students);
callback(err);
}
)
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
)
So that should give you a sample of how the operations you ask about actually work.
Add a course to the student Shows addition of a couple of courses where I think you would ideally use $push functionality of MongoDB. To ensure that you are not adding courses that are already there the "query" expression actually excludes selection if they are already present in the courses array. In the example a "list" is passed, so we use $nin but with a single item you would simply use $ne:
{ "email": "bill#example.com", "courses._id": { "$nin": ids } },
{ "$push": { "courses": { "$each": courses } } },
Add an attended date This actually demonstrates a case where you would want to "positionally match" the item within "courses" in order to know which one to update. This is done by providing much like before a condition to "match" as opposed to "exclude" the specific array element. Then in the actual "update" part, we apply the same $push operator so we can append to the "daysPresent"array, but also using the positional $ operator to point to the correct array index position which corresponds to the match condition:
{ "email": "bill#example.com", "courses.name": 'Course 2' },
{ "$push": { "courses.$.daysPresent": new Date() } },
As a bonus there are a few more operations in there showing the relational nature between keeping a list of "Courses" in their own collection with additional information that you probably do not want to embed on each student.
The last operation in the sample actually performs a .populate() to actually pull in this information from the other collection for display.
The whole example has debugging turned on with mongoose.set('debug',true); so you can see what the actual calls to MongoDB are really doing for each operation.
Also get acquainted with the .findOneAndUpdate() method used here, as well as the various "update operators" from the core MongoDB documentation.
Sample output
Mongoose: courses.remove({}, {})
Mongoose: students.remove({}, {})
Mongoose: students.ensureIndex({ email: 1 }, { unique: true, background: false })
(node:10544) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
Mongoose: courses.insertMany([ { __v: 0, name: 'Course 1', info: 'blah blah blah', _id: 5944d5bc32c6ae2930174289 }, { __v: 0, name: 'Course 2', info: 'fubble rumble', _id: 5944d5bc32c6ae293017428a } ], null)
Mongoose: students.insertMany([ { __v: 0, name: 'Bill', email: 'bill#example.com', _id: 5944d5bc32c6ae293017428b, courses: [] }, { __v: 0, name: 'Ted', email: 'ted#example.com', _id: 5944d5bc32c6ae293017428c, courses: [] } ], null)
Mongoose: courses.find({}, { fields: {} })
Mongoose: students.findAndModify({ 'courses._id': { '$nin': [ ObjectId("5944d5bc32c6ae2930174289"), ObjectId("5944d5bc32c6ae293017428a") ] }, email: 'bill#example.com' }, [], { '$push': { courses: { '$each': [ { daysPresent: [], _id: ObjectId("5944d5bc32c6ae2930174289"), name: 'Course 1', subscriptionDays: 5 }, { daysPresent: [], _id: ObjectId("5944d5bc32c6ae293017428a"), name: 'Course 2', subscriptionDays: 5 } ] } } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "5944d5bc32c6ae293017428b",
"__v": 0,
"name": "Bill",
"email": "bill#example.com",
"courses": [
{
"subscriptionDays": 5,
"name": "Course 1",
"_id": "5944d5bc32c6ae2930174289",
"daysPresent": []
},
{
"subscriptionDays": 5,
"name": "Course 2",
"_id": "5944d5bc32c6ae293017428a",
"daysPresent": []
}
]
}
Mongoose: students.findAndModify({ 'courses.name': 'Course 2', email: 'bill#example.com' }, [], { '$push': { 'courses.$.daysPresent': new Date("Sat, 17 Jun 2017 07:09:48 GMT") } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "5944d5bc32c6ae293017428b",
"__v": 0,
"name": "Bill",
"email": "bill#example.com",
"courses": [
{
"subscriptionDays": 5,
"name": "Course 1",
"_id": "5944d5bc32c6ae2930174289",
"daysPresent": []
},
{
"subscriptionDays": 5,
"name": "Course 2",
"_id": "5944d5bc32c6ae293017428a",
"daysPresent": [
"2017-06-17T07:09:48.662Z"
]
}
]
}
Mongoose: students.find({}, { fields: {} })
Mongoose: courses.find({ _id: { '$in': [ ObjectId("5944d5bc32c6ae2930174289"), ObjectId("5944d5bc32c6ae293017428a") ] } }, { fields: {} })
[
{
"_id": "5944d5bc32c6ae293017428b",
"__v": 0,
"name": "Bill",
"email": "bill#example.com",
"courses": [
{
"subscriptionDays": 5,
"name": "Course 1",
"_id": {
"_id": "5944d5bc32c6ae2930174289",
"__v": 0,
"name": "Course 1",
"info": "blah blah blah"
},
"daysPresent": []
},
{
"subscriptionDays": 5,
"name": "Course 2",
"_id": {
"_id": "5944d5bc32c6ae293017428a",
"__v": 0,
"name": "Course 2",
"info": "fubble rumble"
},
"daysPresent": [
"2017-06-17T07:09:48.662Z"
]
}
]
},
{
"_id": "5944d5bc32c6ae293017428c",
"__v": 0,
"name": "Ted",
"email": "ted#example.com",
"courses": []
}
]

Schema you can define like:-
var mongoose = require('mongoose');
var courseSchema = new mongoose.Schema({
name: String
});
var studentSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true},
phone: String,
gender: String,
age: String,
city: String,
street: String,
picture: String,
courses: [{
course:{type:mongoose.Schema.Types.ObjectId,ref:'courseSchema'},
isAttending:{type:Boolean ,default:false}
}],
subscriptionDays: Number,
daysPresent: [Date]
}, schemaOptions);
module.exports = mongoose.model('Student', studentSchema);
isAttending will solve your problem if studen subscribe 3 courses and going to particular one course then isAttending will be true otherwise false.
You Can use Cron npm module which will run a function on what time will you set and its make your life easy.
Thanks

Related

Mongoose return unique documents with count while using populate

I have a collection that will store multiple copies of the same data
Is there a way in mongoose to return all the unique records based on a certain field, and additionally provide the count field with each object, while using populate to get the full document?
A simple example is something like this:
Lets say the data in my collection looks like this:
[
{
person: ObjectId("123"),
data: 'this will be random data',
_id: ObjectId("xxx")
},
{
person: ObjectId("456"),
data: 'this will be random data',
_id: ObjectId("xxx")
},
{
person: ObjectId("123"),
data: 'this will be random data',
_id: ObjectId("xxx")
}
]
Since I dont want the data field, because this data is random, and I only want a count of the unique documents, my query looks like this:
Model.find().select({'person':1})
Now I only have the person field and _id.
Now I want to populate the data, so my query looks like this:
Model.find().select({'person':1}).populate('person')
Then I get data like this:
[
{
name: 'Jim',
salary: '200'
},
{
name: 'Mike',
salary: '150'
},
{
name: 'Jim',
salary: '200'
},
]
Now what I finally want to get is something like this:
[
{
name: 'Jim',
salary: '200',
count: 2
},
{
name: 'Mike',
salary: '150',
count: 1
},
]
All the random fields have been removed, and I only get a single, populated document with a count for each unique type. in this case, the name field is used as the distinct field.
So far I could not yet get this to work while using populate. Is this possible to achieve with mongoose, or will it require some manual computation?
You might want to use a virtual populate:
Person model:
const personSchema = new mongoose.Schema(
{
name: String,
},
{
// this is needed to display the virtual in json format, like res.json()
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
personSchema.virtual("count", {
ref: "Task",
localField: "_id",
foreignField: "person",
count: true,
});
Task model:
const taskSchema = mongoose.Schema({
person: { type: mongoose.Schema.Types.ObjectId, ref: "Person" },
data: String,
});
instead of searching by task model, you can search by people model
const persons = await personModel.find().select("-_id").populate("count");
Person collection:
{
"_id": "639222fa3f87b88812e95d1d",
"name": "jonh",
"__v": 0,
},
{
"_id": "639222fffcd0793b0580302e",
"name": "joseph",
"__v": 0,
}
Tasks collection:
{
"_id": "6392231815cd8663c5671edf",
"person": "639222fffcd0793b0580302e",
"data": "some data",
},
{
"_id": "6392231a15cd8663c5671ee1",
"person": "639222fffcd0793b0580302e",
"data": "some data",
},
{
"_id": "639223337409d3e1a1f741ac",
"person": "639222fa3f87b88812e95d1d",
"data": "some data",
}
and the result of the query:
[
{
"_id": "639222fa3f87b88812e95d1d",
"name": "jonh",
"count": 1,
"id": "639222fa3f87b88812e95d1d"
},
{
"_id": "639222fffcd0793b0580302e",
"name": "joseph",
"count": 2,
"id": "639222fffcd0793b0580302e"
}
]
"id" field is added for toJSON: { virtuals: true }, you can ignore it.
I hope it helps you, here the docs: enter link description here

Fetch data by matching multiple queries MongoDB

I want to get the data only when all the query matches.
here is the query to get the data:
exports.getParkingListByCriteria = async (req, res) => {
const cityQuery = req.body.city;
const stateQuery = req.body.state;
const countryQuery = req.body.country;
try {
const filter = await Parking.find({
$and: [
{
"location.city": { $regex: new RegExp(cityQuery, ($options = "i")), //I want to apply date filter here },
"location.state": {
$regex: new RegExp(stateQuery, ($options = "i")),
},
"location.country": {
$regex: new RegExp(countryQuery, ($options = "i")),
},
},
],
});
res.status(200).send(filter);
} catch (error) {
return res.status(500).json({ error: error.message });
}
};
Date is stored inside of the parking table where it shows the availability of the parking details for that date and time
availability: [
{
date: {
type: Date,
},
day: {
type: String,
},
startTime: {
type: Date,
},
endTime: {
type: Date,
},
},
],
collection from the database:
{"_id": {"$oid": "62cc46e920782c4be0673d50"},
"merchantId": {"$oid": 62c950ebc96c2b690028be8b"},
"contactInfo": {"name": "Ronda Green", "phoneNumber": 9104933588},
"about": "Laborum non minim ad",
"location":
{"address": "349 scott avenue",
"city": "edmund",
"state": "louisiana",
"zipCode": 49755,
"country": "australia"},
"price": 18,
"parkingType": "parkingLot",
"parkingInfo": [{"parkingName": "College Place","_id":"$oid":"62cc46e920782c4be0673d51"},"default": []}],
"totalSpots": [168],
"parkingSpotType": ["Motorbike","Large"],
"coordinates":
{"lng": 1.522645,
"lat": 125.939061},
"status": "active",
"isFeePaid": false,
**"availability": [
date: "2022-07-10"
startTime: "7:00"
endTime:"22:00"
_id: 62cd048559e9a786ff1df9d7],**
"specialEvents": [],
"__v": 0}
I want to show data only when both city criteria and date criteria matches.
The question is not very clear. If you want to get the totalSpots for each matching document, you can use a simple find with a projection:
db.collection.find(
{
"location.city": {$regex: "edmund", $options: "i"},
"availability.date": "2022-07-10"
},
{
totalSpots: 1
})
See how it works on the playground example
If you want something else, please provide the requested results

Insert items into mongo array via mongoose

I have a mongo collection like the code below:
const ExerciseSchema = new Schema({
name: { type: String, required: true },
exercise: [
{
exerciseId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: Exercise,
},
period: { type: String, enum: ["day", "night"], required: true },
},
],
timestamp: { type: Date, default: Date.now() },
});
When I try to insert multiple exercises at the same time, considering that my exercises are an array, the mongo saves just the first register. For instance, I try to insert:
{
"name": "Exercise 1",
"exercise": [
{
"exerciseId": "1",
"period": "night"
},
{
"exerciseId": "1",
"period": "day"
}
]
}
And, after saving it, the get method returns me:
[
{
"timestamp": "2021-11-30T14:18:42.455Z",
"_id": "1",
"name": "Exercise 1",
"exercise": [
{
"exerciseId": "1",
"period": "night",
}
],
"__v": 0
}
]
That is, the mongoose saves just the first register in my array. Does anyone know why it is happening?
Hear it my create method from the controller:
exports.create = (req, res) => {
const {
name,
exercise: [{ exerciseId, period }],
} = req.body;
const newExercise = new Exercise({
name,
exercise: [{ exerciseId, period }],
});
newExercise.save((err, data) => {
if (err) {
res.status(500).send({ message: err });
return;
}
res
.status(200)
.send({
message: "Success",
});
});
};
Obs: I'm referring to my ExerciseSchema from the controller by "new Exercise(...)"
use it to update arrays:
parent ---> a mongooose doc
parent.child.push({new child object});
parent.markModified('child');
parent.save();
in your case child is exercise array and in parent.markModified('child'); you have to put parent.markModified('exercise');

Get the populated data associated with ref in mongoose

I have two schemas: 'Leads' and 'LeadsCategory'.
Leads Schema:
const id = mongoose.Schema.Types.ObjectId;
const leadsSchema = mongoose.Schema(
{
_id: id,
userId: { type: id, ref: "User", required: true },
leadName: String,
leads: [
{
_id: id,
name: String,
mobile: Number,
address: String,
education: {
school: String,
graduation: String
},
leadType: { type: id, ref: "LeadsCategory", required: true }
}
]
},
{
timestamps: true
}
);
module.exports = mongoose.model("lead", leadsSchema);
Leads Category Schema:
const id = mongoose.Schema.Types.ObjectId;
const leadsCategorySchema = mongoose.Schema({
_id: id,
name: {
type: String,
required: true
},
leadsData: [{ type: id, ref: 'lead' }]
},
{ timestamps: true }
);
module.exports = mongoose.model("LeadsCategory", leadsCategorySchema);
I'm referencing the leadsCategory as soon as new lead is created and it does populate my leadsCategory into the Leads controller.
So my final data inside 'Leads collection' looks like this:
[
{
"_id": "5e8832dde5d8273824d86502",
"leadName": "Freshers",
"leads": [
{
"education": {
"school": "LPS",
"graduation": "some school"
},
"location": {
"state": "delhi",
"country": "india"
},
"name": "Joey",
"mobile": 1524524678,
"_id": "5e8832dde5d8273824d86500",
"leadType": {
"_id": "5e88285f5dda5321bcc045a6",
"name": "all"
}
},
{
"education": {
"school": "DAV",
"graduation": "some school"
},
"location": {
"state": "delhi",
"country": "india"
},
"name": "Ben",
"mobile": 1524524678,
"_id": "5e8832dde5d8273824d86501",
"leadType": {
"_id": "5e88285f5dda5321bcc045a6",
"name": "all"
}
}
]
}
]
But now I need to associate the leads data into my 'leadsCategory' collection so that I can query the leads data according to the leadType created. For now, I have only one 'leadType':'all'. But further, I will create more types and populate the data accordingly.
I tried something like this:
exports.get_leads_type_all = (req, res) => {
LeadsCategory.find()
.populate('leadsData')
.then( data => {
res.json(data)
})
}
But this returns me only empty array like this:
{ "leadsData": [],
"_id": "5e88285f5dda5321bcc045a6",
"name": "all",
"createdAt": "2020-04-04T06:25:35.171Z",
"updatedAt": "2020-04-04T06:25:35.171Z",
"__v": 0
},
Please help me to associate and related this data. I have tried lot's of thins but could not make it work.
try this:
exports.get_leads_type_all = (req, res) => {
LeadsCategory.find()
.populate('leadsData')
.execPopulate()
.then( data => {
res.json(data)
})
}
https://mongoosejs.com/docs/api/document.html#document_Document-execPopulate

Mongoose - search in array with array

I have following model
var reservationSchema = new Schema({
lcode: Number,
reserved_days: [daySchema],
});
var daySchema = new Schema({
pk: Number,
date: String,
price: Number,
payment_type: String,
payment_date: String,
room: { room_id: String, name: String }
});
I have a reservation with array of reserved_days. I need to compare this reservation and reservations in database and find reservation where at least one reserved_days date or reserved_days room.room_id are the same that contains in array. Array must not be fully equal, I just need to find duplicate days.
Now I try the following
Reservation.find({
$or: [{
"reserved_days.room.room_id": {
$in: req.body.reservation.reserved_days
}
}, {
"reserved_days.date": {
$in: req.body.reservation.reserved_days
}
}]
}).exec(function(err, found)
but it doesn't work.
Test request reservation.reserved_days object looks the following
"reserved_days": [
{
"pk": 3543,
"date": "2018-07-14",
"price": 3213.0,
"payment_type": "Безналичный расчет",
"payment_date": "2017-12-28",
"room": {
"room_id": "7",
"name": "Стандарт"
}
},
{
"pk": 3544,
"date": "2018-07-15",
"price": 3213.0,
"payment_type": "Безналичный расчет",
"payment_date": "2017-12-28",
"room": {
"room_id": "7",
"name": "Стандарт"
}
}]
Is it possible to do ?

Resources