How to update a MongoDB document field only once? - node.js

I'm trying to make a Mongoose query to run only once if the field inside the document hasn't been updated already, but I'm getting a bit lost with the $exist, $update, $ne - I don't know what to use really.
I have the following query:
const assignPhotoCodeToModel = (modelID, photoCode) => {
const filter = { id: modelID }
const update = { photoCode: photoCode }
const options = { new: true, useFindAndModify: false }
return new Promise((resolve, reject) => {
ModelModel.findOneAndUpdate(filter, update, options)
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
But I want this to run only if the field Model.photoCode is empty. If it already has a value, I just want it to return that value.
The problem I'm having is that every time the backend hits the route, a new photoCode gets assign to the model even if it has been already assigned. So it gets reassigned again again with a new one.
router.post(
'/payment/success',
(req, res, next) => {
...omitted_code...
photoCodeModel
.assignPhotoCodeToModel(req.body.modelId, photoCode)
... omitted_code
})
Edit 1
I'm writing this after trying the answers provided here. At first glance they seem to work, but now I'm getting into the error that when the Model.photoCode already has a value in it the return of the following function:
const assignPhotoCodeToModel = (modelID, photoCode) => {
//the difference is here
const filter = { id: modelID, photoCode : null }
const update = { $set: { photoCode: photoCode } }
const options = { new: true, useFindAndModify: false }
return new Promise((resolve, reject) => {
ModelModel.findOneAndUpdate(filter, update, options)
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
returns null, while I'm actually expecting the same model document to be returned. I'm guessing is because of the filtering? When I remove the photoCode : null it goes back to "working", but then I'm back to the problem that photoCode field gets populated with new values.
Basically I would like that function populates the filed photoCode once, but every subsequent call should just bring me back the same model document.

I think what you need is $set .
And, following your logic, I think you have to query "document that doesn't have photoCode yet".
It should be:
const assignPhotoCodeToModel = (modelID, photoCode) => {
//the difference is here
const filter = { id: modelID, photoCode : null }
const update = { $set: { photoCode: photoCode } }
const options = { new: true, useFindAndModify: false }
return new Promise((resolve, reject) => {
ModelModel.findOneAndUpdate(filter, update, options)
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}

Write it in a neat asynchronous form. It will work.
const assignPhotoCodeToModel = async (modelID, photoCode) => {
const filter = { id: modelID, photoCode : null };
const update = { photoCode: photoCode };
const options = { new: true, useFindAndModify: false };
return await ModelModel.findOneAndUpdate(filter, update, options);
}

Related

how to unset (delete) field in findByIdAndUpdate in mongoDB

I have an API that update a post and I want when a field is passed as undefined to delete this field or at least set it to undefined.
Here is my current implementation but it's not working:
const finalData = {
_id,
...,
mileage,
};
const result = await provider.updatePost(finalData);
and my provider
updatePost: async (payload) => {
const { _id, ...newData } = payload;
const result = await Model.findByIdAndUpdate(_id, newData, {
new: true,
omitUndefined: true,
});
await indexPost(result);
if (!result) {
throw new NotFound('No post found');
}
return result;
}

node mongoose how to auto increment

Trying to follow the example here:
https://www.tutorialspoint.com/mongodb/mongodb_autoincrement_sequence.htm
export interface RowProps {
id?: number; // This is to auto increment
todoText: string;
}
const addAutoIncrement = async ({ db, collectionName, todoText }) => {
const getNextSequenceValue = (sequenceName: string) => {
const sequenceDocument = db
.collection<RowProps>(collectionName)
.findAndModify({
query: { _id: sequenceName },
update: { $inc: { sequence_value: 1 } },
new: true,
});
console.log('sequenceD', sequenceDocument)
return sequenceDocument.sequence_value;
};
db.collection<RowPropsClient>(collectionName).insertOne(
{
id: getNextSequenceValue('id'),
todoText
},
(err) => {
if (err) {
console.log("err");
}
}
);
}
// db is already defined and works
// I can add to the collection so this also works.
addAutoIncrement({ db, collectionName: 'todos', todoText: 'hello' });
Error: throw new Error('Collection#findAndModify unimplemented by driver');
^
Error: Collection#findAndModify unimplemented by driver
update
Tried to follow this example:
https://medium.com/#salonimalhotra1ind/how-to-increment-a-number-value-in-mongoose-785066ba09d8
const addAutoIncrement = async ({ db, collectionName, todoText }) => {
const modelTodo = db.model(collectionName, TodosSchema);
const res = await new modelTodo({ todoText }).save();
const { _id } = res;
return new Promise((resolve, reject) => {
modelTodo.findOneAndUpdate(
{ _id },
{ $inc: { id: 1 } },
{ new: true },
(err, res) => {
if (err) {
reject(err);
}
resolve(res);
}
);
});
};
**The result is just setting the value to 1 each time - not incrementing**
Collection#findAndModify() is a method that is implemented in the MongoDB shell, but not in the Node.js driver.
You should use Collection#findOneAndUpdate instead:
const { value : sequenceDocument } = db
.collection<RowProps>(collectionName)
.findOneAndUpdate({
{ _id: sequenceName },
{ $inc: { sequence_value: 1 } },
{ returnDocument : 'after' } // equivalent to `new: true`
});
ok I don't know why I didnt do this before. All the online examples make everything unnecessarily complicated.
Just get the total count and then add it.
const addAndIncrement = async ({ db, collection, todoText }) => {
const connectedModel = db.model(collection, TodosSchema);
const documentCount = await connectedModel.count({}); // get the total count
return new connectedModel({ todoText, id: documentCount }).save();
};
Unless anyone comes up with a more performant way, this is what I'm going with.

ReferenceError: Cannot access 'savedPost' before initialization

My POST method which throws the ReferenceError:
postsRouter.post('/:postNumber', async (req, res, next) => {
const postDate = new Date().toUTCString()
const post = new Post({
...req.body,
poster: req.body.poster
? (req.body.poster.includes(config.AP) ? req.body.poster.replace(config.AP, '') : req.body.poster)
: 'nonamer',
date: postDate,
IP: req.socket.remoteAddress,
admin: req.body.poster.includes(config.AP) ? true : false
})
try {
const [savedPost, updatedBubble] = await Promise.all([
/* save() doesn't save the post with the new postnumber which it should (see the pre('save')
function in the next code block), it's only in the post that is being returned while saving. */
post.save().catch(e => { console.log('Task 1 failed!'); next(e) }),
// req.params.postNumber is the postnumber of the bubble a.k.a thread being replied to.
Post.findOneAndUpdate(
{ postNumber: req.params.postNumber },
{ $push: { replies: savedPost.postNumber } })
.catch(e => { console.log('Task 2 failed!'); next(e) })
])
res.status(201).json(savedPost)
console.log('!!!!! ' + req.socket.remoteAddress + ' saved a post: ' + JSON.stringify(savedPost))
} catch (e) {
next(e)
}
})
Also this mess of a code is in my Post model (which utilizes Counter model):
// Increase postnumber in counter collection's sole document, and insert it into new post.
postSchema.pre('save', function(next) {
let post = this
const ccc = Counter.findByIdAndUpdate(
{ '_id': 'postNumCounter' },
{ '$inc': { 'currentPostNum': 1 }}, // Works
{ new: true },
// inserting postnumber not working, fix
function(error, counter) {
post.postNumber = counter.currentPostNum
}
)
// Not working either
post.postNumber = ccc.currentPostNum
next()
})
My goal is I simply want to grab the currentPostNum after incrementing it, and insert that into the post before saving it.
Solved myself. I made the pre('save') function asynchronous as well, and ditched Promise.all in the controller POST method. Now everything works in tandem.
postSchema.pre('save', async function (next) {
let post = this
const counter = await Counter.findByIdAndUpdate(
{ '_id': 'postNumCounter' },
{ '$inc': { 'currentPostNum': 1 } }, // Works
{ new: true }
)
post.postNumber = counter.currentPostNum
next()
})

Updating a nested objects in Mongoose

I have the following express route:
const updateSnapshot = async (req, res) => {
const accountId = req.body.account_id;
if (!accountId) {
return fail(res, 'account id is missing', 400);
}
try {
const account = await Account
.findOne({ _id: accountId})
.populate({
path: 'snapshot',
model: 'Snapshot'
});
// I want to update these fields in snapshot
const snapshot = {
friends_count: data.friends_count,
updated_date: new Date()
};
account.snapshot.friends_count = snapshot.friends_count;
account.snapshot.updated_date = snapshot.updated_date;
await account.save();
return success(res, snapshot);
} catch(error) {
fail(res, error.message, 500);
}
};
I want to update the nested object snapshot (just the fields friends_count and update_date) however when I check the database it seems to have not work. What am I doing wrong here?
const updateSnapshot = (req, res) => {
const accountId = req.body.account_id;
if (!accountId) {
return fail(res, 'account id is missing', 400);
};
Account
.findOneAndUpdate({ _id: accountId }, {
$set: {
snapshot: {
friends_count: data.friends_count,
updated_date: new Date()
}
}
}, {
new: true
})
.then(account => {
if(!account) {
return fail(res, "Account not found", 404);
} else {
return success(res, account);
};
})
.catch(err => {
return fail(res, error.message, 500);
});
};
Here we're using the findOneAndUpdate method and promises to find and update the document in one operation.
findOneAndUpdate takes the query criteria as the first parameter, the second parameter updates values specified in the object (we're using the $set operator to update specific values of the snapshot object), and the three parameter is an object that defines the operation's options (new is set to true to return the newly updated object).
Note: $set will replace the entire snapshot object so if there are other properties inside the snapshot object, they will need to be included inside the $set object.

node mongoose updating an Object in Array doesn't work

I am trying to update an Array of objects wo any success..
the update statement does not work ..
I may have to update the table instance array and update it with the new valueand then save the table...
const picked = table.meta.permissions.find(obj => obj._id == req.params.permissionId);
console.log('picked: %j', picked); // need to update it !
// how can I update this permission object with the new value from req.body ?
table.save()
.then(savedTable => res.json(savedTable))
.catch(e => next(e););
I have an Array of permissions in a 'meta' field:
MODEL
const Schema = mongoose.Schema;
const Permission = new Schema({
permission: {
role_id: { type: String },
canWrite: { type: Boolean }
}
});
const TableSchema = new mongoose.Schema({
meta: {
name: { type: String, required: true },
permissions: [Permission],
...
},
...
);
In the controller , I firstly load the requested table and append it to the req object, then I execute the updatePermission function, and try to update the table instance permission with new values using $set
CONTROLLER
import Table from '../../models/table.model';
/**
* Load table and append to req.
*/
function load(req, res, next, id) {
Table.get(id)
.then((table) => {
req.table = table;
return next();
})
.catch(e => next(e));
}
function updatePermission(req, res, next) {
const table = req.table;
console.log('Current table.meta.permissions: %j', table.meta.permissions, '\n');
console.log('update permission: ', req.params.permissionId, ' with: ', req.body, '\n');
const query = { 'meta.permissions._id': req.params.permissionId }
const update = { $set: { 'meta.permissions.$.permission': req.body } };
const options = { new: true};
table.update(query, update, options)
.then(savedTable => res.json(savedTable))
.catch((e) => { next(e); });
}
The console log displays the current table permissions and the req.params and req.body
Why the update statement doesn't run correctly
thanks for feedback
I found a way to solve this issue but I don't know if it's the best one ?
I update the table object using some() then I save the table..
function updatePermission(req, res, next) {
const table = req.table;
table.meta.permissions.some((obj, idx) => {
if (obj._id == req.params.permissionId) {
table.meta.permissions[idx].permission = req.body;
return true;
}
return false;
});
table.save()
.then(savedTable => res.json(savedTable))
.catch(e => next(e); );
}

Resources