NodeJs Mongoose Db aggregation query - node.js

Below code snippets contain 2 schemas. I am trying to fetch all club items that does NOT have a booking with a given date and time in the booking schema. As you can see the booking schema has a clubItem property where it takes an object. I want to get the club items that is not booked in the given date and time.
For example if there is a booked room from 21-11-2022, 9-10, I want all available club items to be fetched except that booked club item. The last code snippet represents my approach for tackling this issues but I can't quite get what I want. I tried to aggregation and tried to understand its concept but I am having issues implementing my condition.
const { default: mongoose } = require("mongoose");
const { PaymentStatusStrings, BookingStatusStrings, PaymentMethodStrings } = require("../utils/enums");
const { ClubItem } = require("./club_item");
const bookingSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
club: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Club',
required: true
},
clubItems: {
type: ClubItem.schema,
required: true
},
date: {
type: String,
required: true
},
startingTime: {
type: String,
required: true
},
endingTime: {
type: String,
required: true
},
totalHours: {
type: Number,
required: true
},
extraTime: {
type: Number,
required: true,
default: 0
},
subtotal: {
type: Number,
required: true
},
tax: {
type: Number,
required: true
},
totalPrice: {
type: Number,
required: true
},
paymentStatus: {
type: String,
enum: Object.values(PaymentStatusStrings),
required: true
},
status: {
type: String,
enum: Object.values(BookingStatusStrings),
required: true
},
paymentMethod: {
type: String,
enum: Object.values(PaymentMethodStrings),
required: true
},
DateTimeStamp:
{
type: Date,
default: Date.now
}
})
bookingSchema.virtual('id').get(function () {
return this._id.toHexString();
});
bookingSchema.set('toJSON', {
virtual: true,
});
exports.Booking = mongoose.model('Booking', bookingSchema);
const { default: mongoose } = require("mongoose");
const { CategoryStrings, ClubItemStatusStrings } = require("../utils/enums");
const clubItemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
pricePerHour: {
type: Number,
required: true
},
minCapacity: {
type: Number,
required: true
},
maxCapacity: {
type: Number,
required: true
},
multiplayer: Boolean,
description: String,
image1: {
type: String,
required: true
},
image2: String,
image3: String,
category: {
type: String,
enum: Object.values(CategoryStrings),
required: true
},
club: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Club',
required: true
},
status: {
type: String,
enum: Object.values(ClubItemStatusStrings),
required: true
}
})
clubItemSchema.virtual('id').get(function () {
return this._id.toHexString();
});
clubItemSchema.set('toJSON', {
virtual: true,
});
exports.ClubItem = mongoose.model('ClubItem', clubItemSchema);
const { default: mongoose } = require("mongoose");
const { CategoryStrings, ClubItemStatusStrings } = require("../utils/enums");
const clubItemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
pricePerHour: {
type: Number,
required: true
},
minCapacity: {
type: Number,
required: true
},
maxCapacity: {
type: Number,
required: true
},
multiplayer: Boolean,
description: String,
image1: {
type: String,
required: true
},
image2: String,
image3: String,
category: {
type: String,
enum: Object.values(CategoryStrings),
required: true
},
club: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Club',
required: true
},
status: {
type: String,
enum: Object.values(ClubItemStatusStrings),
required: true
}
})
clubItemSchema.virtual('id').get(function () {
return this._id.toHexString();
});
clubItemSchema.set('toJSON', {
virtual: true,
});
exports.ClubItem = mongoose.model('ClubItem', clubItemSchema);
const express = require('express');
const { Booking } = require('../../models/booking');
const { ClubItem } = require('../../models/club_item');
const { Club } = require('../../models/club');
const { User } = require('../../models/user');
const router = express.Router();
router.get('/getPlaystationRooms/:datetime', async (req, res) => {
const bookingDateTime = req.params.datetime
const clubItems = await ClubItem.aggregate([
{
$lookup: {
from: "booking",
localField: "_id",
foreignField: "clubItems._id",
as: "freeClubItems"
}
}
])
// const availableClubItems = [];
// clubItems.forEach(async clubItem => {
// const booking = Booking.findOne({ 'clubItems._id': clubItem._id}).where({date: bookingDateTime})
// if(!booking)
// {
// availableClubItems.push(clubItem);
// }
// })
console.log(clubItems);
res.status(200).send(clubItems);
})
module.exports = router;
my commented approach is implementing the logic with foreach loop and pushing a club item into an empty array if the booking with the given date, time and clubItemID is not found.
my 2nd approach is using aggregation using $lookup and $match but i reached no where.
router.get('/getPlaystationRooms/:datetime', async (req, res) => {
const bookingDateTime = req.params.datetime
const clubItems = await ClubItem.aggregate([
{
$lookup: {
from: "booking",
localField: "_id",
foreignField: "clubItems._id",
as: "freeClubItems"
}
}
])
// const availableClubItems = [];
// clubItems.forEach(async clubItem => {
// const booking = Booking.findOne({ 'clubItems._id': clubItem._id}).where({date: bookingDateTime})
// if(!booking)
// {
// availableClubItems.push(clubItem);
// }
// })
console.log(clubItems);
res.status(200).send(clubItems);
})

