How can I update element in array in mongo db node js - javascript

How can I update element in array in one of my objects in database.
I need to update just the page value in certain title. In last viewed I need to have multiple values.
My model looks something like this:
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
lastViewed: [
{
title:{
type: String,
required: true
},
page:{
type: Number
}
}
]
});
This is my node js express app
router.post("/api/update",async(req, res)=>{
let {token} = req.body;
let {itemId, page} = req.body;
if (token == null){
res.json({status: 'error', error: 'wrong token'});
}
try {
let userVerification = jwt.verify(token, JWT_SECRET);
let _id = userVerification.id;
let item = await Item.findById(itemId);
await User.updateOne({_id},{
$set: {
lastViewed: [
{
title: item.title,
page: page
}
]
}
},
{ upsert: true }).catch(err => console.log(err));
res.json({status: 'ok' });
} catch(err){
res.json({status: 'error', error: 'wrong token'});
}
});
What do you think I should do

You point out the element you want to update rather than point to the whole array. The $ operator is used to identify the array element you want to update.
Look at the documentation
You can write the update query by
User.update({
"_id": mongoose.Types.ObjectId(_id),
"lastViewed.title": "n3"
},
{
$set: {
"lastViewed.$.page": 4
}
})
Check the sample

Related

MongoDB find user by search returning all users

I am trying to query a user by their name field, and I am testing it in insomnia. I have my user schema like so:
const userSchema = mongoose.Schema({
id: { type: String },
name: {
type: String,
required: true,
},
companion: {
type: String,
required: false
},
bio: {
type: String,
required: false
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
min: 5
},
userImage: {
type: String,
required: false,
},
});
And I have my route for /search which runs the getUserBySearch function:
router.get('/search', getUserBySearch)
getUserBySearch logic:
export const getUserBySearch = async (req, res) => {
// tried req.query and req.query.search
const { searchQuery } = req.params
try {
// make the search query not case sensitive
const user = new RegExp(searchQuery, `i`)
//find the user's name using the name field
const userFound = await User.find({name: user})
res.json({data: userFound})
} catch (error) {
res.status(404).json({message: error.message})
}
}
Tested in insomnia under the route: http://localhost:3001/user/search?searchQuery=test
I should only be receiving the users whose name field include test; however I get back 200 response with ALL of the users in the DB. how can I only retrieve the users related to my search query?
In your case http://localhost:3001/user/search?searchQuery=test,
you should be accessing the ?searchQuery=test as req.query.searchQuery:
const { searchQuery } = req.query;
const user = new RegExp(searchQuery, `i`);
Since, you were wrongly reading the query value, you had undefined as a value for user constant, hence getting all users in the DB.
exports.searchRecipe = async(req,res) => {
try {
let searchTerm = req.body.searchTerm;
let user = await user.find( { $text: { $search: searchTerm, $diacriticSensitive: true } });
res.render('search', {user} );
} catch (error) {
res.satus(500).send({message: error.message || "Error Occured" });
}
}
and in the search form try this,#
<form method="POST" action="/search">
<input type="search" name="searchTerm" class="form-control" placeholder="Search..." aria-label="Search">
</form>
try like this ,

Express doesn't get another user with .map()

I came to a problem, where I can create conversations with multiple people 2 and so on. However, I can't understand why it doesn't store data to seperate User models.
Here is a code that you only need to know:
router.post(
"/",
auth,
[
check("conversators", "There should be at least two conversators").isLength(
{ min: 2 }
),
],
async (req, res) => {
const { conversators } = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
let conversation = new Conversation({
user: req.user.id,
conversators: conversators,
});
await conversators.map(async (conversator) => {
let user = await User.findById(conversator);
let newData = user;
newData.conversations.push(conversation.id);
console.log('Created data', newData);
let newUser = await User.findOneAndUpdate(
{ user: conversator },
{
$set: {
newData,
},
},
{ new: true }
);
await newUser.save();
console.log(newUser);
});
await conversation.save();
res.status(200).json(conversation);
} catch (error) {
console.error(error.message);
res.status(500).send("Server error.");
}
}
);
module.exports = router;
What I can assure is that this line: console.log('Created data', newData); prints the desired data. However, the next console: console.log(newUser); prints the same User model as the previous one.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
surname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
conversations: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "conversation",
},
],
date: {
type: Date,
default: Date.now,
},
});
module.exports = User = mongoose.model("user", UserSchema);
The reason might be the difference in search methods used to get a record for newData and newUser. You have used User.findById for newData, which will obviously return different objects for different ids. But User.findOneAndUpdate uses filter criteria that may satisfy several results, but only first will be returned. So it boldly depends on what that user field is.
Here is the part that I changed and started to see the data on MongoDB:
await conversators.map(async (conversator) => {
let user = await User.findById(conversator);
let newData = user;
newData.conversations.push(conversation.id);
new Promise(async (resolve, reject) => {
user = await User.findOneAndUpdate(
{ id: conversator },
{
$set: {
newData,
},
},
{ new: true }
);
return resolve;
})
return await user.save();
});
Posted on behalf of the question asker

