Mongoose: Part of array - arrays

I have this MongoDB Notification Model:
User: Mongo ID
Created: Date
Content: {}
Subscriptions: [ {user: Mongo Id, read: Boolean} ]
Using Mongoose in NodeJS I create a query that return all the documents where my user is inside the Subscriptions array.
Notification
.find({subscriptions:{$elemMatch: {user: req.user}}})
.sort('-created')
.exec(function (err, notifications) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(notifications);
}
});
This query works, and returns the documents that I wanted... But I don't want exactly this... This code returns, in the documents returned, all subscriptions array, including other users. If there are 10.000 users inside the subscriptions array, all the users are returned in all documents.
How can I extract a part of the array? I want that only my user is returned inside the subscriptions array. Why? for security and performance reasons.
I tried this query:
.find({subscriptions:{$elemMatch: {user: req.user}}}, {'subscriptions.$': 1})
This query only returns one user in the subscriptions array (my user). But... Is not returned any field more except the subscriptions array...
Any suggestion?

You can include other fields in your result like this:
.find({ 'subscriptions': { '$elemMatch': { 'user': req.user }}},
{ 'subscriptions.$': 1, 'content': 1, 'created': 1, 'user': 1 }
)

Related

How do I sort a list of users by name on a node server?

I have created several user accounts on mongodb and i want to sort them out by user name. I compare the user names in the database against a string provided through aaxios request with a body value that is taken from an input value, like this:
frontend
const findUsers = async () => {
try {
const response = await axios.post(`http://localhost:8080/search-users/${_id}`, { searchValue });
setReturnedUser(response.data.matchedUser);
} catch (error) {
console.log(error);
}
}
findUsers();
backend
exports.sort = (req, res) => {
let result;
User.find({ name: req.body.searchValue }).exec((error, users) => {
if (error) {
return res.status(400).json({
message: error,
});
}
result = users;
res.status(200).json({
message: 'Description added successfully',
matchedUser: result,
});
});
};
The problem with this approach is that the users are returned only after I type in the entire name.
What I want is that the users to get returned as I type in the name, so several matching users will het returned when I start typing and as I continue the list will narrow down, until only the matching user remains.
I have successfully achieved this on the react side, but that was possible only by fetching all the users from the database, which would be a very bad idea with a lot of users. The obvious solution is to do the sorting on the server.
Filtering on the client-side is possible but with some tweaks to your architecture:
Create an end-point in node that returns all the users as JSON. Add caching onto this end-point. A call to origin would only occur very infrequently. YOu can then filter the list easily.
Use something like GraphQL and Appollo within node. This will help performance
To do the filtering in node you can use a normal array.filter()
I woul do the filter in mongo as the quick approach and then change it if you notice performance issues. It is better no to do pre-optimisation. As Mongo is NoSQL it wshould be quick

query multiple entities in Google Datastore with a clean syntax

I'm using node.js with Google App Engine for a class project. My code works fine so I'm not asking how to solve this, but this is ugly beyond reason and I'm assuming there has to be a better way for making this look clean. That isn't part of my grade, just want to know the better way. Also less round trips is always desired so
I have built a basic forum backend but part of creating a forum thread is I need to check that the thread has a unique name and that the user exists. Plan on adding authentication later, but for now just querying the name.
Is there a way to use a single query to get 2 entities by key if they are of different 'kinds'? Nesting queries like this seems bad all around. Not finding a lot of documentation on this subject, or my google-fu is a bit weak. Do something like this in a couple places so would really like to improve on this in particular.
Basic code
router.post('/', function (req, res) {
if (req.body["Name"] == null || typeof req.body["Name"] !== "string")
{
res.json({ success: false, data: "Name was not a valid string." });
return;
}
if (req.body["Creator"] == null || typeof req.body["Creator"] !== "string")
{
res.json({ success: false, data: "Invalid creator submitted for thread creation." });
return;
}
//See if thread is unique
var query = datastore.createQuery('Thread')
.filter('__key__', '=', datastore.key(['Thread', req.body["Name"]]));
datastore.runQuery(query, function (err, entities, nextQuery) {
//
if (err == null && entities.length == 0) {
var query2 = datastore.createQuery('User')
.filter('__key__', '=', datastore.key(['User', req.body["Creator"]]));
datastore.runQuery(query2, function (err2, entities2, nextQuery) {
if (err2 == null && entities2.length >= 1)
{
var threadKey =
{
name: req.body["Name"],
kind: "Thread",
path: ["Thread", req.body["Name"]]
}
var threadData =
{
Creator: req.body["Creator"],
DateCreated: new Date(),
LastUpdated: new Date()
}
datastore.upsert({
key: threadKey,
data: threadData
}, function (err) {
if (err) {
res.json({ success: false, data: "Was unable to add value to datastore for unknown reason." });
return;
}
else {
res.json({ success: true, data: "Was able to add thread to datastore." });
return;
}
});
}
else
{
res.json({ success: false, data: "Cannot create thread because a valid user was not submitted." });
return;
}
});
}
else {
res.json({ success: false, data: "Cannot create thread because a matching name already exists." });
return;
}
});
});
Since queries are on a Kind, you cannot run a single query across Kinds. However, if one Kind is an ancestor of the other, then a single query can get both. For example, if creator were a property of Thread, as the User who created it, then a query on the thread can also contain properties of the creator.
More at: https://cloud.google.com/datastore/docs/concepts/entities#ancestor_paths

Posting Schema.Types.ObjectId arrays to MongoDB

