Mongoose Update array in a document does not work as expected - database

I'm scratching my head since a couple day on how to update the content of an array with Mongoose.
Here is my schema to begin with:
const playedGameSchema = new Schema ({
created: Date,
updated: Date,
game: {
type: Schema.Types.ObjectId,
ref: 'game'
},
creator: {
id: {
type: Schema.Types.ObjectId,
ref: 'user'
},
score: Number
},
partners: [{
id: {
type: Schema.Types.ObjectId,
ref: 'user'
},
score: Number
}]
});
module.exports = mongoose.model('PlayedGame', playedGameSchema);
Basically, what I want to achieve is to, at the same time:
- Update the creator.score (successful with dot notation).
- Update the score key for each partner (unsuccessful).
Here is the result of a document created:
{
"creator": {
"id": "5b8544fa11235d9f02a9b4f1",
"score": 0
},
"_id": "5bb6375f5f68cc5c52bc93ae",
"game": "5b45080bb1806be939bfde03",
"partners": [
{
"_id": "5bb637605f68cc5cafbc93b0",
"id": "5b85497111235d677ba9b4f2",
"score": 0
},
{
"_id": "5bb637605f68ccc70ebc93af",
"id": "5b85497111235d677ba9b4f2",
"score": 0
}
],
"created": "2018-10-04T15:53:03.386Z",
"updated": "2018-10-04T15:53:03.386Z",
"__v": 0
}
As I said, I was able to change the score of the score creator by passing something like { "creator.score": 500 } as a second parameter, then I switch to trying to update the array.
Here is my lambda function to update the score for each partner:
export const update: Handler = (event: APIGatewayEvent, context: Context, cb: Callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const body = JSON.parse(event.body);
let partnersScore: object = {};
if(body.update.partners) {
body.update.partners.forEach((score, index) => {
const key = `partners.${index}.$.score`;
partnersScore = Object.assign(partnersScore, { [key]: score});
console.log(partnersScore);
});
}
connectToDatabase().then(() => {
console.log('connected', partnersScore)
PlayedGame.findByIdAndUpdate(body.id, { $set: { partners: partnersScore } },{ new: true})
.then(game => cb(null, {
statusCode: 200,
headers: defaultResponseHeader,
body: JSON.stringify(game)
}))
.catch(err => {
cb(null, {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: err
})});
});
}
Which passes a nice { 'partners.0.$.score': 500, 'partners.1.$.score': 1000 } to the $set.
Unfortunately, the result to my request is a partners array that contains only one empty object.
{
"creator": {
"id": "5b8544fa11235d9f02a9b4f1",
"score": 0
},
"_id": "5bb6375f5f68cc5c52bc93ae",
"game": "5b45080bb1806be939bfde03",
"partners": [
{
"_id": "5bb63775f6d99b7b76443741"
}
],
"created": "2018-10-04T15:53:03.386Z",
"updated": "2018-10-04T15:53:03.386Z",
"__v": 0
}
Can anyone guide me into updating the creator score and all partners score at the same time?

My thoughs about findOneAndUpdate method on a model is that it's better because it doesn't require the data to be changed outside of the BDD, but wanting to update array keys and another key seems very difficult.
Instead, I relied on a set/save logic, like this:
PlayedGame.findById(body.id)
.then(game => {
game.set('creator.score', update.creatorScore);
update.partners.forEach((score, index) => game.set(`partners.${index}.score`, score));
game.save()
.then(result => {
cb(null, {
statusCode: 200,
headers: defaultResponseHeader,
body: JSON.stringify(result)
})
})
.catch(err => {
cb(null, {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({ 'Update failed: ': err })
})});
})

Related

how to refactoring $expr, $regexMatch filter for easier reading React/MongoDB?

