NodeJS: How to get objects with variable child properties with MongoDB? - node.js

I am trying to get an array of documents where a child property is true. Here's some code:
public static getTeams(req, res) {
// Initialize a connection to the database
Globals.initDb(res).then((db: Db) => {
// Reference a collection in the Database
db.collection('teams', (error: Error, collection: Collection) => {
// Check if an error occured getting the collection
Globals.checkError({ error, result: collection, res }).then(() => {
// find teams that a user administrates
collection.find(
{ 'usergroups.users': { [req.params.id]: true } },
{ name: 1, icon: 1, type: 1, thumbnail: 1 }
).toArray((err, teams: Array<{ name: string, icon: string }>) => {
// make sure that went through OK
Globals.checkError({ error: err, result: teams, res }).then(() => {
if (!teams[0]) {
teams = [];
}
res.status(200).json(teams);
});
});
});
});
});
}
checkError is just a function I wrote to make it easier to check for errors, and it works fine, I checked it. I am trying to access an array of teams where the usergroups.users.#userId# is true. So if your id was j4k53, usergroups.users.j4k53 is true.
However, when it is true for multiple IDs, the documents are not returned. For example, if j4k53 and lfk3m are both true, it gets returned for neither of them.
Thank you for your help. (This is node with typescript, so it may look a little funky if you are not familiar with TS).

Got it: you can write it like this:
{ ['usergroups.users.' + req.params.id]: true}
Good luck to anyone who finds this useful!

Related

MERN - update specific string in an array's object