findOneandReplace keeps giving error: "Error: The replacement document must not contain atomic operators."?

I am currently developing a Pokemon Team Builder app with a React frontend and an Express backend with MongoDB for the database.
As far as I can tell my TeamSchema has no such atomic operators? Here is my TeamSchema:
const mongoose = require('mongoose');
const TeamSchema = new mongoose.Schema({
name: {
type: 'String',
required: true,
unique: true,
},
team: [
{
name: { type: String },
types: [{ type: String }],
sprite: { type: String },
},
],
username: {
type: String,
required: true,
},
userId: {
type: String,
required: true,
},
});
const TeamModel = mongoose.model('Team', TeamSchema);
module.exports = TeamModel;
And the error gets thrown in this method when I attempt to call the findOneAndReplace method by finding a team that has a name and userId match.
const replaceTeam = async (req, res) => {
const { teamName: name, filteredTeam: team } = req.body;
const { username, _id: userId } = req.user;
const newTeam = new Team({ name, team, username, userId });
try {
const replacedTeam = await Team.findOneAndReplace({ name, userId }, newTeam);
console.log(replacedTeam);
res.status(200).json({ message: 'Team was successfully overwritten!' });
} catch (err) {
console.log(err);
res.status(500).json({ message: 'An error occurred while updating the team.' });
}
};
This has been a real headscratcher here and I am not sure what is going wrong here. I have only started using mongoose a couple of weeks ago, so I wonder if it's something fundamental I am misunderstanding here.
The Mongoose function findOneAndReplace expects a document object passed in. See the below code.
details.findOneAndReplace(
{ location: "New York" },
{ name: "Sunny", age: 292, location: "Detroit" },
function(err, result) {
if (err) {
res.send(err);
} else {
res.send(result);
}
}
);
Change
const newTeam = new Team({ name, team, username, userId })
to
const newTeam = {name, team, username, userId}
Also as in the other poster's code, add the new: true option to the call as follows by changing
const replacedTeam = await Team.findOneAndReplace({ name, userId }, newTeam);
to
const replacedTeam = await Team.findOneAndReplace({ name, userId }, newTeam, { new: true });
otherwise the original document will be returned into replacedTeam
You can just use findOneAndUpdate and update all the fields with new data. You can do it like this:
const replaceTeam = async (req, res) => {
const { teamName: name, filteredTeam: team } = req.body;
const { username, _id: userId } = req.user;
try {
const replacedTeam = await Team.findOneAndUpdate({ name, userId }, { name, team, username, userId }, {new: true});
console.log(replacedTeam);
res.status(200).json({ message: 'Team was successfully overwritten!' });
} catch (err) {
console.log(err);
res.status(500).json({ message: 'An error occurred while updating the team.' });
}
};

How to call `findOne` function in an array which is stored in a collection in Mongoose?

