MongoDB - addToSet not adding element to array - node.js

"mongoose": "^5.12.2"
I have a schema named User. This schema has a field named "rol" of type string[] for a multiple rol application (User, Admin, Freetour, BarOwner, etc).
The function that adds a rol to a user is defined like this:
public addRolToUser = (idUser:string, newRol:string):Promise<IUser> => {
try{
return new Promise<IUser>((resolve, reject) => {
User.findByIdAndUpdate(idUser, { addToSet: {rol:newRol} }, {new:true}).then(user => {
return resolve(user);
}).catch(err => {
return reject(err);
});
});
}catch (e) {
throw e;
}
};
However this doesn´t update the "rol" field of the user. The following function should add the rol "FreeTour" to the user with the id returned by "petition.user".
public acceptPetition = async(req:Request, res:Response) => {
try{
return this.solFreeTourService.acceptPetition(req.body.idPetition).then(petition => {
let acceptPromise = new Promise((resolve, reject) => {
// Here I´m invoking the addRolToUser function
return this.userService.addRolToUser(petition.user, "FREETOUR").then((resUser)=>{
// resUser here has the same value for the "rol" field, didn´t get updated.
return resolve(petition);
}).catch(err=>{
return reject(err);
})
})
return acceptPromise.then(petition=>{
return res.status(200).json({petition});
}).catch(e=>{
res.status(400).json({ status: 400, message: "There has been an error." });
});
})
}catch (e) {
res.status(400).json({ status: 400, message: "There has been an error." });
}
}
I don't want repeated values in the "rol" array, hence pushing is not an option.
What am I doing wrong?

first of all, Welcome to StackOverflow! 👋
I have to assume that you might have something not working well together as you say you're using Mongoose and for such I've made a very simple project that you can look into in GitHub
where I create a very simple schema
const UserSchema = mongoose.Schema({
role: [{
type: String
}],
guid: {
type: String,
required: true
},
});
and then make use of the Mongoose API to create, update and find the user
const guid = uuidv4();
// create user
await UserModel.create({ guid });
log('user created');
["admin", "user", "admin"].forEach(async (role) => {
// add role to user
await UserModel.updateOne({ guid }, { $addToSet: { role } });
log(`user role updated with ${role}`);
});
// read user
const newUser = await UserModel.where({ guid }).findOne();
log(JSON.stringify(newUser, null, 2));
and the output is the expected one
user created
user role updated with admin
user role updated with user
user role updated with admin
{
"role": [
"admin",
"user"
],
"_id": "60a2397b1c488d4968d6ed46",
"guid": "26ccacbf-ddbc-4cbf-ac69-2da3235e156b",
"__v": 0
}
fell free to look into the source code, clone, run, and test, and notice that I'm using in fact the Mongo command as $addToSet

Related

mongo DB always update first document

I have a posts collection that has array of likes.I want to push object into likes array if user have not liked and pull if user has liked the post.I test my API but it always update first document of collection though I provided postId of other document.
schema.js
likes: [
{
userId: String,
isNotified: {
type: Boolean,
default: false,
},
email: String,
types: String,
},
],
API
router.post("/like", (req, res) => {
postModel.find(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
(err, doc) => {
// console.log(doc)
if (!doc.length) {
postModel.updateOne(
{ "_Id": req.body.postId,},
{
$push: {
likes: {
userId: req.body.userId,
email: req.body.email,
// types: req.body.types,
},
},
},
(err, doc) => {
res.send("like");
}
);
} else {
// console.log("pull")
postModel.find(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
(err, doc) => {
doc.map((e) => {
e.likes.map((x) => {
if (x.userId == req.body.userId) {
postModel.updateOne(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
{
$pull: {
likes: {
userId: req.body.userId,
email:req.body.email
},
},
},
(err, doc) => {
res.send("unlike");
}
);
}
});
});
}
);
}
// res.send(doc);
}
);
// });
});
postman request
{
"email":"mahima#gmail.com",
"types":"like",
"postId":"6312c2d1842444a707b6902f",
"userId":"631452d0e1c2acf0be28ce43"
}
How to fix this,suggest an advice.Thanks in advance.
I'm not sure if I undrestand the logic, but here are couple of things that I think you can improve:
You are using find method to get a single document, you should use findOne method which return a single document (if exists) and not an array of documents. But in general when you have the _id value of a document, it's better to just use findById method which is much faster.
When you find a document, you can just modify it and call it's save method to write your changes to the database, there is no need to use updateOne. (please note that partital update has many advantages but in your case they don't seem necessary, you can read about it online.)
your API code can be something like this:
router.post("/like", (req, res) => {
const postId = req.body.postId
const userId = req.body.userId
postModel.findById(postId) // get the post
.then(post => {
if (post) { // check if post exists
// check if user has already liked the post
if (post.likes.find(like => like.userId == userId)){
// user has already liked the post, so we want to
// remove it from likes (unlike the post).
// I know this is not the best way to remove an item
// from an array, but it's easy to understand and it
// also removes all duplications (just in case).
post.likes = post.likes.filter(like => like.userId != userId)
// save the modified post document and return
return post.save(_ => {
// send success message to client
res.send("unlike")
})
} else {
// user has not liked the post, so we want to add a
// like object to post's likes array
post.likes.push({
userId: userId,
email: req.body.email // you can other properties here
})
// save the modified post document and return
return post.save(_ => {
// send success message to client
res.send("like")
})
}
} else { // in case post doesn't exist
res.status(404).send("post not found.")
}
})
.catch(err => {
// you can handle errors here
console.log(err.message)
res.send("an error occurred")
})
})
I didn't run the code, but it should work.