How can I post an array of Schema.Types.ObjectId (s) to MongoDB? I'm trying to create User Groups, which is a group of the 'User' Model e.g.
var UserGroup = new Schema({
users: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
});
New UserGroup Function
module.exports.create = function(request, response) {
var group = new UserGroup({
users = request.body.users
});
group.save(function(error) {
if(error) { throw error; } else { response.send('Group Created Successfully.');
});
};
I'm currently using Postman to test the functionality, how exactly should the data be posted?
As a Javascript array i.e ['A_USER_ID', 'A_USER_ID'] ?
Thanks!
#Answer
I was using the older syntax of the select() function, and therefore was passing invalid parameters to the $push function. When sending the request, I simply pass the ObjectIds as id,id,id and once they get to the server, simply put it into an array using var my_array = request.body.users.split(','); and then push it to the database using the following:
$push: { users: { $each: my_array } }
I hope this was helpful, the documentation isn't particularly clear on this matter.

How to limit/filter a query of messages to just the most recent message [duplicate]

This question already has an answer here:
Mongodb get last combination in aggregation framework
(1 answer)
Closed 8 years ago.
I'm building a message inbox and it's pretty much all set! On the list of all messages, it literally has every message. I'd like this to just be a list of conversations. So instead of showing 10 messages from Person A, it only shows the most recent message from Person A. It'd also have to be an OR condition where the receiver is either person A OR person B. The same could be true of the sender. The sender is either person A OR person B.
<div ng-repeat="message in messages.messages" ng-class="{unread: !message.read}">
<h5>{{message.sender}}</h5>
{{message.message.message}}
<i>Sent {{timeAgo(message.sent)}}</i>
</div>
Here's the current backend to get every message instead of every conversation.
db.collection('messages', function (err, collection) {
collection.find({
receiver: req.user._id
}).sort({
date: -1
}).toArray(function (err, docs) {
if (err) {
console.log('Error getting messages', err);
throw err;
}
if (docs) {
console.log('Found some messages');
console.log(docs);
res.send({
messages: docs
});
} else {
console.log('Didnt find messages');
res.send({
messages: []
});
}
})
});
Limiting is straight-forward, use limit (docs). The callback is not required since limit modifies the cursor returned by find (just like sort):
collection.find({...}).sort({...}).limit(1).toArray(...);
The OR logic you're seeking is provided by the $or query operator (docs). $ is a special character within MongoDB: it always prefixes an operator (query or update), except in the case where it is used as an array positional operator as projection. Anyways, what you're seeking is:
{
$or: [
{receiver: req.user._id},
{sender: req.user_id}
]
}
The $or logical operator (as well as $and, and $nor, but not $not) takes an array of objects that you would normally treat as a simple query: i.e. something like $or:[{...},{...},{...}].
Now to put it all together:
db.collection('messages', function (err, collection) {
collection.find({
$or: [
{receiver: req.user._id},
{sender: req.user_id}
]
}).sort({
date: -1
}).limit(1).toArray(function (err, docs) {
if (err) {
console.log('Error getting messages', err);
throw err;
}
if (docs) {
console.log('Found some messages');
console.log(docs);
res.send({
messages: docs
});
} else {
console.log('Didnt find messages');
res.send({
messages: []
});
}
})
});
Alternatively, find (docs) can accept options for sort and limit instead of using the function chaining syntax:
collection.find({
$or: [
{receiver: req.user._id},
{sender: req.user_id}
]
},{
sort:{
date: -1
},
limit: 1
}).toArray(...)

NodeJS create json object from array

I'm working with some arrays in node and I want to send if as one JSON object to the front-end. I use express to do this. I have a model called User where I find users based on their email. That email is provided in an array. I do get the user object but I can't create one JSON object out of them!
I have tried some middleware but that didn't give me any result! https://www.npmjs.com/package/node.extend
var users = {};
for (var i = 0; i <emails.length; i++) {
User.findOne({
'email': project.students[i]
}, function (err, user) {
if (err) {
res.send(err);
}
// Fill the users object with each user found based on the email
});
}
console.log(users); // Should be one JSONObject
Thanks for the help!
You should be able to do this in a single query. It looks like you're using mongoose, so try something like this:
User.find({ email: { $in: emails } }, function(err, results) {
if (err) return res.send(err);
res.send(results);
});
It's also worth noting that javascript is single threaded. This means that a lot of operations happen asynchronously, meaning you have to wait for the operation to get done before you can move on. Your console logging statement above doesn't wait for the database operation to complete. You have to wait for the callback function to execute.
UPDATE: Also just noticed that you are looping over emails but then using project.students[i] within each iteration. I can't see the rest of your code, but this is just buggy code. You should be either looping over project.students or using emails[i] within each iteration.
UPDATE 2: It appears that you are wanting to send more than just an array of user with the response. So the first goal is to use a single query using the $in operator (see example above - you should be able to pass a list of emails to mongoose). Anything mongo-related, you always want to reduce the amount of queries to the database if you care at all about performance. The second task is to reformat your users and other data accordingly:
var finalResponse = { token: "12341234", users: null };
User.find({ email: { $in: emails } }, function(err, results) {
if (err) return res.send(err);
if (!results.length) return res.send(finalResponse);
// OPTION 1: Array of users (recommended)
finalResponse.users = results;
// OPTION 2: users object, keyed by the users email
finalResponse.users = {};
results.forEach(function(user) {
finalResponse.users[user.email] = user;
});
// FINALLY, send the response
resp.send(finalResponse);
});

Resources