How to add multiple scores to user in mongodb - javascript

I have built a quiz game where when a player finishes their score is submitted to mongodb as an object for e.g:
{
_id: "b7db3e12161567"
name: "james"
score: 5
}
Now what I want to do is to create multiple quizzes, not just one. I've created multiple quizzes which all have a unique name i.e quiz1, quiz2, quiz3 etc.
I want to use this quizId and give each player a score for the specific quiz they play instead of just one singular score. I was thinking something like:
{
_id: "b7db3e12161567"
name: "james",
quiz1: 5,
quiz2: 6
}
Here is my current code for the version which just the single score:
try {
const { _id, name, score, quizId } = req.body;
const users = await mongodb.getDb()
.db('database')
.collection('users')
.updateOne(
{ _id },
{ $set: { _id, name }, $max: { score } },
{ upsert: true }
);
res.json(users);
} catch(err) {
console.log(err);
throw new Error('Cannot add user score');
}
How would I implement what I suggested with mongo? And or would there be a better way to implement what I suggested?
Thanks

One option is to use an update with pipeline query like:
.updateOne(
{ _id, name }
[
{$set: {
quiz1: {$max: ["$quiz1", 3]},
quiz2: {$max: ["$quiz1", 7]}
}}
],
{ upsert: true }
)
Which works like this
You can create it dynamically of course with something like:
const setPart = {}
for (const quiz_name of Object.keys(quizes)) {
setPart[quiz_name] = `{$max: ["$${quiz_name}", ${quizes[quiz_name]}]}`
}
const users = await mongodb.getDb()
.db('database')
.collection('users')
.updateOne(
{ _id },
{ $set: setPart },
{ upsert: true }
);

Related

Mongoose delete other objects after update

I'm having trouble with my update query on mongoose. I'm not sure why other objects get deleted after I update a specific object. the code works when I update but after that, the rest of the objects inside the array are getting deleted/removed. Literally, all of the remaining objects get deleted after the update request.
export const updateProduct = async (req,res) => {
const { id } = req.params;
try {
if(!mongoose.Types.ObjectId.isValid(id)) return res.status(404).json({ message: 'Invalid ID' });
await OwnerModels.findOneAndUpdate({'_id': id, store:{$elemMatch: {productname: req.body.store[0].productname }}},
{$set:
{
store:
{
productname: req.body.store[0].productname,
price: req.body.store[0].price,
quantity: req.body.store[0].quantity,
categoryfilter: req.body.store[0].categoryfilter,
description: req.body.store[0].description,
timestamp: req.body.store[0].timestamp
}
}
}, // list fields you like to change
{'new': true, 'safe': true, 'upsert': true});
} catch (error) {
res.status(404).json(error)
} }
I'm not sure why other objects get deleted after I update a specific object.
Because you are updating the whole object and it will replace the existing store array of object in the database,
You need to use arraFilters, and upsert is not effective in array of object updated, so i have commented,
await OwnerModels.findOneAndUpdate(
{
'_id': id,
store:{
$elemMatch: {
productname: req.body.store[0].productname
}
}
},
{
$set: {
store: {
"store.$[s].productname": req.body.store[0].productname,
"store.$[s].price": req.body.store[0].price,
"store.$[s].quantity": req.body.store[0].quantity,
"store.$[s].categoryfilter": req.body.store[0].categoryfilter,
"store.$[s].description": req.body.store[0].description,
"store.$[s].timestamp": req.body.store[0].timestamp
}
}
},
{
'arrayFilters': [
{ "s.productname": req.body.store[0].productname }
],
'new': true,
'safe': true,
// 'upsert': true
}
);

How to wait for a function to finish with all its inside expressions, in router in mongoose?

