Mongoose build query to update multiple fields - mongodb

I want to create endpoint which will update multiple fields from one collection but not every field and I don't know exact amount of fields.
So for example my user.model contain fields like email, password, name, active. Now I want to create endpoint which will be allowed to change name and password but not email and active. Also it can change both values at once or just one depending on what is sent. So if I pass to it JSON like this:
{ "password":"someNewPass" }
it will update password but won't touch other fields and when I will send JSON like this:
{ "password":"someNewPass", "name":"John Smith" }
then it will update both values but when I send JSON like this
{ "password":"someNewPass", "name":"John Smith", "active":false }
it will not allow the action and give status forbidden or something.
I know I can update multiple fields like this:
await User.findOneAndUpdate(
{ 'resetPasswordKey': req.query.key },
{ $set: { 'password': newPasswordHash, 'name': 'John Smith' } }
)
but how to handle that I don't know amount of fields which will be sent (one or two in above example) and I don't want to let update every field (forbid email and active in above example)?

You can try something like this:
let update = { "password":"someNewPass", "name":"John Smith", "active":false }
if(update.hasOwnProperty('active'))
{
throw 'forbidden';
}
else
{
await User.findOneAndUpdate(
{ 'resetPasswordKey': req.query.key },
{ $set: update }
)
}

Related

how to query for key exists in object field in firestore document (firebase web sdk)

I have subjects collection. In this collection every document has tutors field that is object where key is id of tutors( from tutors collection)
tutors: {
"tutor_id_1": {
"name": "jonas",
"email": "jonas#gmail.com"
},
"tutor_id_2":{
"name": "stephen",
"email": "stephen#gmail.com"
},
"tutor_id_3":{
"name": "maria",
"email":"maria#gmail.com"
}
}
So how to query subjects where tutors field contain tutor id equal to "tutor_id_1" ?
I found one way
if I have two variables in client side
const tutorToFindId = "xxx"
const tutorToFindEmail = "YYY"
query(
collection(db, 'subjects'),
where(`tutors.${tutorToFindId}.email`, '==', `${tutorToFindEmail}`)
),
Is there any other way ??
As I understand, "tutor_id_1" is being used as a unique id. Considering that, you may structure your data model with "tutors" as a subcollection instead of a field and you will be able to get the content of that specific document as follows:
const docRef = db.collection('subjects').doc('subjectX').collection('tutors').doc(tutorToFindId);
const tutor = await docRef.get();
if (!tutor.exists) {
console.log('No such document!');
} else {
console.log('Document data:', tutor.data());
}
db.getBy({ where: { [`tutors.${tutorToFindId}.email`]: tutorToFindEmail } });
Take a look at the getBy function in https://www.npmjs.com/package/firebase-firestore-helper
Disclaimer: I am the creator of this library. It helps to manipulate objects in Firebase Firestore (and adds Cache)
Enjoy!

how can I set the value of objectId to another property different that _id when creating a document?

I'm trying to create an object that looks like this:
const userSettingsSchema = extendSchema(HistorySchema, {
user: //ObjectId here,
key:{type: String},
value:{type: String}
});
this is the post method declared in the router
app.post(
"/api/user/settings/:key",
userSettingsController.create
);
and this is the method "create":
async create(request, response) {
try {
const param = request.params.key;
const body = request.body;
console.log('body', body)
switch (param) {
case 'theme':
var userSettings = await UserSettings.create(body) // user:objecId -> missing...
response.status(201).send(userSettings);
break;
}
} catch (e) {
return response.status(400).send({ msg: e.message });
}
}
I don't know how to assign the value of ObjectId to the user property, because ObjectId is generate when the doc is created, thus, I can not do this: userSettings.user = userSettings._id, because the objectr is already. I only manage to get something like this created:
{
"_id": "60c77565f1ac494e445cccfe",
"key": "theme",
"value": "dark",
}
But it should be:
{
"user": "60c77565f1ac494e445cccfe",
"key": "theme",
"value": "dark",
}
_id is the only mandatory property of a document. It is unique identifier of the document and you cannot remove it.
If you provide document without _id the driver will generate one.
You can generate ObjectId as
let id = mongoose.Types.ObjectId();
and assign it to as many properties as you want.
You can even generate multiple different ObjectIds for different properties of the same document.
Now, you don't really need to assign ObjectId to "user" property. _id will do just fine. In fact it is most likely you don't need user's settings in separate collection and especially as multiple documents with single key-value pair.
You should be good by embedding "settings" property to your "user" collection as a key-value json.