I would like to explain my problem of the day.
Currently I perform a filter on an input which allows me to search the last name and first name it works really well
I have deleted a lot of things for a simpler reading of the code if there is a need to bring other element do not hesitate to ask
const {
data: packUsersData,
} = useQuery(
[
"pack",
id,
"users",
...(currentOperatorsIds.length ? currentOperatorsIds : []),
value,
],
async () => {
const getExpr = () => ({
$expr: {
$or: [
{
$regexMatch: {
input: {
$concat: ["$firstName", " ", "$lastName"],
},
regex: value,
options: "i",
},
},
{
$regexMatch: {
input: {
$concat: ["$lastName", " ", "$firstName"],
},
regex: value,
options: "i",
},
},
],
},
});
let res = await usersApi.getrs({
pagination: false,
query: {
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
$or: value
? [
{
entities: [],
...getExpr(),
},
{
entities: { $in: id },
...getExpr(),
},
]
: [
{
entities: [],
},
{
entities: { $in: id },
},
],
},
populate: "entity",
sort: ["lastName", "firstName"],
});
{
refetchOnMount: true,
}
);
and so i find the read a bit too long have any idea how i could shorten all this?
thx for help.
You can reduce entities field $or condition, just concat the empty array and input id,
let res = await usersApi.getrs({
pagination: false,
query: {
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
entities: { $in: [[], ...id] },
...getExpr()
},
populate: "entity",
sort: ["lastName", "firstName"]
});
If you want to improve the regular expression condition you can try the below approach without using $expr and aggregation operators,
create a function and set input searchKeyword and searchProperties whatever you want to in array of string
function getSearchContiion(searchKeyword, searchProperties) {
let query = {};
if (searchKeyword) {
query = { "$or": [] };
const sk = searchKeyword.trim().split(" ").map(n => new RegExp(n, "i"));
searchProperties.forEach(p => {
query["$or"].push({ [p]: { "$in": [...sk] } });
});
}
return query;
}
// EX:
console.log(getSearchContiion("John Doe", ["firstName", "lastName"]));
Use the above function in query
let res = await usersApi.getrs({
pagination: false,
query: Object.assign(
{
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
entities: { $in: [[], ...id] }
},
getSearchContiion(value, ["firstName", "lastName"])
},
populate: "entity",
sort: ["lastName", "firstName"]
});

Axios send strange array to React

I geting the data back from my API in React from a post request and I get just the first object of the entire Array.prototype
My API for the upload:
router.post("/uploads", upload.any(), async (req, res) => {
try {
if (!req.files) {
res.send({
status: false,
message: "No file uploaded",
});
} else {
let data = req.files;
res.send({
status: true,
message: "Files are uploaded",
data: data,
});
}
} catch (error) {
res.status(500).send(err);
}
});
POSTMAN gives me back:
{
"status": true,
"message": "Files are uploaded",
"data": [
{
"fieldname": "uploads\n",
"originalname": "46335256.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "client/uploads/",
"filename": "46335256-2020-08-04.jpg",
"path": "client/uploads/46335256-2020-08-04.jpg",
"size": 19379
},
{
"fieldname": "uploads\n",
"originalname": "120360358.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "client/uploads/",
"filename": "120360358-2020-08-04.jpg",
"path": "client/uploads/120360358-2020-08-04.jpg",
"size": 78075
}
]
}
perfect!
this is my function in React to upload
const uploadFiles = () => {
uploadModalRef.current.style.display = "block"
uploadRef.current.innerHTML = "File(s) Uploading..."
for (let i = 0; i < validFiles.length; i++) {
const formData = new FormData()
formData.append("images", validFiles[i])
axios
.post("http://localhost:5000/api/db/uploads", formData, {
onUploadProgress: progressEvent => {
const uploadPercentage = Math.floor(
(progressEvent.loaded / progressEvent.total) * 100
)
...// code for graphic upload
},
})
.then(resp => {
console.log(resp.data.data)
resp.data.data.map(item => {
console.log(item)
})
})
.catch(() => {
... // code
}
}
and with this I get (from the console):
[{…}]
0:
destination: "client/uploads/"
encoding: "7bit"
fieldname: "images"
filename: "46335256-2020-08-04.jpg"
mimetype: "image/jpeg"
originalname: "46335256.jpg"
path: "client/uploads/46335256-2020-08-04.jpg"
size: 19379
__proto__: Object
length: 1
__proto__: Array(0)
is an array(if I map it works) but with just the first object.
How is it possible ??
I tried even with async/await but nothing changes
Where I'm mistaking?
Thanks!

How to update the value in action creator - react

i am trying to update my response in the action creator.
Once then i receive response i am updating the time zone(as of now hardcoded)
Here the response
data = [
{
"created": {timestamp: "2018-05-12T16:55:32Z", Id: "234j", name: "jim"}
"id": "804690986026920900000061579629"
"lastUpdated": {timestamp: "2018-05-12T16:55:32Z", Id: "234j", name: "jim"}
"note": "standard 9"
},
{
"created": {timestamp: "2018-05-12T17:49:32Z", Id: "444a", name: "antony"}
"id": "804690986026920900000061579630"
"lastUpdated": {timestamp: "2020-05-12T16:49:32Z", Id: "444a", name: "antony"}
"note": "standard 9"
},
{
"created": {timestamp: "2018-05-12T17:55:12Z", Id: "123m", name: "mark"}
"id": "804690986026920900000061579631"
"lastUpdated": {timestamp: "2020-05-12T17:49:12Z", Id: "123m", name: "mark"}
"note": "standard 9"
}
];
action.js
then((results) => {
const hardcodedValue = "2020-05-22T04:49:44Z"
const getLocaltime = results.data.map((updatetime)=>{
return {...updatetime, lastUpdated.timestamp:hardcodedValue}
//getting error at lastUpdated.timestamp
})
results.data = getLocaltime;
dispatch({
type: "RECEIVED_DATA",
payload: updateId === '' ? {} : results,
})
Thats not a valid object:
{ ...updatetime, lastUpdated.timestamp:hardcodedValue }
Try fixing it to:
{ ...updatetime, lastUpdated: { ...updatetime.lastUpdated, timestamp: hardcodedValue } }
You could also do it like this:
updatetime.lastUpdated.timestamp = hardcodedValue;
return {...updatetime}
This would update the lastUpdated object and since you return a new outer object, the reference would change and you would not lose any data.
The easiest way, since its new data and the object reference can stay the same, you can just mutate it like this:
then((results) => {
const hardcodedValue = "2020-05-22T04:49:44Z";
results.data.forEach((row) => {
row.lastUpdated.timestamp = hardcodedValue;
});
dispatch({
type: "RECEIVED_DATA",
payload: localAccountId === '' ? {} : results,
})

How to increment property's value(integer) inside .update() and $set mongoose?

I'm trying to find a document in my database using findOne() and then search that document for options array that contains objects. Then I check object's property if it's equal to pollOption then I want to increment that object's another property votes by 1, but I can't get that property's value so I can increment it. Please help.
Routes.js
router.post('/submitVote', function(req, res){
const {pollId, pollOption} = req.body;
Polls.findOne({_id: pollId}
).update({'options.option': pollOption}, {'$set': {
'options.$.votes': '', // INCREMENT BY 1 //
}}, function(err){
if(err){
return console.log(err);
} else {
return res.send('success');
}
});
});
Sample Model:
{
"_id": {
"$oid": "5b2ec4852a51d06734f71e79"
},
"options": [
{
"option": "Amazing!",
"votes": 0
},
{
"option": "Good.",
"votes": 0
}
],
"creator": "Guest",
"name": "Rate this website!",
"__v": 0
}
Polls.js - Schema
var mongoose = require('mongoose');
const Poll = new mongoose.Schema({
name: { type: String, required: true },
options: { type: Array, required: true },
creator: { type: String, default: 'Guest' }
});
const Polls = mongoose.model('Polls', Poll);
module.exports = Polls;

How to remove Object from array using mongoose

I'm trying to remove an object from an array in a document using mongoose.
The Schema is the following:
var diveSchema = new Schema({
//irrelevant fields
divers: [{
user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
meetingLocation: { type: String, enum: ['carpool', 'onSite'], required: true },
dives: Number,
exercise: { type: Schema.Types.ObjectId, ref: 'Exercise' },
}]
});
a possible entry can be
{
//irrelevant fields
"divers": [
{
"_id": "012345678",
"user": "123456789",
"meetingLocation": "carpool",
"exercise": "34567890",
},
{
"_id": "012345679",
"user": "123456780",
"meetingLocation": "onSite",
"exercise": "34567890",
}
]
}
Say I want to remove the entry where user is 123456789 (note I do not know the _id at this point).
How do I do this correctly?
I tried the following:
var diveId = "myDiveId";
var userIdToRemove = "123456789"
Dive.findOne({ _id: diveId }).then(function(dive) {
dive.divers.pull({ user: userIdToRemove });
dive.save().then(function(dive) {
//do something smart
});
});
This yieled no change in the document.
I also tried
Dive.update({ _id: diveId }, { "$pull": { "divers": { "diver._id": new ObjectId(userIdToRemove) } } }, { safe: true }, function(err, obj) {
//do something smart
});
With this I got as result that the entire divers array was emptied for the given dive.
What about this?
Dive.update({ _id: diveId }, { "$pull": { "divers": { "user": userIdToRemove } }}, { safe: true, multi:true }, function(err, obj) {
//do something smart
});
I solve this problem using this code-
await Album.findOneAndUpdate(
{ _id: albumId },
{ $pull: { images: { _id: imageId } } },
{ safe: true, multi: false }
);
return res.status(200).json({ message: "Album Deleted Successfully" });
Try this
Dive.update({ _id: diveId },{"$pull": { "drivers": {"user": "123456789"}}})
Try this async code
var diveId = "myDiveId";
var userIdToRemove = "123456789"
const dive=await Dive.findOne({ _id: diveId })
await dive.divers.pull({ user: userIdToRemove });
await dive.save();
Use this with try/catch:
await Group.updateOne(
{ _id: groupId },
{ $pull: { members: {id: memberId }}}
);

Resources