Related

How to return documents in mongoose and express js which are associated with only logged in user?

Here i have two mongoose models orders and users which have one to many relationship.
user.model.js
import mongoose from "mongoose";
const userSchema = mongoose.Schema(
{
firstname: {
type: String,
required: [true, "Name is required"],
},
lastname: {
type: String,
required: [true, "LastName is required"],
},
email: {
type: String,
required: [true, "Email is required"],
unique: true,
},
password: {
type: String,
required: [true, "Password is required"],
},
isAdmin: { type: Boolean, default: false },
},
{
timestamps: true,
}
);
const User = mongoose.model("User", userSchema);
export default User;
order.model.js
import mongoose from "mongoose";
const orderSchema = mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
customerId: { type: String },
paymentIntentId: { type: String },
products: [
{
id: { type: String },
name: { type: String },
category: { type: String },
price: { type: String },
size: { type: String },
color: { type: String },
thumbnail: { type: String },
qty: { type: Number },
},
],
// subTotal: { type: Number, required: true },
total: { type: Number, required: true },
shipping: { type: Object, required: true },
deliveryStatus: { type: String, default: "pending" },
paymentStatus: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
const Order = mongoose.model("Order", orderSchema);
export default Order;
I have some orders created by different users in my database. Now i am trying to get those specific orders associated with currently logged in user.
order.controller.js
export const getAllOrders = async (req, res) => {
const { _id } = req.user;
// console.log(typeof id);
try {
const orders = await Order.find({userId: _id});
console.log(orders);
res.status(200).json({ orders });
} catch (error) {
res.status(500).json({ msg: error.message });
}
};
I have tried this one but it always return an empty array.

Nodejs mongoose get results from multiple collections in one query

