Get data from first collection and check if exist on second - node.js

Have problem:
userMoments:
userId: {
type: String,
required: true
},
momentId : {
type : String,
required : true
},
pay : {
type : Number
},
momentActive : {
type : Boolean,
default : false
}
and moments :
name: {
type: String,
required: true
},
img : {
type : String
}
I need get all moments from moments collection that not isset in collection userMoments with some userId

To achieve that I would propose the following example:
Algorithm
(1) Get all momentId that are stored into userMoments in relation with some userId
(2) Get all moments that are unrelated with momentId we got
Code
// Get all momentId
UserMoments.find({
$in: arrayThatContainsUserIds,
}, 'momentId')
// Make every momentId unique
.distinct('momentId')
.exec()
// get all moments that do not refer to ids we got before
.then((ret) => find({
$nin: Array.from(ret, x => x.momentId),
}))
.then((ret) => {
// Here you have your moments
})
.catch(err => {});
Documentations
$nin mongoDB documentation
distinct mongoose documentation
Array.from documentation
Promise documentation X.then(...).catch(...)

Related

Use arrayFilters in order to update a property of an object in nested array of objects, with Mongoose

I have a collection in MongoDb that has documents "flights" which contain a field array of objects. I want to update one property of one object at a time. In order to do so, I have to use two filters: One in order to select the document that I want to update, and a second one to select the object in the array.
I am using arrayFilters with Mongoose as follows:
This is my Flight shema
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const flightSchema = new Schema({
flightName :{ type : String, required :true},
sits : {type : Array, required : true}, //[{n:1, d:f, s:f}]
origin : {type: String, required : true},
destination : {type : String, required: true},
departure : {type : Date, required : true},
arrival : {type : Date, required : true}
})
module.exports = mongoose.model('Flight', flightSchema)
// Models/Flight.js
{
flightName: a164651,
origin: Monterrey,
detination: Cancun,
sits: [{
sitNumber: 1,
isAvailable: true,
isSuspended: false
}, {
sitNumber: 2,
isAvailable: true,
isSuspended: false
}]
}
Lets imagine that I want to update the property IsSuspended from false to true in the object with sitNumber : 2.
//Controllers dashboard.js
blockSit : async (req, res) => {
try {
const flight = req.body.flightName
const sit = req.body.sitToBlock //sit es 2
const updateSit = await Flight.updateOne(
{ "flightName": flight},
{ "$set" : {"sits.$[si].isSuspended": true} },
{ "arrayFilters": [{ "si.sitNumber": sit} ]}
)
console.log(updateSit)
} catch (error) {
console.log(error)
}
}
As far as I can see my sintaxis is correct. However I keep receiving the following error message:
Error: Could not find path "sits.0.sitNumber" in schema
I do not have to use arrayfilters necesarily. I am open to try any other solution that allows me to update a property in a nested array of objects with mongoose.
It looks like your sits field is array of sub-documents but there is not a schema to describe the fields. Try defining the schema.
const sitSchema = new Schema({
sitNumber: Number,
isAvailable: Boolean,
isSuspended: Boolean,
// etc
});
// in the flight schema
sits: [sitSchema],
This is how I solved:
First I needed to use the method findOneandUpdate in Mongoose.
Also, I added to arrayFilters the property new and set it to true.
blockSit : async (req, res) =>{
try {
const flight = req.body.flightName
console.log(flight)
const sit = req.body.sit
console.log(sit)
const updateSit = await Flight.findOneAndUpdate(
{"flightName" : flight},
{ "$set" : {"sits.$[si].isSuspended" : true, "sits.$[si].isAvailable": false} },
{"arrayFilters" :[ { "si.sitNumber" :sit} ], new : true}
)
res.send(updateSit)
}
catch (error) {
console.log(error)
}
},

Concurrency problems updating another's collection stats