Why User is registering instead showing err 11000 in mongoose?

Repo : https://github.com/heet-vakharia/codepen-cloned-server
I m creating Codepen Clone Backend using mongoose
I dont why but register route is not working
It is showing is this err on creating new user
{
"err": {
"driver": true,
"name": "MongoError",
"index": 0,
"code": 11000,
"keyPattern": {
"pens.id": 1
},
"keyValue": {
"pens.id": null
}
}
}
Here is the Reagister Route function
const register = async (req, res, User, bcrypt) => {
const { userid, password } = req.body;
if (userid && password) {
var encryptedPassword = bcrypt.hashSync(password, 8);
User.create(
{
userid: userid,
password: encryptedPassword,
pens: [],
},
(err, user) => {
if (err) {
res.status(405).status(err);
} else {
res.status(201).json(user);
}
}
);
} else {
res.status(400).json({ msg: "Plz provide all information" });
}
};
export default register;
Code 11000 means you're trying to save a duplicate record. From the repo, userid is unique so when you try to save the same record twice you'll get code 11000.
You should instead look to use an update function (even when creating new records). When you use {upsert:true}, new documents will be inserted into the collection, while existing records will be updated.
findOneAndUpdate() is a useful function.

Reading information from Database collection and then bulk writing OR bulk updating

I am syncing users from another service into our systems. These users are saved into a collection called TempUser, that is about 10k documents (This will continue to grow). There is steps that happen whenever I want to update/create a new user and they are as follows:
Read the TempUser from the database
Determine if the document from TempUser is an existing user on the database. IF they are an existing user, I would need to update the User document in the database. IF they are NOT existing in the database I want to write them as a new User. If the user is a new User, they require also a document called Profile, that is saved as user.profile in the User document.
Below outlines my code, I believe I can fix this by batching users by 100+, instead of just looping through an array and creating them one by one. I could maybe do a bulkWrite or bulkUpdate, but I am curious to hear feedback from the community.
Currently, I am reading the users one bye one (haha, I know):
console.log("Creating accounts in system...");
const PAGE_LIMIT = 1;
// After user promise resolve, take tempUser and add them as actual users
const totalUsers = await TempUser.countDocuments();
let currentUserCount = 0;
for (let PAGE_NUM = 1; currentUserCount < totalUsers; PAGE_NUM++) {
const skips = PAGE_LIMIT * (PAGE_NUM - 1);
const userList = await TempUser.find()
.skip(skips)
.limit(PAGE_LIMIT);
currentUserCount += userList.length;
const users = await Promise.all(
userList.map((student) => {
return new Promise(async (resolve, reject) => {
// Find school
const schoolId = await School.findOne(
{
"cleverData.cleverId": student.school,
},
{ _id: 1 }
);
let userObj = {
firstName: student.name.first,
lastName: student.name.last,
cleverId: student.id,
schoolId: schoolId._id,
cleverSchoolId: student.school,
email: student.email,
studentNumber: student.student_number,
stemuliDistrictId: student.stemuliDistrictId,
districtName: student.districtName,
districtId: student.district,
};
const user = await userSync(studentObj);
resolve(students);
});
})
);
}
the userSync then takes the object and creates or updates the user, depending on if it exists:
module.exports = exports = ({
firstName,
lastName,
schoolId,
cleverSchoolId,
email,
studentNumber,
userPrograms,
program,
programsEnrolled,
term,
password,
districtName,
districtId,
cleverId,
}) => {
const avatar =
"https://stemuli.blob.core.windows.net/stemuli/Placeholders/avatar-placeholder.png";
return new Promise(async (resolve, reject) => {
School.findOne({
_id: schoolId,
})
.then(async (schoolDoc) => {
if (schoolDoc) {
// We need to get the district name, so get a user with it
// Check if user exists with email
let checkedUser = null;
try {
let update = {
$set: {
"district.cleverId": districtId,
"district.name": districtName,
schoolCleverId: cleverSchoolId,
cleverId: cleverId,
school: schoolId,
},
};
if (typeof studentPrograms !== "undefined") {
update = {
...update,
$addToSet: { userPrograms: { $each: userPrograms } },
};
}
checkedUser = await User.findOneAndUpdate({ email: email }, update);
} catch (err) {
console.error(err);
return reject({
code: 500,
message: "Unexpected error occured",
});
}
if (checkedUser) {
return resolve("Email already exists");
}
let newUser = null;
try {
newUser = await new User({
name: firstName + " " + lastName,
email: email.toLowerCase(),
cleverId: cleverId,
district: {
cleverId: districtId,
name: districtName,
},
studentInfo: { sis_id: studentNumber },
account_type: "student",
profile_type: "StudentProfile",
program: program,
school: schoolId,
schoolCleverId: cleverSchoolId,
firstName: firstName,
lastName: lastName,
password: password,
}).save();
let studentProfile = null;
try {
userProfile = await new UserProfile({
user: newUser._id,
profile_picture: { url: avatar },
}).save();
assignProjects(schoolDoc.district, newUser._id);
newUser.profile = userProfile._id;
newUser
.save()
.then((userDocFinal) => {
payload = {
id: userDocFinal.id,
firstName: userDocFinal.firstName,
lastName: userDocFinal.lastName,
name: userDocFinal.name,
profile_picture: {
url: avatar,
},
hasDailyReport: false,
hasSignedOn: false,
account_type: userDocFinal.account_type,
hasAssessment: userDocFinal.hasAssessment,
};
resolve(payload);
})
.catch((err) => {
console.error(err);
reject({
code: 500,
message: "Error saving profile ID to new User profile id",
});
});
} catch (err) {
console.error(err);
reject({
code: 500,
message: "Error saving Student Profile",
});
}
} catch (err) {
console.error("Issue creating user profile");
}
} else {
console.log("Could not located school with id");
}
})
.catch((err) => {
console.error(err);
reject({
code: 400,
message: "School is not registered with system",
});
});
});
};
You can use upsert=true to insert the document if it's not already inserted. You can refer to this link : MongoDB-FindOneAndUpdate
And for minimising the time of processing the different documents and collections, you should use indexes. For example, schoolId should be an index.
Also, you can refer to this article about cursor based pagination: Cursor-Based-Pagination
TLDR; Cursor based pagination uses the fact that document id's are in an incremental order, so you can iterate through data using them (and they've proven to be more performant than Skip-Limit approach).