I'm very new to Nodejs and MongoDB, I have 3 collections one for chapter,one for lecture and one for asset
every course have chapters, every chapter has array of lectures and every lecture have assets
so I want to get chapters by courseId which already done, and inside chapter to get its lectures and in every lecture to get its assets
Course:
const mongoose = require('mongoose');
var Double = require('#mongoosejs/double');
const courseSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
requirements: {
type: String,
},
code: {
type: String,
required: true,
},
coverPhoto: {
type: String,
required: false,
},
description: {
type: String
},
instructor:{
type:mongoose.Schema.Types.ObjectId,
ref:'User',
required:true
},
category:{
type:mongoose.Schema.Types.ObjectId,
ref:'Category',
required:true
},
learns: [{
type: String
}],
subCategory:{
type:mongoose.Schema.Types.ObjectId,
ref:'SubCategory',
required:true
},
isCoaching: {
type: Boolean,
default: false,
},
isFree: {
type: Boolean,
default: false,
},
price: {
type: Double,
default: 0,
},
rating: {
type: Double,
default: 0,
},
isPublished: {
type: Boolean,
default: false,
},
dateCreated: {
type:Date,
default:Date.now,
},
});
exports.Course = mongoose.model('Course', courseSchema);
exports.courseSchema = courseSchema;
Chapter:
const mongoose = require('mongoose');
const chapterSchema = new mongoose.Schema({
course:{
type:mongoose.Schema.Types.ObjectId,
ref:'Course',
required:true
},
title: {
type: String,
required: true,
},
sort_order: {
type: Number,
default: 1,
},
is_published: {
type: Boolean,
default: true,
},
});
exports.Chapter = mongoose.model('Chapter', chapterSchema);
exports.chapterSchema = chapterSchema;
Lecture:
const mongoose = require('mongoose');
const lectureSchema = new mongoose.Schema({
chapter:{
type:mongoose.Schema.Types.ObjectId,
ref:'Chapter',
required:true
},
title: {
type: String,
required: true,
},
sort_order: {
type: Number,
default: 1,
},
is_published: {
type: Boolean,
default: true,
},
});
exports.Lecture = mongoose.model('Lecture', lectureSchema);
exports.lectureSchema = lectureSchema;
Asset:
const mongoose = require('mongoose');
const assetSchema = new mongoose.Schema({
lecture:{
type:mongoose.Schema.Types.ObjectId,
ref:'Lecture',
required:true
},
title: {
type: String,
required:true
},
asset_type: {
type: String
},
description: {
type: String,
require:true
},
file_url: {
type: String,
require:true
},
page_number: {
type: Number,
default:1
},
time_estimation: {
type: String,
require:true
},
is_external: {
type: Boolean,
default: false,
},
is_published: {
type: Boolean,
default: true,
},
});
exports.Asset = mongoose.model('Asset', assetSchema);
exports.assetSchema = assetSchema;
Get Chapters of a course
router.get(`/`, async (req, res) => {
let course_filter = {};
if (req.query.course) {
course_filter = {course:req.query.course};
}
const chapterList = await Chapter.find(course_filter).populate('lecture').sort('sort_order');
if (!chapterList) {
res.status(500).json({ success: false });
}
res.send(chapterList);
});
using simple aggregation:
const chapterList = await Chapter.aggregate([{
$lookup: {
from: 'lectures',
localField: '_id',
foreignField: 'chapter',
as: 'lectures'
}}]);
You have to nest the populate in another populate:
router.get(`/`, async (req, res) => {
let course_filter = {};
if (req.query.course) {
course_filter = { course: req.query.course };
}
const chapterList = await Chapter.find(course_filter)
.populate({ path: 'lectures', populate: { path: 'assets' } })
.sort('sort_order');
if (!chapterList) {
res.status(500).json({ success: false });
}
res.send(chapterList);
});
You have to ensure that you have set a virtual for 'assets' prop in lectureSchema accordingly. I assume you have also done it for your 'Chapter' schema.
If not, you have do add the following:
virtual for Chapter schema:
chapterSchema.virtual('lectures', {
ref: Lecture.collection.collectionName,
localField: '_id',
foreignField: 'chapter'
});
virtual for Lecture schema:
lectureSchema.virtual('assets', {
ref: Asset.collection.collectionName,
localField: '_id',
foreignField: 'lecture'
});

How to get data from mongoDB on the basis of a value inside a nested object