I am using mongoose to connect my backend (Express) server to database. I want to do normal CRUD operations - but I am able to do it only for direct data in object, but I need to be able to access also array data.
Example of my model:
const LeewaySchema = new mongoose.Schema({
name: {
type: String,
},
shirt: [
{
name: String,
image: String,
},
],
With the following code I am able to update only name of the object, but I need to be able to update also name in shirt array
Here is working approach when changing name of object:
app.put('/update', async (req, res) => {
const updateName = req.body.updateName;
const id = req.body.id;
console.log(updateName, id);
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.name = updateName;
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
And I tried the same with shirt array, just specifying the correct path
app.put('/update-shirt', async (req, res) => {
const updateShirtName = req.body.updateShirtName;
const id = req.body.id;
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.shirt.name = updateShirtName; // different path here
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
The server crashes and /update-shirt conflicts with /update path
I am using the same route and frontend for READ
useEffect(() => {
axios
.get('http://localhost:8000/read')
.then((response) => {
setListOfClosets(response.data);
})
.catch(() => {
console.log('error');
});
}, []);
And update name function calling with button onClick:
const updateCloset = (id) => {
const updateName = prompt('Enter new data');
axios
.put('http://localhost:8000/update', {
updateName: updateName,
id: id,
})
.then(() => {
setListOfClosets(
listOfClosets.map((val) => {
return val._id === id
? {
_id: id,
name: updateName,
email: val.email,
}
: val;
})
);
});
};
I don't really know how to do update for shirt's name, I tried to copy paste and just change path and url of course, but it did not work.
The question doesn't actually describe what specific transformation (update) you are attempting to apply to the document. Without knowing what you are attempting to do, there is no way for us to help advise on how to do it.
Say, for example, that the document of interest looks like this:
{
_id: 1,
shirt: [
{ name: "first shirt", image: "path to first shirt" },
{ name: "second shirt", image: "path to second shirt" },
{ name: "third shirt", image: "path to third shirt" }
]
}
Also let's say that the application hits the /update-shirt endpoint with an id of 1 and a updateShirtName of "updated shirt name". Which entry in the array is that string supposed to be applied to? Similarly, how would that information be passed to the server for it to construct the appropriate update.
It is absolutely possible to update documents in an array, here is some documentation about that specifically. But the actual structure of the command depends on the logic that you are attempting to provide from the application itself.
The only other thing that comes to mind here is that the motivation for the schema described in the question seems a little unclear. Why is the shirt field defined as an array here? Perhaps it should instead just be an embedded document. If so then the mechanics of updating the field in the subdocument are more straightforward and none of the aforementioned concerns about updating arrays remain relevant.
just make an update api where you just have to pass the id and and pass the shirt in the findByIdAndUpdate query and hit the postman by passing the below code.
shirt: [
{
name: "jhrh",
image: String,
},
],

Can't acces specific values in my node.js mongoose Model (only the Object)

I have been working at this for the past 4 hours. I would therefore like some help. I want to access the specific values in my database, eg. as response.data.values.imglink although when adding imglink in console.log() I get undefined. I can get the general object but not the specifik values.
I have defined my Song Schema as:
const songSchema = new Schema({
values: [{
imglink: {
type: String
},
id: {
type: String
},
spotify: {
type: String,
},
soundCloud: {
type: String,
},
youtube: {
type: String,
},
appleMusic: {
type: String,
}}
],
}, {
timestamps: true,
})
As you can see values is an array of objects. People with a similiar problem on here hadn't included the correct values in their Schema, so maybe that's my problem? Although to me it looks correct. I then GET the values in my database. The JSON object usually looks something like this:
[
{
"_id": "5ffbba4dc47e847a79c9c68f",
"values": [
{
"_id": "5ffbba4dc47e847a79c9c690",
"imglink": "imagelink",
"id": "id",
"soundCloud": "soundcloudvalue",
"youtube": "youtubevalue",
"appleMusic": "applemusicvalue",
"spotify": "spotifyvalue"
}
]
}
]
I call it by this function, which is supposed to print out the individual values:
const getAllSongs = () => {
axios.get('http://localhost:5000/songs/'+id)
.then(function (response) {
console.log(response); // returns an object
console.log(response.data.values.imglink); // returns an object
})
.catch(function (error) {
// handle error
console.log(error);
})
}
I have an Express route object that allows me to access a song by it's id as GET http://localhost:5000/songs/id in the VS-code HTTP client (similiar to postman):
router.get(`/:id`, function(req, res) {
return Song.find(
{"values.id": req.params.id}
).then(function(song) {
// return orders when resolved
res.send(song);
console.log(id);
res.json('works yesss');
})
.catch(function (err) {
// handle error
res.status(400).json('Error: '+err)
})
});
Here are some popular solutions I have tried:
Wrapping response in JSON.stringify() doesn't work.
toObject() and toJSON() don't work either as they aren't defined when I use them.
the _doc hack doesn't work either.
I have tried looking at the Schema which is where I think the problem is. The POST-request adds the right data, the GET-request goes through I just can't acces the specific values.
I hope you have the time to help, thanks. I will be extremely grateful. And of course let me know if you have any questions.
the result of find() is a Array so to access the desired key, if length of result Array is one, to access the desired key is response.data[0].values[0].imglink.
note: the values key is a array of obejct
If the array size is more than one, you want to see the result, you can use map()
if it's not worked, using lean() like this
router.get(`/:id`, function(req, res) {
return Song.find(
{"values.id": req.params.id}
).lean().then(function(song) {
// return orders when resolved
res.send(song);
console.log(song[0].values[0].imglink); // type of song is array of object [] and values is array
res.json('works yesss');
})
.catch(function (err) {
// handle error
res.status(400).json('Error: '+err)
})
});

Using updateOne method to update an object field inside array - throws error "Cannot create field 'url' in element"

I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)

Update a field in an array of subdocuments for ALL records

When I first created my site I didn't know MongoDB did case sensitive searching, among many other things, and I'm trying to fix the problems.
I've already updated the code to alter the user input before it's saved so it is lowercase AND trimmed of extra spaces.
I can easily update a collection of users from the mongo shell, and then I'm good to go there, but I have another collection that needs updating and it looks like this:
Registration Data Object:
{
event: string,
field: string,
users: [string],
players: [
first: string,
last: string,
email: string
]
}
If it's possible to update the players.email field for ALL registrations by trimming it and making it lowercase from the mongo shell, I'd love to do that. But I don't think it is, and I've had trouble doing it from mongoose (using Node.js backend, AngularJS frontend).
I was thinking something like this, but I get Type error: Registration.save is not a function:
module.exports.regUpdateAll = function(req, res) {
console.log('UPDATE ALL REGISTRATIONS');
Registration.find().exec((err, reg) => {
reg.forEach((reg) => {
for(var i = 0; i < reg.players.length; i++) {
reg.players[i].email = reg.players[i].email.toLowerCase().trim();
}
});
console.log(reg);
Registration.save(reg).then((err, response) => {
if(!err) {
res.status(200).send(response);
} else {
console.log(err);
res.status(500).send(err);
}
});
});
};
How can I fix this to work?
You need to call .save() from the object of Registration model. for example
Registration.find().exec((err, regtrations) => {
regtrations.forEach((reg) => {
reg.players =
reg.players.map( p=> {
p.email = p.email.toLowerCase().trim();
return p;
})
console.log(reg);
reg.markModified("players");
reg.save( (error)=> {
//do something
})
})
});
I GOT IT! This took ALL DAY, but I finally got it to work. I could NOT get it to work based on the value of has_signed, but I was able to do it this way:
this.adminService.GetUnsignedWaivers()
.subscribe((data: []) => {
this.players = data;
this.players.forEach((p) => {
p.waivers =
p.waivers.map( w=> {
if(w.signatureUrl.length>0) {
w.url = w.signatureUrl;
w.message = "View Waiver";
}
return w;
});
});
this.size = this.players.length;
console.log(this.players);
});
I just don't understand WHY.

Mongodb/mongoose omit a field in response [duplicate]

I have a NodeJS application with Mongoose ODM(Mongoose 3.3.1). I want to retrieve all fields except 1 from my collection.For Example: I have a collection Product Which have 6 fields,I want to select all except a field "Image" . I used "exclude" method, but got error..
This was my code.
var Query = models.Product.find();
Query.exclude('title Image');
if (req.params.id) {
Query.where('_id', req.params.id);
}
Query.exec(function (err, product) {
if (!err) {
return res.send({ 'statusCode': 200, 'statusText': 'OK', 'data': product });
} else {
return res.send(500);
}
});
But this returns error
Express
500 TypeError: Object #<Query> has no method 'exclude'.........
Also I tried, var Query = models.Product.find().exclude('title','Image'); and var Query = models.Product.find({}).exclude('title','Image'); But getting the same error. How to exclude one/(two) particular fields from a collection in Mongoose.
Use query.select for field selection in the current (3.x) Mongoose builds.
Prefix a field name you want to exclude with a -; so in your case:
Query.select('-Image');
Quick aside: in JavaScript, variables starting with a capital letter should be reserved for constructor functions. So consider renaming Query as query in your code.
I don't know where you read about that .exclude function, because I can't find it in any documentation.
But you can exclude fields by using the second parameter of the find method.
Here is an example from the official documentation:
db.inventory.find( { type: 'food' }, { type:0 } )
This operation returns all documents where the value of the type field is food, but does not include the type field in the output.
Model.findOne({ _id: Your Id}, { password: 0, name: 0 }, function(err, user){
// put your code
});
this code worked in my project. Thanks!! have a nice day.
You could do this
const products = await Product.find().select(['-image'])
I am use this with async await
async (req, res) => {
try {
await User.findById(req.user,'name email',(err, user) => {
if(err || !user){
return res.status(404)
} else {
return res.status(200).json({
user,
});
}
});
} catch (error) {
console.log(error);
}
In the updated version of Mongoose you can use it in this way as below to get selected fields.
user.findById({_id: req.body.id}, 'username phno address').then(response => {
res.status(200).json({
result: true,
details: response
});
}).catch(err => {
res.status(500).json({ result: false });
});
I'm working on a feature. I store a userId array name "collectedUser" than who is collected the project. And I just want to return a field "isCollected" instead of "collectedUsers". So select is not what I want. But I got this solution.
This is after I get projects from database, I add "isCollected".
for (const item of projects) {
item.set("isCollected", item.collectedUsers.includes(userId), {
strict: false,
})
}
And this is in Decorator #Schema
#Schema({
timestamps: true,
toObject: {
virtuals: true,
versionKey: false,
transform: (doc, ret, options): Partial<Project> => {
return {
...ret,
projectManagers: undefined,
projectMembers: undefined,
collectedUsers: undefined
}
}
}
})
Finally in my controller
projects = projects.map(i => i.toObject())
It's a strange tricks that set undefined, but it really work.
Btw I'm using nestjs.
You can do it like this
const products = await Product.find().select({
"image": 0
});
For anyone looking for a way to always omit a field - more like a global option rather than doing so in the query e.g. a password field, using a getter that returns undefined also works
{
password: {
type: String,
required: true,
get: () => undefined,
},
}
NB: Getters must be enabled with option { toObject: { getters:true } }
you can exclude the field from the schema definition
by adding the attribute
excludedField : {
...
select: false,
...
}
whenever you want to add it to your result,
add this to your find()
find().select('+excludedFiled')

Resources