I'm trying to make a notation system for movies
A user can note a Movie in their List.
Whenever the user clicks on the frontend, the listId, movieId, note are sent to the server to update the note. The note can be set to null, but it does not remove the entry from the list.
But if the user clicks too much times, the movie's totalNote and nbNotes are completely broken. Feels like there is some sort of concurrency problems ?
Is this the correct approach to this problem or am I updating in a wrong way ?
The mongoose schemas related :
// Movie Schema
const movieSchema = new Schema({
// ...
note: { type: Number, default: 0 },
totalNotes: { type: Number, default: 0 },
nbNotes: { type: Number, default: 0 },
})
movieSchema.statics.updateTotalNote = function (movieId, oldNote, newNote) {
if (!oldNote && !newNote) return
const nbNotes = !newNote ? -1 : (!oldNote ? 1 : 0) // If oldNote is null we +1, if newNote is null we -1
return Movie.findOneAndUpdate({ _id: movieId }, { $inc: { nbNotes: nbNotes, totalNotes: (newNote - oldNote) } }, { new: true }).catch(err => console.error("Couldn't update note from movie", err))
}
// List Schema
const movieEntry = new Schema({
_id: false, // movie makes an already unique attribute, which is populated on GET
movie: { type: Schema.Types.ObjectId, ref: 'Movies', required: true },
note: { type: Number, default: null, max: 21 },
})
const listSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'Users', required: true },
movies: [movieEntry]
})
The server update API (add / Remove movieEntry are similar with $push and $pull instead of $set)
exports.updateEntry = (req, res) => {
const { listId, movieId } = req.params
const movieEntry = { movieId: movieId, note: req.body.note }
List.findOneAndUpdate({ _id: listId, 'movies.movie': movieId }, { $set: { 'movies.$[elem]': movieEntry } }, { arrayFilters: [{ 'elem.movie': movieId }] })
.exec()
.then(list => {
if (!list) return res.sendStatus(404)
const oldNote = list.getMovieEntryById(movieId).note // getMovieEntryById(movieId) = return this.movies.find(movieEntry => movieEntry.movie == movieId)
Movie.updateTotalNote(movieId, oldNote, movieEntry.note)
let newList = list.movies.find(movieEntry => movieEntry.movie == movieId) // Because I needed the oldNote and findOneAndUpdate returns the list prior to modification, I change it to return it
newList.note = movieEntry.note
newList.status = movieEntry.status
newList.completedDate = movieEntry.completedDate
return res.status(200).json(list)
})
.catch(err => {
console.error(err)
return res.sendStatus(400)
})
}
The entries I needed to update were arrays that could grow indefinitely so I had to first change my models and use virtuals and another model for the the list entries.
Doing so made the work easier and I was able to create, update and delete the entries more easily and without any concurrency problems.
This might also not have been a concurrency problem in the first place, but a transaction problem.

mongoose find by ObjectId

I'm defining a mongoose schema like this
var accountPostSchema = new mongoose.Schema({
account: {
id: { type: mongoose.Schema.Types.ObjectId, ref: 'Account' }
},
post: {
id: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' }
}
});
app.db.model('AccountPost', accountPostSchema);
When a user(account holder) create a post, I save the post in a Post schema and get the 'postId'. Then I save the 'postId' and the 'accountId'
in the above accountPostSchema like this
var fieldsToSet = {
post: {
id: postId
},
account: {
id: accountId
}
};
db.models.AccountPost.create(fieldsToSet, function(err, accountPost) {
if (err) {
// handle error
}
// handle success
});
After entering few postId's and accountId's, I see the following results in the mongo shell
> db.accountposts.find({})
{ "_id" : ObjectId("5835096d63efc04da96eb71e"), "post" : { "id" : ObjectId("5835096d63efc04da96eb71d") }, "account" : { "id" : ObjectId("5833c920c868d7264111da69") }, "__v" : 0 }
{ "_id" : ObjectId("583509e12052c7a2a93c4027"), "post" : { "id" : ObjectId("583509e12052c7a2a93c4026") }, "account" : { "id" : ObjectId("5833c920c868d7264111da69") }, "__v" : 0 }
Now how do I find all the matching 'Posts' given an accountId? (not the postId's)
For example if I the accountId is 583509e12052c7a2a93c4026, I need to find Posts with Post._id=5835096d63efc04da96eb71d and Post._id=583509e12052c7a2a93c4026
What is the query I should run to get the matching Posts?
I think, you should follow this way to get all the posts associated with particular accountid.
db.accountposts.find({'account.id' : accountId})
.populate('post.id')
.exec();
First, I would suggest changing your Schema to the following
var accountPostSchema = new mongoose.Schema({
account: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Account'
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
});
This actually makes more sense, especially when you try to populate the subdocuments. Actually, I would say this Schema is useless. Why don't you define your Post schema like the following?
var PostSchema = new mongoose.Schema({
poster: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Account'
},
message: String
});
If you use the latter code, you could execute the following query to get all posts by a particular user:
db.posts.find({poster: accountId}, function(dbErr, userPosts) {
if(dbErr) {
// Handle the error.
}
// Do something with the posts by accountId in the array userPosts.
});
The advantages of removing the id field from poster becomes clear once you try to populate poster. If you defined poster as an object with the field id and try to populate it, you will need to access data about the poster as such:
posterName = retrievedPost.poster.id.name;
Alternatively, by just making the poster field an ObjectId directly, you can access the populated user more directly:
posterName = retrievedPost.poster.name;

How to find a record using dot notation & update the value in a schema using mongoose