The model of the collection is
const clientInfo = {
uniqueID: {
type: String,
required: true,
},
firstName: {
type: String,
required: true,
},
lastName: String,
email: {
type: String,
required: true,
},
countryCode: String,
phone: String,
status: {
type: String,
required: true,
},
addedOn: {
date: String,
time: String,
},
};
And this model is stored in another model
const userClient = {
userID: {
type: String,
required: true,
},
clients: [clientInfo],
};
Now, I want to compare the email of the client with the request body i have received.
I am currently doing it like this:
await UserClient.findOne(
{ userID: validUser.userID },
async (err, clientList) => {
if (clientList) {
//Check for duplicate client
await clientList.findOne(
{ email: req.body.email },
(err, duplicateClient) => {
if (duplicateClient) {
return res.status(400).send(`Client already exists!`);
} else {
clientList.clients.push(client);
clientList.save();
const response = {
statusCode: 200,
message: "Client added!",
client: client,
};
res.send(response);
}
}
);
} else {
const newList = new UserClient({
userID: validUser.userID,
clients: client,
});
newList.save();
const response = {
statusCode: 200,
message: "Client added!",
client: client,
};
res.send(response);
}
}
);
});
But I am getting an error UnhandledPromiseRejectionWarning: TypeError: clientList.findOne is not a function.
What I am doing now is, finding a collection with a specific userID, and If I hot a match, i want to compare all the objects inside the array with the email, i have received on my request body.
Currently, If It does not find any match in the UserClient.findOne, everything goes well, but if I have match in UserFind.findOne and i want to call the similar method in the array, I am getting the error.
How to do I get rid of this error?
Thanks in advance.
Try this
await UserClient.findOne(
{ userID: validUser.userID,'clients.email': req.body.email },
async (err, clientList) => {
if(err){
//throw error here
}
console.log(clientList)
})
Corrected some issues,
find user by id, check if user is found then find client on the base of email in loop if found then return client exists otherwise save to User document,
if client not found then add new user and client
await UserClient.findOne(
{ userID: validUser.userID },
async (err, User) => {
// FIND CLIENT
if (User) {
let clientExists = false;
for (let c in User.clients) {
if (User.clients[c].email == req.body.email ) clientExists = true;
}
// ADD CLIENT
if (clientExists) {
User.clients.push(client);
User.save();
return res.send({
statusCode: 200,
message: "Client added!",
client: client,
});
}
// CLIENT EXISTS
else {
return res.status(400).send(`Client already exists!`);
}
}
// ADD NEW USER AND CLIENTS
else {
const newList = new UserClient({
userID: validUser.userID,
clients: client,
});
await newList.save();
return res.send({
statusCode: 200,
message: "Client added!",
client: client
});
}
}
);
I have not tested this code, let me know if you are getting any issues.

Trying to increment by 1 every time the page is viewed

I'm trying to figure out how to update the field by incrementing +1 each time the page is visited and if it has never been visited then add it to the DB.
Currently, this is what I have got but it does not seem to do much. I must have gone wrong somewhere and I have not yet implemented the part where if the page has never been viewed then create a new object in the array which is stored in the database.
Little note: Where I created the map they do match with the same ID if I view the page with the same ID as the one stored in the database but no increment happens.
exports.pageVisitCount = (req, res, next) => {
User.findById({
_id: req.userData.userId
}, 'visits', function (err, pageVists) {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
const pageCounts = pageVists.visits;
pageCounts.map(page => {
const postViewed = req.body.postId;
if (page.postId.toString() === postViewed) {
User.findByIdAndUpdate({
_id: req.userData.userId
}, {
$set: {
visits: [{
"postId": postViewed,
$inc: { visitCount: 1 }
}]
}
}, {
upsert: false
},
(err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Update successful!"
})
}
});
}
});
}
});
}
This is the schema I am using:
const visitsSchema = new Schema ({
postId: {
type: String
},
visitCount: {
type: Number
}
})
const userSchema = mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
required: true
},
answers: {
type: String
},
visits: [visitsSchema]
});
Any feedback would be highly appreciated, I would like to mention that I am new to backend, thanks!
To avoid using the map to filter the visits after querying the visits of the user under consideration, I suggest you let mongodb do that for you. In this case you first do a find based on both the user id and the postId. If you get a record matching both criteria you are sure you can easily update the user visits by incrementing the particular visits visitCount by 1.
Otherwise i.e. if they don't match any records then since u might be using a valid user id then such user has not visited such post. So you now create a new visit with the postId and initialize its visitCount to 1 (Although we intend to create, but since its a subdocument you'll need use $push). Enough of the talking try the code below.
exports.pageVisitCount = (req, res, next) => {
User.findOne({
_id: req.userData.userId, "visits.postId": req.body.postId
}, 'visits.$', function (err, user) {
if (err) {
res.status(401).json({
message: "Error Occured!"
});
} else {
if(user == null){
User.findByIdAndUpdate({
_id: req.userData.userId
}, {
$push: {
visits: {
"postId": req.body.postId,
visitCount: 1
}
}
}, function (err) {
if(err)
return res.status(401).json({
message: "Error Occured when creating new visit!"
})
return res.status(200).json({
message: "Success"
})
})
}
User.update({
_id: req.userData.userId, "visits.postId": req.body.postId
}, {
$inc: { "visits.$.visitCount": 1 }
},(err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Update successful!"
})
}
});
}
});
};

Categories