Chaining promise to update a reference document in Mongoose

Given the following schema:
user
{ uid:12345, name:myname, account=null }
account
{ _id:6789, name:"myaccount", _owner:12345 }
How can I update the user.account to have the value of its referenced field account._owner. When the account document is created I want to find and replace the user.account value. The route I have looks like this:
app.post('/accounts', authenticate, (req, res) => {
var account = new Account({
name: req.body.name,
_owner: req.body._owner,
});
account.save().then((doc) => {
//here i wasnt to update a refernce to a
// an account field in a User document and set
//it to the account.owner created above.
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
In my example when the account is created
I want to update user.account to 6789 (the value of the created account id)
Mongoose handles promises : http://mongoosejs.com/docs/promises.html
So you can simply :
app.post('/accounts', authenticate, (req, res) => {
var account = new Account({
name: req.body.name,
_owner: req.body._owner,
});
account.save()
.then((doc) => User.findOneAndUpdate(
{ uid: req.body._owner },
{ $set: { account: doc._id } },
{ new: true }
)
.then(() => doc);
}).then((account) => {
res.send(account);
}, (e) => {
res.status(400).send(e);
});
});
Another solution would be to attach a hook to the save action of account model
var Owner = require('path/to/owner/model');
var schema = new Schema({name:String,_owner:{type: Schema.Types.ObjectId,ref: 'owner'}}); // ref will be useful if you want to use populate later
schema.post('save', function(account) {
return Owner.findOne({uid:account._owner})
.then(owner => {
owner.account = account._id; // assign account id to user
return owner.save();
})
});
Then you just have to create a new account object and the hook will do it in the background.
app.post('/accounts', authenticate, (req, res) => {
var account = new Account({
name: req.body.name,
_owner: req.body._owner,
});
account.save().then((doc) => {
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
IMO, the routes look cleaner this way, you could try it out.

Fetching data with qraphQL by _id

I recently started working with GraphQL. I am able fetch the records from mongodb collections with the base of name, but if I try the same code to get the data by _id(mongodb generated id) I am getting null values for all fields.
Here is my sample code...
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
// To get a user based on id
getUser: {
type: UserType,
args: {
_id: {
description: 'The username of the user',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: (root, {_id}) => {
//Connect to Mongo DB
return mongo()
.then(db => {
return new Promise(
function(resolve,reject){
//Query database
let collection = db.collection('users');
collection.findOne({ _id},(err,userData) => {
if (err) {
reject(err);
return;
}
console.log(userData);
resolve(userData);
});
})
});
}
},
and the sample query is:
{
getUser ( _id: "55dd6300d40f9d3810b7f656")
{
username,
email,
password
}
}
I am getting response like:
{
"data": {
"getUser": null
}
}
Please suggest me any modifications if required...
Thanq.
Because the "_id" field generated by mongoDB is not just a string, it is actually ObjectId("YOUR ID STRING HERE").
So in your query, mongoDB won't find any _id that is equal to the string you feed to it.
Try to use collection.findById() instead.

Resources