I am using mongoose to perform CRUD operation on my db. This is how my model looks.
var EmployeeSchema = new Schema({
name: String,
description: {
type: String,
default: 'No description'
},
department: [],
lastUpdated: {
type: Date,
default: Date.now
}
});
The department can contains array of object like this.
[
{
"id" : "55ba28f680dec4383eeebf97",
"text" : "Sales",
"topParentId" : "55ba28f680dec4383eeebf8b",
"topParentText" : "XYZ"
},
{
"id" : "55ba28f680dec4383eeebf98",
"text" : "IT",
"topParentId" : "55ba28f680dec4383eeebf8b",
"topParentText" : "XYZ"
},
{
"id" : "55ba28f680dec4383eeebf94",
"text" : "Marketing",
"topParentId" : "55ba28f680dec4383eeebccc",
"topParentText" : "ABC"
}
]
Now I need to find all the employee where department.id = '55ba28f680dec4383eeebf94' and then I need to update the text of the object.
Employee.find({'department.id': '55ba28f680dec4383eeebf94'}, function(err, Employees) {
_.each(Employees, function (emp) {
_.each(emp.department, function (dept) {
if(dept.id === '55ba28f680dec4383eeebf94'){
dept.text = 'XXXXX'; // How to update the employee to save the updated text
}
});
});
});
What is the right way to save the employee with updated text for that department?
Iterating is code is not a "sharp" way to do this. It is better to use the MongoDB update operators, especially since there is no schema defined for the array items here, so no rules to worry about:
Employee.update(
{'department.id': '55ba28f680dec4383eeebf94'},
{ "$set": { "department.$.text": "XXXXX" },
function(err,numAffected) {
// handling in here
}
);
The $set is the important part, otherwise you overwrite the whole object. As is the positional $ operator in the statement, so only the matched ( queried item in the array ) index is updated.
Also see .find**AndUpdate() variants for a way to return the modified object.
I think you can use the update model:
Employee.update({department.id: '55ba28f680dec4383eeebf94'}, {department.text: 'XXXXX'}, {multi: true},
function(err, num) {
console.log("updated "+num);
}
);
First object is the query, what to find: {department.id: '55ba28f680dec4383eeebf94'}, the second one is the update, what to update: {department.text: 'XXXXX'} and the third one is the options to pass to the update, multi means update every records you find: {multi: true}

Mongoose populate many objects using 'path array' {path: objectpath,model:'Model'}

In my webapp, after ordering (SingleOrder) for products, the customer should check for offers. if available, then I should add the order to the ComboOfferOrder.
There, I want to check for the order's payment status. Also, I have to get the entire products list.
I have all the values in my db in backend. But I am not able to populate any of the objects in 'SingleOrder' for my api method.
I have the below schemas.
*User*
{
name : String,
email : Sring,
role : String
}
*BankTransaction*
{
type : String,
transationReference: String,
date : Date,
amount : Number
}
*ComboOfferOrder*
{
customer :{
type :Schema.ObjectId,
ref : 'User'
},
order : {
type :Schema.ObjectId,
ref : 'SingleOrder'
},
productList : [{
type :Schema.ObjectId,
ref : 'Product'
}]
discount : Number,
totalCost : Number,
paymentStatus :String,
deliveryStatus : String
}
*SingleOrder*
{
code: String,
products : {
groceries:[{
type :Schema.ObjectId,
ref : 'Product'
}],
other:[{
type :Schema.ObjectId,
ref : 'Product'
}]
},
billingAddress: String,
deliveryAddress : String,
payment:{
status : String,
transaction :{
type :Schema.ObjectId,
ref : 'BankTransaction'
}
}
}
*Products*
{
name : String,
cost : Number,
expiryDate : Date
}
My api
mongoose.model('ComboOfferOrder')
.findOne({
_id: comboOfferOrderId
})
.select('order')
.exec(function(err, comboOfferOrder) {
var paths = [
{path : "payment.status"},
{path : "payment.trasaction"},
{path : "products.groceries"},
{path : "products.other"}
];
mongoose.model('comboOfferOrder').populate(comboOfferOrder.order,paths,function(err, singleOrder) {
if (err) {
return deferred.reject(err);
}
return deferred.resolve(comboOfferOrder.order);
});
});
In the result, I get only the objectIds of "payment.status","payment.trasaction",products.groceries", "products.other"
Please let me know the solution.Thanks.
You can't populate a nested field with mongoose, and therefore you must nest your callbacks.
You may reread the documentation of populate for usage examples.
This should work (not tested):
mongoose.model('ComboOfferOrder')
.findOne({
_id: comboOfferOrderId
})
.select('order')
.exec(function(err, comboOfferOrder) {
comboOfferOrder.populate('order', function(err, singleOrder) {
singleOrder.populate('products.other products.groceries etc...', function(err, singleOrder) {
if (err) {
return deferred.reject(err);
}
return deferred.resolve(singleOrder);
});
});
});
Populate lets you get a list of a user's friends, but what if you also wanted a user's friends of friends? Specify the populate option to tell mongoose to populate the friends array of all the user's friends:
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});

Resources