Nodejs mongodb driver Partial update - node.js

I'm learning mongodb, and after spend hours trying to find some information I failed, and that is the reason I'm asking this question here.
I'm wondering how can I update my array of documents in better way if possible.
If I do something like this
const res = await collection.updateOne(
{ name: profile.name, 'links._id': olid },
{
$set: { 'links.$': data, },
},
);
All fields that is undefined in data will remove that field from document.
async updateLink(profile: Profile, link: Link, data: IUpdateLinkDataDTO): Promise<void> {
const database = await this.repo.getDb();
const collection = database.collection('profiles');
const olid = new ObjectId(link.id);
const res = await collection.updateOne(
{ name: profile.name, 'links._id': olid },
{
$set: {
'links.$.label': data.label || link.label,
'links.$.media': data.media || link.media,
'links.$.action': data.action || link.action,
'links.$.hide': data.hide || link.hide,
'links.$.index': data.index || link.index,
},
},
);
console.log(res.modifiedCount);
}
So I found this solution, which is basically check if the field exists on data otherwise I send the "old" information again to preserve the fields.
Is there a better way?
As #prakash-harvani suggested, I know that I can do something like he said or even a loop on Object.entries like
const values: any = {};
Object.entries(data).forEach((entry) => {
if (entry[1] !== undefined) {
values[`links.$.${entry[0]}`] = entry[1];
}
});
But I would like to know, if is there any kind of mongo operator to do that for me.
Mongo has a lot of configuration and operators, and I expected that some operator could take care of this for me, my mongodb versions is 4.4.1.

Yes, there is one another way, try like this.
let updateData = { };
// for label
if(data && data.label && data.label!=undefined){
updateData['links.$.label'] =data.label;
}
// for media
if(data && data.media && data.media!=undefined){
updateData['links.$.media'] =data.media;
}
// for action
if(data && data.action && data.action!=undefined){
updateData['links.$.action'] =data.action;
}
// for hide
if(data && data.hide && data.hide!=undefined){
updateData['links.$.hide'] =data.hide;
}
// index
if(data && data.index && data.index!=undefined){
updateData['links.$.index'] =data.index;
}
const res = await collection.updateOne(
{ name: profile.name, 'links._id': olid },
{
$set: updateData,
},
);
it's a better way to do this type of stuff

Related

GET request always defaults to /(?:)/i - how can i make it 'undefined'?

If i use RegExp then my Search Widget page always gets:
/(?:)/i
And thus always loads with a list of results. I dont want this this to happen. I just want my page to load, then a user fills out the search box and it then does the GET request.
app.get("/la_widget", function(req, res) {
var test = new RegExp(req.query.search, 'i');
console.log(test);
Restaurant.find({
LocalAuthorityName: test
},
null,
{
limit: 50
},
function(err, foundAuthority) {
if (foundAuthority) {
res.render("la_widget", {foundAuthority})
} else {
res.render("la_widget", "No local authority matching that input was found.");
}
});
});
Test if the req.query.search string is defined (thuthey) before setting the search query.
const test = (req.query.search)
? new RegExp(req.query.search, 'i')
: undefined
This uses a ternary operator which equates to:
let test
if (req.query.search) {
test = new RegExp(req.query.search, 'i')
}
else {
test = undefined
}

Does Sequelize findAll [Op.or] return in order?

Let's say I have this very basic search
const from = 'something';
const to = 'somethingElse';
const firstSearchCondition = { from, to: 'test' };
const secondSearchCondition = { from: 'test', to };
const models = await Model.findAll({
where: {
[Op.or]: [
firstSearchCondition,
secondSearchCondition
],
},
});
const [toTest, fromTest] = models;
if (toTest && fromTest) {
// both models exist
}
when both models exist, is toTest 100% from firstSearchCondition? or the order is not guaranteed
Order is not guaranteed unless you use an order by clause. Regardless of the query.
Thanks to #Lev and some more researches on my side, what I've come up as solution is something like this:
hope it helps future people ( if you have any idea to make it better, hit me up )
let toTest = null;
let fromTest = null;
for (let i = 0; i < models.length; i += 1) {
if (models[i].to === 'test') {
toTest = models[i];
} else {
fromTest = models[i];
}
}
// check here if both exist

