I'm running a simple application with mongoDB + nodejs, I'm trying to achieve the following:
The unit belongs to a company, the classroom belongs to a unit and the user belongs to a classroom.
In certain moment, I want to add the user to another unit or/and classroom, then he'll belong to 2 or more units/classrooms.
My form will sent only one unit/classroom per time, in this case, I want to add it to the user model unit:[string] and classroom:[string] only if he doesn't previously belong to it. So I need to check if the arrays already have the sent data, if don't, add to it.
Mongo has the $addToSet property, and the $ne to do it, but I can't seem to make it work.
Here's my code:
User.findById(req.body._id)
.select("-__v")
.exec((err: Error, user: any) => {
if (err) {
// display error
}
if (!user) {
// display error
}
user.update({
unit: {
$ne: user.unit
},
classroom: {
$ne: user.classroom
}
}, {
$addToSet: {
unit: req.body.unit,
classroom: req.body.classroom
}
}).exec((err: Error) => {
if (err) {
// Display error
}
res.status(200).json({
status: "OK",
response: response,
})
return
})
It belongs to "Academy one" and the classroom id reference, I will add him to another unit like "Academy 2" and add another classroom reference, but if I add him to another classroom of "Academy One", I don't want a duplicate item in it's unit array.
When I post the following through postman, it gives me the error:
{
"_id":"5d8ba151248ecb4df8803657", // user id
"unit":"Test", // another unit
"classroom":"5d8a709f44f55e4a785e2c50" // another classroom
}
Response:
{
"status": "NOK",
"response": "Cast to [string] failed for value \"[{\"$ne\":[\"Academy One\"]}]\" at path \"unit\"" }
What am I missing?
Actually, I didn't needed the $ne operator, I just needed to use the $addToSet directly
user.updateOne({
$addToSet: { unit: req.body.unit, classroom: req.body.classroom }
}).exec((err: Error) => {
Thanks!
You need to use $nin instead of $ne, https://docs.mongodb.com/manual/reference/operator/query/nin/
unit: {$nin: [user.unit]}
Related
I'm building REST APIs in node.js using express.js library.
For the embedded data stored in mongodb as json object, I'm trying to get specific data by filtering, thereby using the aggregate match to achieve so.
For the users collection, I've the data as below
[
{
unique_key:"1",
user_form:
{
user_details:
{
first_name:"Tely",
last_name:"Zed"
}
}
},
{
unique_key:"2",
user_form:
{
user_details:
{
first_name:"Rock",
last_name:"Monty"
}
}
}
]
I want to get data of user by searching name. So I'm using aggregate method and then using match operator to achieve it.
User is the name of the model.
For filtering I'm using below aggregate method on User model.
User.aggregate([
{
$match: {
user_form:
{
user_details:
{
first_name: req.body.user_form.user_details.first_name
}
}
}
}
])
.exec(function(err,filteredUsers){
if(filteredUsers) {
console.log(filteredUsers);
res.json({
"msg": "Successfully gets the filtered users",
"data": filteredUsers,
"success": true
});
} if(err){
res.json({
"msg":"Error occured while filtering users",
"error": err ,
"success":false
});
}
})
//Now when I'm posting request from postman with searching with first_name as Rock in postman //body as shown below.
{
user_form:
{
user_details:
{
first_name:"Rock"
}
}
}
//So upon hitting send, I'm getting below response in postman, so I'm not able to get filtered //data instead getting an empty array.
{
"msg": "Successfully gets the filtered users",
"data": [],
"success": true
}
So please tell what should I do to filter data .
Used dot notation to query for subdocument.
db.users.aggregate([
{$match:{"user_form.user_details.first_name":"Rock"}}]).pretty()
Output:
{
"_id" : ObjectId("63c01a42232f174edd983fbf"),
"unique_key" : "2",
"user_form" : {
"user_details" : {
"first_name" : "Rock",
"last_name" : "Monty"
}
}
}
The mistake I did was I kept a space in "first_name" like "first_name " while inserting the document, so that's why I wasn't able to get filtered data by dot notation method also, so later I did found by same query.
In searching through MongoDB data I've been able to query a collection based on whatever fields the user provides by adding any non-null field to a query object, which so far has covered all of my bases. But I've run into an issue with needing to add to it. Basically doing this for 10+ fields:
let query = {};
let weekNumber = null;
if (req.params.weekNumber !== 'noDataSent'){
weekNumber = req.params.weekNumber;
query.weekNumber = weekNumber;
}
...
Ticket.find(query)...
I've got two collections, User and Ticket. The Ticket objects contain an objectId ref to a User object. Since I can't query the Tickets by a users name due to the ref, I'm finding all users based on a provided name and then pulling out their _id to then add to the query object. In this case, if nothing else is specified I would like the result to only return the tickets that match any of the user _ids, and if there are other fields to work the same, to match everything else and find the tickets with the user ids.
I've tried using $or and $in, but unless I'm doing something wrong, I can't figure out how to make this work right.
Doing this returns an error, and I'm not even sure how to go about using $or in this case.
let users = [];
User.find( { $or: [ { firstname: requestor }, { lastname: requestor } ] } ).exec((err, user) => {
if (err){ return res.status.json({err: "No user(s) found"}); }
user.forEach((e) => {
users.push(new ObjectId(e._id));
});
});
Ticket.find(query, { requestor: { $in: users } } )
How can I match all of the fields in my query object and find all tickets with the user(s) _ids with this setup?
Edit:
I've tried doing this to get it to search through both, and this does search through the users correctly and brings back the tickets tied to them, it is ignoring everything in the query object.
Ticket.find({
$and:
[
{ query },
{requestor:
{ $in: users.map(doc => new ObjectId(doc._id))
}
}
]
})
You are running Ticket.find() outside of the callback of User.find(). While this is valid code, both of these queries are effectively queued up to go concurrently, with the the Ticket query utilizing the array of users - which the Users query has yet to populate.
In order to use the result of the Users query, you must put the next bit of code in that callback which can only be called after the query is complete:
User.find({
$or: [
{ firstname: requestor },
{ lastname: requestor }
]
}).exec((err, users) => {
if (err) return res.status.json({ err: "No user(s) found" });
Ticket.find({
requestor: {
$in: users.map(doc => new ObjectId(doc._id))
}
}).exec((err, tickets) => {
if (err) return res.status.json({ err: "No ticket(s) found" });
// do something with 'tickets'
tickets.forEach(doc => console.log(doc));
});
});
To solve my original question of how to search through the tickets using the query object I built up as well as use $in to find the tickets associated with the returned users, I came up with this solution that seems to work nicely.
query.requestor = { $in: users.map(doc => new ObjectId(doc._id)) };
Set the query. to the field I want to search through, in this case the requestor. That way I can simply do what I've been doing thus far and do a plain Ticket.find(query)
Currently I am using Apollo/GraphQL/Node.js/Sequelize to build my backend server, and my server code looked like below, in there I can use req.user to get the current login user
app.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => ({
schema,
context: {
models,
user: req.user,
},
})),
);
Now I have two models User and Recipe, and the association rule is Recipe belongs to User, so in the Recipe schema I can use the UserId to know which user create this schema, the Recipe schema is
type Recipe {
id: Int!
authorName: String!
authorFbPage: String #virtual
perfumeName: String!
message: String
UserId: Int
}
type Query {
allRecipe: [Recipe]
meRecipe: [Recipe]
AvailableWatchRecipe: [Recipe]
}
My problem is in the meRecipe part, this Query supposed to be able to show the recipes created by login user, the resolver code is
meRecipe: async (parent, args, { models, user }) => {
if (user) {
console.log(user.id);
console.log(user.username);
return models.Recipe.find({ where: { UserId: user.id } })
.then((result) => { return result });
}
return null;
},
You can see I also use the console.log to check whether I can get the current user information, it actually can, so I am really confused why when I run this Query in the GraphQL server, it always shows "message": "Expected Iterable, but did not find one for field Query.meRecipe.
I have checked these resources:
https://github.com/brysgo/graphql-bookshelf/issues/10
and
GraphQL Expected Iterable, but did not find one for field xxx.yyy
but none of them fit my case, can anyone give me some advice, thanks!
Instead of using :
models.Recipe.find
Use
models.Recipe.findAll // this will return single result in array
I'm trying to develop a Node.js backend using express and mongoose.
Over the network there's plenty of examples of how to implement a proper authentication layer, but I couldn't find any example of how to correctly implement an authorization layer.
In my specific case, I'm creating the backend of a multi-user application and I want that every user can only see the data inserted by himself/herself.
I have three models:
User
Category
Document
User owns one or many Categories, Categories contain zero or more Documents.
The CRUD operations are implemented on the following endpoints:
/user/:userid
/user/:userid/category
/user/:userid/category/:categoryid
/user/:userid/category/:categoryid/document
/user/:userid/category/:categoryid/document/:documentid
In the authentication part I set to each request the current logged in user id, so I can check easily that
jsonwebtoken.userId == req.params.userid
And return a 403 error otherwise.
Checking the ownership of categories is quite easy, because each category contains a reference to the user who created them.
var CategorySchema = mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true
}
});
In the Document model, however, I only have a reference to the category it belongs, but I didn't add a reference to the user.
I'm wondering therefore how to proceed with "nested" relationships. Do I need to add a user_id reference to all of them at any depth level? Is there any best practice?
Moreover, is this the right way to do what I need or is there any official/mature library that already does the same?
Well no-sql database gives you the power of embedding your sub documents(or equivalent table in relational db) in to the single document. So you may consider redesigning your schema to something like
{
userId:"",
categories": [
{
"categoryId": "",
"name": "",
"documents": [
{
"documentId": "",
},
{
"documentId": "",
},
]
},
{
"categoryId": "",
"name": "",
"documents": [
{
"documentId": "",
},
{
"documentId": "",
},
]
}
]
}
This may help you optimize the number of db query but the important thing to note here is that if the number of categories and documents per user and per category repectively could grow very large then this approach would not be good.
Always remember 6 important thumb rules for mongo db schema design
Favor embedding unless there is a compelling reason not to
Needing to access an object on its own is a compelling reason not to embed it
Arrays should not grow without bound. If there are more than a couple of hundred documents on the “many” side, don’t embed them; if there are more than a few thousand documents on the “many” side, don’t use an array of ObjectID references. High-cardinality arrays are a compelling reason not to embed.
Don’t be afraid of application-level joins
Consider the write/read ratio when denormalizing. A field that will mostly be read and only seldom updated is a good candidate for denormalization.
You want to structure your data to match the ways that your application queries and updates it.
Taken from here
After some tinkering, I ended up with the following middleware.
It basically checks for route parameters in the expected order and checks for coherent memberships.
Not sure if it's the best way of achieving this, but it works:
var Category = require('../category/Category'),
Document = require('../document/Document'),
unauthorizedMessage = 'You are not authorized to perform this operation.',
errorAuthorizationMessage = 'Something went wrong while validating authorizations.',
notFoundMessage = ' not found.';
var isValidMongoId = function (id) {
if (id.match(/^[0-9a-fA-F]{24}$/)) {
return true;
}
return false;
}
var verifyPermissions = function (req, res, next) {
if (req.userId) {
if (req.params.userid && isValidMongoId(req.params.userid)) {
if (req.userId != req.params.userid) {
return res.status(403).send({error: 403, message: unauthorizedMessage});
}
if (req.params.categoryid && isValidMongoId(req.params.userid)) {
Category.findOne({_id: req.params.categoryid, user_id: req.params.userid}, function(err, category){
if (err) {
return res.status(500).send({error: 500, message: errorAuthorizationMessage})
}
if (!category) {
return res.status(404).send({error: 404, message: 'Category' + notFoundMessage});
}
if (req.params.documentid && isValidMongoId(req.params.documentid)) {
Document.findOne({_id: req.params.documentid, category_id: req.params.categoryid}, function(err, document){
if (err) {
return res.status(500).send({error: 500, message: errorAuthorizationMessage})
}
if (!document) {
return res.status(404).send({error: 404, message: 'Document' + notFoundMessage});
}
});
}
});
}
}
next();
} else {
return res.status(403).send({error: 403, message: unauthorizedMessage});
}
};
module.exports = verifyPermissions;
I have the following structure. I would like to prevent pushing in the document with the same attribute.
E.g. Basically, i find the user object first. If i have another vid (with is already inside), it will not get pushed in. Try using $addToSet, but failed.
I am using Mongoose.
This is my Model Structure:
var User = mongoose.model('User', {
oauthID: Number,
name: String,
username: String,
email: String,
location: String,
birthday: String,
joindate: Date,
pvideos: Array
});
This is my code for pushing into Mongo
exports.pinkvideo = function(req, res) {
var vid = req.body.vid;
var oauthid = req.body.oauthid;
var User = require('../models/user.js');
var user = User.findOne({
oauthID: oauthid
}, function(err, obj) {
if (!err && obj != null) {
obj.pvideos.push({
vid: vid
});
obj.save(function(err) {
res.json({
status: 'success'
});
});
}
});
};
You want the .update() method rather than retrieving the document and using .save() after making your changes.
This not only gives you access to the $addToSet operator that was mentioned, and it's intent is to avoid duplicates in arrays it is a lot more efficient as you are only sending your changes to the database rather than the whole document back and forth:
User.update(
{ oauthID: oauthid },
{ "$addToSet": { "pVideos": vid } },
function( err, numAffected ) {
// check error
res.json({ status: "success" })
}
)
The only possible problem there is it does depend on what you are actually pushing onto the array and expecting it to be unique. So if your array already looked like this:
[ { "name": "A", "value": 1 } ]
And you sent and update with an array element like this:
{ "name": "A", "value": 2 }
Then that document would not be considered to exist purely on the value of "A" in "name" and would add an additional document rather than just replace the existing document.
So you need to be careful about what your intent is, and if this is the sort of logic you are looking for then you would need to find the document and test the existing array entries for the conditions that you want.
But for basic scenarios where you simply don't want to add a clear duplicate then $addToSet as shown is what you want.