I just want to change the value of a key of all the objects inside an array
What I want actually -
The object which I queried from the database is -
{
_id: 61389277fa5c742caf959885,
title: 'What is GRE?',
forumTab: 'GRE',
askedAt: 2021-09-08T10:37:43.979Z,
askedBy: {
_id: 60f0a6a9b4259f7ef9c49cc8,
}
}
I want to add more key-value pairs in the askedBy key by again querying the database for the User with the given _id
Now, the user object which is queried is -
{
role: 'student',
_id: 60f0a6a9b4259f7ef9c49cc8,
firstName: 'Rishav',
lastName: 'Raj'
}
Finally I want to return the below object in response -
{
_id: 61389277fa5c742caf959885,
title: 'What is GRE?',
forumTab: 'GRE',
askedAt: 2021-09-08T10:37:43.979Z,
askedBy: {
_id: 60f0a6a9b4259f7ef9c49cc8,
role: 'student',
firstName: 'Rishav',
lastName: 'Raj'
}
}
I am creating a new array questionsToSend and pushing the object with updated key-value pairs which I am getting after querying the database for each elements in the questions array, I have created functions for respective query that I need to render in sequence, even after rendering the functions in proper sequence why the new array questionsToSend is not populating with the objects before returning the response?
router.get("/questions", async (req, res) => {
if (!req.query.forumTab) return res.status(400).send("something went wrong");
const page = parseInt(req.query.page) - 1;
const perPage = parseInt(req.query.perPage);
let questionsToSend = [];
const func0 = async (callback) => {
const questions = await Question.find({ forumTab: req.query.forumTab })
.sort({ askedAt: -1 })
.limit(perPage)
.skip(perPage * page);
console.log("xxxxxxx");
callback(questions);
};
const func1 = async (questions, callBack) => {
questions.forEach(async (question) => {
const askedUserData = await User.findById(question.askedBy._id);
if (!askedUserData) {
const index = questions.indexOf(question);
questions.splice(index, 1);
return;
}
questionsToSend.push({
..._.pick(question, [
"_id",
"title",
"askedAt",
"tags",
]),
askedUserData,
});
console.log(questionsToSend);
});
console.log("yyyyyyyy");
callBack();
};
func0(
(questions) =>
func1(questions, async () => {
console.log("zzzzzzzz");
res.status(200).send(questionsToSend);
})
);
});
We can use aggregation to achieve this
Question.aggregate([
{
$match: { forumTab: req.query.forumTab }
},
{
$lookup: {
from: 'users',
localField: 'askedBy._id',
foreignField: '_id',
as: "user"
}
},
{ $unwind: "$user"},
{ "$addFields": {
"askedBy": {
"$mergeObjects": ["$askedBy", "$user"]
}
}
},
{ $project: { "user" : 0} },
{ $sort: {"askedAt": -1}},
{ $skip: perPage * page},
{ $limit: perPage},
])
$match is used to apply filter
$lookup is used to do a join on a collection. I have assumed the collection name is users.
$lookup returns the matched result as an array. Converting it to object using $unwind since we get only one back.
$addFields with $mergeObjects is merging the existing askedBy field and newly user field
Removing the user field from the result set with $project.
And then sort, skip and limit.

Update or Upsert multiple document mongodb

I am working on a project using mongodb database, and I want to update or upsert multiple document.
So here is what I want to do:
if (you received an array of id )
{
create a verify condition which checks if there is a document for each id and respect the dateFin condition
}
else if (you received one id)
{
create a verify condition which checks if there is a document equal the id and respect the dateFin condition
}
then check if there is a document, then update it, else create it either you got an array or one id
if (Array.isArray(req.body.user) === true)
verify = {
idUser: { $in: req.body.user.map(id => { return ObjectId(id) }) },
dateFin: { $gt: moment().utc(1).format('YYYY-MM-DD HH:MM:SS') }
}
else verify =
{
idUser: ObjectId(req.body.user),
dateFin: { $gt: moment().utc(1).format('YYYY-MM-DD HH:MM:SS') }
}
db.collection("paiement").updateMany(verify,
{
$set: {
dateFin: moment(req.body.date).format('YYYY-MM-DD HH:MM:SS'),
paiementType: "offre",
abonnement: req.body.abonnement,
dateInsert: moment().utc(1).format('YYYY-MM-DD HH:MM:SS'),
},
},
{ upsert: true },
(err, document) => {
if (err) return res.status(400).json(err);
console.log(err);
console.log(document.result);
})
}
but it works with one id, and not with multiple ids, Thank you if there is any one can help me.

how do I increment field in array of objects after finding it (using findOne()) and before saving it?