node express caching issue

I have an app which sends a form schema with the data for page rendering.
The form schema comes from a require call to a configuration - this is in javascript object notation. Depending on the user's permission level, the function massageSchema then for example removes protected fields from the schema, prior to the schema being sent.
This all works. However, if I log out and log in as a different user, the schema relevant to the previous user is sent.
If I stop/start the node instance, the correct schema then gets sent.
So I've deduced that this appears to be a caching issue, but I have no clue as to how to address it.
The router code:
router.get('/:table/:id', authUser, resolveTableName, authMethodTable, function(req, res, next) {
getTableModule(req.params.table)
.then(mod => {
// Massage the schema
mod.formSchema = massageSchema(mod, req.session.user);
...
db.one( sql, [ req.params.table, res.locals.table.idAttribute, req.params.id ])
.then( row => {
res.render("record", {
data: row,
user: req.session.user,
table: req.params.table,
module: mod,
referer: req.get('Referer')
})
....
The massageSchema function:
module.exports = function(mod, user) {
var rv = {};
var orig = mod.formSchema ? mod.formSchema : mod.schema;
// Remove unallowed fields
for(var i in orig) {
if(orig[i].display) {
if(orig[i].display == 'admin' && (user.role == 'admin' || user.role == 'master')) {
rv[i] = orig[i];
} else if(orig[i].display == 'editor' &&
(user.role == 'editor' || user.role == 'admin' || user.role == 'master')) {
rv[i] = orig[i];
}
} else {
rv[i] = orig[i];
}
}
return rv;
};
Why is this happening? What to do?
I'm guessing mod is part of some module.exports?. In JavaScript, objects are always passed by reference, including module.exports mod.formSchema = massageSchema(mod, req.session.user) is actually modifying the module's exported object.
try let schema = massageSchema(mod, req.session.user) instead

How to make Mongoose update work with await?

I'm creating a NodeJS backend where a process reads in data from a source, checks for changes compared to the current data, makes those updates to MongoDB and reports the changes made. Everything works, except I can't get the changes reported, because I can't get the Mongoose update action to await.
The returned array from this function is then displayed by a Koa server. It shows an empty array, and in the server logs, the correct values appear after the server has returned the empty response.
I've digged through Mongoose docs and Stack Overflow questions – quite a few questions about the topic – but with no success. None of the solutions provided seem to help. I've isolated the issue to this part: if I remove the Mongoose part, everything works as expected.
const parseJSON = async xmlData => {
const changes = []
const games = await Game.find({})
const gameObjects = games.map(game => {
return new GameObject(game.name, game.id, game)
})
let jsonObj = require("../sample.json")
Object.keys(jsonObj.items.item).forEach(async item => {
const game = jsonObj.items.item[item]
const gameID = game["#_objectid"]
const rating = game.stats.rating["#_value"]
if (rating === "N/A") return
const gameObject = await gameObjects.find(
game => game.bgg === parseInt(gameID)
)
if (gameObject && gameObject.rating !== parseInt(rating)) {
try {
const updated = await Game.findOneAndUpdate(
{ _id: gameObject.id },
{ rating: rating },
{ new: true }
).exec()
changes.push(
`${updated.name}: ${gameObject.rating} -> ${updated.rating}`
)
} catch (error) {
console.log(error)
}
}
})
return changes
}
Everything works – the changes are found and the database is updated, but the reported changes are returned too late, because the execution doesn't wait for Mongoose.
I've also tried this instead of findOneAndUpdate():
const updated = await Game.findOne()
.where("_id")
.in([gameObject.id])
.exec()
updated.rating = rating
await updated.save()
The same results here: everything else works, but the async doesn't.
As #Puneet Sharma mentioned, you'll have to map instead of forEach to get an array of promises, then await on the promises (using Promise.all for convenience) before returning changes that will then have been populated:
const parseJSON = async xmlData => {
const changes = []
const games = await Game.find({})
const gameObjects = games.map(game => {
return new GameObject(game.name, game.id, game)
})
const jsonObj = require("../sample.json")
const promises = Object.keys(jsonObj.items.item).map(async item => {
const game = jsonObj.items.item[item]
const gameID = game["#_objectid"]
const rating = game.stats.rating["#_value"]
if (rating === "N/A") return
const gameObject = await gameObjects.find(
game => game.bgg === parseInt(gameID)
)
if (gameObject && gameObject.rating !== parseInt(rating)) {
try {
const updated = await Game.findOneAndUpdate(
{ _id: gameObject.id },
{ rating: rating },
{ new: true }
).exec()
changes.push(
`${updated.name}: ${gameObject.rating} -> ${updated.rating}`
)
} catch (error) {
console.log(error)
}
}
})
await Promise.all(promises)
return changes
}
(The diff, for convenience:
9,10c9,10
< let jsonObj = require("../sample.json")
< Object.keys(jsonObj.items.item).forEach(async item => {
---
> const jsonObj = require("../sample.json")
> const promises = Object.keys(jsonObj.items.item).map(async item => {
33a34
> await Promise.all(promises)
)
EDIT: a further refactoring would be to use that array of promises for the change descriptions themselves. Basically changePromises is an array of Promises that resolve to a string or null (if there was no change), so a .filter with the identity function will filter out the falsy values.
This method also has the advantage that changes will be in the same order as the keys were iterated over; with the original code, there's no guarantee of order. That may or may not matter for your use case.
I also flipped the if/elses within the map function to reduce nesting; it's a matter of taste really.
Ps. That await Game.find({}) will be a problem when you have a large collection of games.
const parseJSON = async xmlData => {
const games = await Game.find({});
const gameObjects = games.map(game => new GameObject(game.name, game.id, game));
const jsonGames = require("../sample.json").items.item;
const changePromises = Object.keys(jsonGames).map(async item => {
const game = jsonGames[item];
const gameID = game["#_objectid"];
const rating = game.stats.rating["#_value"];
if (rating === "N/A") {
// Rating from data is N/A, we don't need to update anything.
return null;
}
const gameObject = await gameObjects.find(game => game.bgg === parseInt(gameID));
if (!(gameObject && gameObject.rating !== parseInt(rating))) {
// Game not found or its rating is already correct; no change.
return null;
}
try {
const updated = await Game.findOneAndUpdate(
{ _id: gameObject.id },
{ rating: rating },
{ new: true },
).exec();
return `${updated.name}: ${gameObject.rating} -> ${updated.rating}`;
} catch (error) {
console.log(error);
}
});
// Await for the change promises to resolve, then filter out the `null`s.
return (await Promise.all(changePromises)).filter(c => c);
};

Get placeholder values on firebase onWrite database function

I want to implement a counter function to add up number of likes on a post, here is the code (taken from the firebase counter function example) I have modified it a little, how do I get the placeholder values specificed in the database ref (cid, coid)?
exports.countCommentChange = functions.database.ref('/clipComments/{cid}/{coid}').onWrite(event => {
const db = admin.database();
const clipRef = db.ref(`/clips/${cid}`); // <- how do I get CID?
const countRef = clipRef.child('comments');
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
my database structure is as follows:
clips: {
clipId: {
name: "my awesome clip",
likes: 0,
comments: 0
}
},
clipComments: {
clipId:
{
commentTimestamp: {
comment: "awesome video!"
}
}
}
If You console.log your event from the onWrite listener, You will able to see the whole data stored in this object at the dashboard console.
You should notice a object at the beginning named params. This Object will store all placeholder variables in the exported function.
In Your case, You should be able to access your placeholder with event.params.cid.
I hope it helps!

Resources