Below is my API for fetching all the orders of a specific restaurant from a mongoDB database.
My search is based on the restaurant id which I am getting from params and passing it to the query object.
query1 is working but query2 does not return my data.
It is returning an empty array. If I pass a simple object in find() then I am getting a response. But when i use a nested object for getting data I get nothing.
exports.getOrders = async (req, res) => {
const restId = req.params.restId;
console.log("restaurant id", restId);
if (!restId) return res.status(404).send("No restaurant ID found in params");
const query1 = {grandTotal: 600};
const query2 = { restaurant: { restaurantId: restId } };
const response = await Orders.find(query2);
console.log("Printing response inside API function", response);
}
Below is my MongoDB Schema of Orders.
const mongoose = require("mongoose");
const orderSchema = mongoose.Schema({
customer: {
name: {
type: String,
required: true,
},
contact: {
type: String,
required: true,
},
customerId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Customer",
required: true,
},
customerAddress: {
type: String,
required: true,
},
},
restaurant: {
restaurantName: {
type: String,
required: true,
},
contact: {
type: String,
required: true,
},
restaurantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Restaurant",
required: true,
},
},
/*date: {
day: {
type: Number,
required: true,
},
month: {
type: String,
required: true,
},
year: {
type: Number,
required: true,
},
},
type: {
type: String,
enum: ["delivery","takeaway"],
required: true,
},*/
items: [
{
itemId: { type: String, required: true },
itemName: { type: String, required: true },
itemDescription: { type: String, required: true },
price: {type: Number, required: true},
quantity: { type: Number, required: true },
total: {type: Number, required: true},
},
],
grandTotal: {type: Number, required: true},
status: {
type: String,
enum: ["pending", "rejected","cancelled","accepted","received"],
required: true,
},
});
orderSchema.virtual("booking", {
ref: "Booking",
localField: "_id",
foreignField: "orderId",
});
orderSchema.set("toObject", { virtual: true });
orderSchema.set("toJSON", { virtual: true });
const Orders = mongoose.model("Orders", orderSchema);
exports.Orders = Orders;
change query2 to this
const query2 = { "restaurant.restaurantId": restId } };

Mongoose(mongoDB) Linking multiple schema's

Im relatively new to MongoDB and Mongoose. Im much used to MySQL so in used to inner joining tables on calls. Ive read a lot that you can link two Mongoose Schemas to achieve the same outcome. How would like like the two schemas together to when I make a call to get a chore by id it'll return the chore and then for the assignedTo & createdBy have the user scheme data for the said userId?
Chore Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ChoreSchema = new Schema({
title: {
type: String,
required: true
},
desc: {
type: String,
required: true
},
time: {
type: Number,
required: true
},
reaccurance: {
type: [{
type: String,
enum: ['Daily', 'Weekly', 'Bi-Weekly', 'Monthly']
}]
},
reward: {
type: Number,
required: true
},
retryDeduction: {
type: Number,
required: false
},
createdDate: {
type: Date,
default: Date.now
},
createdBy: {
type: String,
required: true
},
dueDate: {
type: Date,
required: true
},
status: {
type: [{
type: String,
enum: ['new', 'pending', 'rejected', 'completed', 'pastDue']
}],
default: ['new']
},
retryCount: {
type: Number,
default: 0,
required: false
},
rejectedReason: {
type: String,
required: false
},
familyId: {
type: String,
required: true
},
assignedTo: {
type: String,
required: false,
default: ""
}
});
let Chores = module.exports = mongoose.model('Chores', ChoreSchema);
module.exports.get = function (callback, limit) {
Chores.find(callback).limit(limit);
};
User Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
role: {
type: [{
type: String,
enum: ['Adult', 'Child']
}]
},
birthday: {
type: String,
required: false
},
familyId: {
type: String,
required: true
},
balance: {
type: Number,
required: true,
default: 0.00
}
});
let Users = module.exports = mongoose.model('Users', UserSchema);
module.exports.get = function (callback, limit) {
Users.find(callback).limit(limit);
};
Im trying to link ChoreSchema.createdBy & ChoreScheme.assignedTo by UserSchema._id
How I make the call in Node.js:
exports.index = function(req, res) {
Chore.get(function(err, chore) {
if (err)
res.send(err);
res.json({
message: 'Chore List',
data: chore
});
});
};
Mongoose has a more powerful alternative called populate(),
which lets you reference documents in other collections.
https://mongoosejs.com/docs/populate.html
Here is how you can link ChoreSchema.createdBy and ChoreScheme.assignedTo by UserSchema._id
var mongoose = require('mongoose');
const { Schema, Types } = mongoose;
var UserSchema = new Schema({
firstName: { type: String, required: true },
...
})
var ChoreSchema = new Schema({
title: { type: String, required: true },
...
//The ref option is what tells Mongoose which model to use during population
assignedTo: { type: Types.ObjectId, ref: 'Users' },
createdBy: { type: Types.ObjectId, ref: 'Users' },
})
let Chores = mongoose.model('Chores', ChoreSchema);
let Users = mongoose.model('Users', UserSchema);
Then in your express route handler you can populate assignedTo & createdBy like this
router.get('/chores/:id', function (req, res) {
const choreId = req.params.id;
Chores.find({ _id: choreId })
.populate('createdBy') // populate createdBy
.populate('assignedTo') // populate assignedTo
.exec(function (err, chore) {
if(err) {
return res.send(err)
}
res.json({ message: 'Chore List', data: chore });
});
})