I want to update an object inside an array of schemas without having to do two requests to the database. I currently am incrementing the field using findOneAndUpdate() if the object already exists and it works fine. but in case the object does not exist then I am having to make another request using update() to push the new object and make it available for later increments.
I want to be able to do only one request (e.g. findOne()) to get the user and then increment the field only if object exists in the array and if not I would like to push the new object instead. then save the document. this way I am only making one read/request from the database instead of two.
this is the function now:
async addItemToCart(body, userId) {
const itemInDb = await Model.findOneAndUpdate(
{
_id: userId,
'cart.productId': body.productId,
},
{ $inc: { 'cart.$.count': 1 } }
);
if (itemInDb) return true;
const updated = await Model.update(
{ _id: userId },
{ $push: { cart: body } }
);
if (updated.ok !== 1)
return createError(500, 'something went wrong in userService');
return true;
}
what I would like to do is:
async addItemToCart(body, userId) {
const itemInDb = await Model.findOne(
{
_id: userId,
'cart.productId': body.productId,
}
);
if (itemInDb) {
/**
*
* increment cart in itemInDb then do itemInDb.save() <<------------
*/
} else {
/**
* push product to itemInDb then save
*/
}
Thank you!
You can try findOneAndUpdate with upsert.
upsert: true then create data if not exists in DB.
Model.findOneAndUpdate(
{
_id: userId,
'cart.productId': body.productId,
},
{ $inc: { 'cart.$.count': 1 } },
{
upsert: true,
}
)
Use $set and $inc in one query.
try {
db.scores.findOneAndUpdate(
{
_id: userId,
'cart.productId': body.productId,
},
{ $set: { "cart.$.productName" : "A.B.C", "cart.$.productPrice" : 5}, $inc : { "cart.$.count" : 1 } },
{ upsert:true, returnNewDocument : true }
);
}
catch (e){
//error
}
reference Link : here
You can use upsert.
upsert is defined as an operation that creates a new document when no document matches the query criteria and if matches then it updates the document. It is an option for the update command. If you execute a command like below it works as an update, if there is a document matching query, or as an insert with a document described by the update as an argument.
Example: I am just giving a simple example. You have to change it according to your requirement.
db.people.update(
{ name: "Andy" },
{
name: "Andy",
rating: 1,
score: 1
},
{ upsert: true }
)
So in the above example, if the people with name Andy is found then the update operation will be performed. If not then it will create a new document.

Update field in sub document mongoose

My parent model
var GameChampSchema = new Schema({
name: String,
gameId: { type: String, unique: true },
status: Number,
countPlayers: {type: Number, default: 0},
companies: [
{
name: String,
login: String,
pass: String,
userId: ObjectId
}
],
createdAt: {type: Date, default: Date.now},
updateAt: Date
})
I need insert userId property in first child where he is not set
So, need this action only on parent with condition ({status: 0, countPlayers: { $lt: 10 })
Since this is an embedded document it is quite easy:
If you want to update a document that is the first element of the array, that doesn't have a userId
db.collection.update(
{
"status": 0,
"countPlayers": {"$lt": 10 },
"companies.userId": {"$exists": false }
},
{ "$set": {"companies.$.userId": userId } }
)
Which would be nice, but apparently this doesn't match how MongoDB processes the logic and it considers that nothing matches if there is something in the array that does have the field present. You could get that element using the aggregation framework but that doesn't help in finding the position, which we need.
A simplified proposal is where there are no elements in the array at all:
db.collection.update(
{
"status": 0,
"countPlayers": {"$lt": 10 },
"companies.0": {"$exists": false }
},
{ "$push": {"userId": userId } }
)
And that just puts a new thing on the array.
The logical thing to me is that you actually know something about this entry and you just want to set the userId field. So I would match on the login:
db.collection.update(
{
"status": 0,
"countPlayers": {"$lt": 10 },
"companies.login": login,
},
{ "$set": {"companies.$.userId": userId } }
)
As a final thing if this is just updating the first element in the array then we don't need to match the position, as we already know where it is:
db.collection.update(
{
status: 0,
countPlayers: {"$lt": 10 }
},
{ $set: { "companies.0.userId": userId } }
)
Tracing back to my logical case, see the document structure:
{
"_id" : ObjectId("530de54e1f41d9f0a260d4cd"),
"status" : 0,
"countPlayers" : 5,
"companies" : [
{ "login" : "neil" },
{ "login" : "fred", "userId" : ObjectId("530de6221f41d9f0a260d4ce") },
{ "login": "bill" },
]
}
So if what you are looking for is finding "the first document where there is no userId", then this doesn't make sense as there are several items and you already have a specific userId to update. That means you must mean one of them. How do we tell which one? It seems by the use case that you are trying to match the information that is there to an userId based on information you have.
Logic says, look for the key value that you know, and update the position that matches.
Just substituting the db.collection part for your model object for use with Mongoose.
See the documentation on $exists, as well as $set and $push for the relevant details.
Big thanks.
I solved his problem
exports.joinGame = function(req, res) {
winston.info('start method');
winston.info('header content type: %s', req.headers['content-type']);
//достаем текущего пользователя
var currentUser = service.getCurrentUser(req);
winston.info('current username %s', currentUser.username);
//формируем запрос для поиска игры
var gameQuery = {"status": 0, "close": false};
gameChamp.findOne(gameQuery, {}, {sort: {"createdAt": 1 }}, function(error, game) {
if (error) {
winston.error('error %s', error);
res.send(error);
}
//если игра нашлась
if (game) {
winston.info('Append current user to game: %s', game.name);
//добавляем userId только к одной компании
var updateFlag = false;
for (var i=0; i<game.companies.length; i++) {
if (!game.companies[i].userId && !updateFlag) {
game.companies[i].userId = currentUser._id;
updateFlag = true;
winston.info('Credentials for current user %s', game.companies[i]);
//если пользовател последний закрываем игру и отправляем в bw, что игра укомплектована
if (i == (game.companies.length-1)) {
game.close = true;
winston.info('game %s closed', game.name);
}
}
}
//сохраняем игру в MongoDB
game.save(function(error, game) {
if (error) {
winston.error('error %s', error);
res.send(error);
}
if (game) {
res.send({ game: game })
winston.info('Append successful to game %s', game.name);
}
});
}
});
}

Categories