Updating multiple documents based on condition

My Express API has two main entities:
Users
Clients
Each user has a list of clientIds they are allowed to see.
When a new client is added to the system, that clientId needs to be added to all users who are allowed to see newly added clients.
How would I do the update query in Mongoose, considering that I need to add the new clientId to the existing list of clientIds for each user who has canSeeNewClients == true?
User.updateMany(
// Find all users who can see new clients
{"canSeeNewClients": true},
// Add the new clientId to the user's list of clientIds
{"$set":{"listOfClientIds": ??? }}
);
Assuming that your User model look somthing like this:
{
"_id": "123",
"canSeeNewClients": true,
"clientIds": [
"2",
"3"
]
}
then you can add the clientId with an update and $push, you can replace $push with $addToSet to be sure that a clientId won't be added twice:
await Users.updateMany(
{ canSeeNewClients: true },
{ $push: { clientIds: clientId } }
);

Updating current user's email address in Meteor

I'm having a problem updating the current user's email address in Meteor. The problem code is here (it's all client code):
var id = Meteor.userId();
if (firstName) {
Meteor.users.update(
{ _id: id },
{ $set: { "profile.firstName": firstName }}
)};
if (lastName) {
Meteor.users.update(
{ _id: id },
{ $set: { "profile.lastName": lastName }}
)};
var oldEmail = Meteor.user().emails[0].address;
if (newEmail) {
Meteor.users.update(
{ _id: id, 'emails.0.address': oldEmail },
{ $set: { 'emails.0.address': newEmail }}
)};
The first two lines work fine (user can update their first and last names), but the last update fails with the following error showing in the console:
"errorClass {isClientSafe: true, error: 403, reason: "Not permitted. Untrusted code may only update documents by ID."
I don't understand the error, because I AM updating by ID--or at least I think I am.
Also: if I remove the reference to the old email, I get a simple "Update failed. Access denied" error in the console instead of the above-mentioned error.
Is there a way to fix this with client-side code only?
(I realize I'll also need to reset the "verified" key back to false, but that's a different issue I guess)
Do not edit the emails array directly. The Accounts package has utility functions for this use case, see Accounts.addEmail and Accounts.removeEmail.
First you will need to invoke a Method since these functions must be performed on the server.
Client js:
if (newEmail) {
Meteor.call('updateEmail', newEmail, err => {
if (err) console.log(`Error updating email address: {err}`);
});
}
server:
Meteor.methods({
updateEmail(newAddress) {
const userId = this.userId;
if (userId) {
const currentEmail = Meteor.users.findOne(userId).emails[0].address;
Accounts.addEmail(userId, newAddress);
Accounts.removeEmail(userId, currentEmail)
}
return;
}
});
You should also validate in your method that the new email address is a string that has an email address pattern.

Meteor: Publish using users profile properties rather than ID

I'm currently creating an app that will be used by multiple companies.
Each user has the following profile:
username: johnDoe
emails: [{address: "some#email.com", verified: true}],
profile: {
name: "John Doe",
companyId: "1234"
}
I then have a collection (called Companies) of company objects that contain configuration info, templates etc specific to that company.
{
id: "1234",
configuration: {},
templates: []
}
In order to isolate each companies data I want to only publish data that matches the users profile companyId to the companies id.
if (Meteor.isServer) {
// collection to store all customer accounts
Companies = new Mongo.Collection('Companies');
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: Meteor.user().profile.companyId});
})
}
This currently works if I hardcode the clinic Id
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: "1234");
})
But returns an empty cursor with the Meteor.user().profile.companyId.
This means that the issue is either that I'm using the wrong function or more probably, the publish is happening before the user().profile.companyId can run.
Anybody know what I'm doing wrong? and do you have any advice on what to read up about so that I have an understanding of this moving forwards?
Thanks
Try doing an explicit findOne() in your publish function:
// publish collection
Meteor.publish("Company", function () {
var user = Meteor.users.findOne({_id: this.userId});
if(user && user.profile && user.profile.companyId) {
return Companies.find({id: user.profile.companyId});
} else {
console.log(user);
return this.ready();
}
});