How to delete the referenced document in one collection and its record from the referred other collection

In my NodeJS API and MongoDB, I'm trying to delete a record which is a reference to another collection.
What I would like to do is to delete the referred objectId and the records related to the other collection which is referred.
I have 2 models Profiles and Posts and I want to delete the same one post from Profile and Post collection.
I was able to delete the reference id in Profile but I don't know how to delete also the record from Posts collection.
I tried this:
async delete(req, res) {
try {
// Match with username and pull to remove
await Profile.findOneAndUpdate(
{ _id: res.id._id },
{ $pull: { posts: req.params.postId } },
err => {
if (err) {
throw new ErrorHandlers.ErrorHandler(500, err);
}
res.json({ Message: "Deleted" });
}
);
} catch (error) {
res.status(500).send(error);
}
}
And my 2 models:
// Here defining profile model
// Embedded we have the Experience as []
const { Connect } = require("../db");
const { isEmail } = require("validator");
const postSchema = {
type: Connect.Schema.Types.ObjectId,
ref: "Post"
};
const experienceSchema = {
role: {
type: String,
required: true
},
company: {
type: String,
required: true
},
startDate: {
type: Date,
required: true
},
endDate: {
type: Date,
required: false
},
description: {
type: String,
required: false
},
area: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
username: {
type: String,
required: false
},
image: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
}
};
const profileSchema = {
firstname: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
trim: true,
lowercase: true,
unique: true,
required: [true, "Email is required"],
validate: {
validator: string => isEmail(string),
message: "Provided email is invalid"
}
},
bio: {
type: String,
required: true
},
title: {
type: String,
required: true
},
area: {
type: String,
required: true
},
imageUrl: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
},
username: {
type: String,
required: true,
unique: true
},
experience: [experienceSchema],
posts: [postSchema],
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
}
};
const collectionName = "profile";
const profileSchemaModel = Connect.Schema(profileSchema);
const Profile = Connect.model(collectionName, profileSchemaModel);
module.exports = Profile;
const { Connect } = require("../db");
const reactionSchema = {
likedBy: {
type: String,
unique: true,
sparse: true
}
};
const postSchema = {
text: {
type: String,
required: true,
unique: true,
sparse: false
},
profile: {
type: Connect.Schema.Types.ObjectId,
ref: "Profile",
},
image: {
type: String,
default: "https://via.placeholder.com/150",
required: false
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
reactions: [reactionSchema],
comments: {
type: Connect.Schema.Types.ObjectId,
ref: "Comment",
required: false
}
};
const collectionName = "post";
const postSchemaModel = Connect.Schema(postSchema);
const Post = Connect.model(collectionName, postSchemaModel);
module.exports = Post;
Just add a query to remove the post after pulling it's ID from the profile collection:
async delete(req, res) {
try {
// Match with username and pull to remove
await Profile.findOneAndUpdate(
{ _id: res.id._id },
{ $pull: { posts: req.params.postId } },
// You don't need an error callback here since you are
// using async/await. Handle the error in the catch block.
);
await Posts.remove({ _id: req.params.postId });
} catch (error) {
// This is where you handle the error
res.status(500).send(error);
}
}

Resources