How to search by ID User and not by MongoDB Id? - node.js

I'm consuming an API REST with NodeJs with POST, GET, DELETE and PUT verbs.
This is my mongoose Schema
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
"id": {
type: Number,
require: true
},
"email": {
type: String,
require: true
},
"first_name":{
type: String,
require: true
},
"last_name":{
type: String,
require: true
},
"company":{
type: String,
require: true
},
"url":{
type: String,
require: true
},
"text":{
type: String,
require: true
}
});
module.exports = mongoose.model('User', userSchema);
What I'm trying to do is that when I do a GET, I want to search by user ID and not by the ID that MongoDB gives the user.
The endpoint I want to use:
localhost:9000/api/user/[id]
The endpoint I don't want to use:
localhost:9000/api/user/[MongoDb id]
Also I'm trying that when I GET the user, I only receive the parameters that I have in the Mongoose Schema and not the ID that MongoDB gives me
Expecting:
If I search a user, I expect this resources:
{
id: 1,
email: "John#gmail.com",
first_name: "Smith",
last_name: "Smith",
company: "google",
url: "Smith.com",
text: "John Smith"
}
But not this
{
_id: 1nf19tg427gnvn9v
id: 1,
email: "John#gmail.com",
first_name: "Smith",
last_name: "Smith",
company: "google",
url: "Smith.com",
text: "John Smith"
_v: 1
}
This is how I GET the user:
function GetUser(req, res){
const { id } = req.params;
userSchema
.findById(id)
.then((data) => res.json(data))
.catch((error) => res.json({ msg: error }))
};
EDIT 1:
As #Tromgy said, to find by user ID, I changed the GET to this:
function GetUser(req, res){
const { id } = req.params;
userSchema
.find({id})
.then((data) => res.json(data))
.catch((error) => res.json({ msg: error }))
};
Now, to finde only the objects I need, I tried only searching first_name and id with .then((data) => res.json(data.id, data.first_name)) but Postman stays loading and never show anything...

Related

Log a user in and get their profile

I am attempting to log a user in to my DB. When I log the user in, it returns the first userId in the DB and not the user who logged in. I have been struggling with this for a while and really am at a dead end.
This is my POST route to log the user in:
// login
router.post("/login", async (req, res) => {
const user = await User.findOne({
email: req.body.email,
});
const secret = process.env.SECRET;
if (!user) {
return res.status(400).send("the user not found!");
}
if (user && bcrypt.compareSync(req.body.password, user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
secret,
{ expiresIn: "1d" }
);
res.status(200).send({ user: user.email, token: token });
} else {
res.status(400).send("password is wrong!");
}
});
The const user = await User.findOne({ email: req.body.email, }); this returns the wrong user.
When I query the endpoint get a users profile with the userId it gets the right information. So its got nothing to do with the DB.
This is the call in the app.
const handleSubmit = () => {
axios
.post(`${baseURL}users/login`, {
email: email,
passwordHash: password,
})
.then(res => {
console.log('USER ID TOKEN', res.data.token);
setbearerToken(res.data.token);
AsyncStorage.setItem('bearerToken', res.data.token);
const decoded = decode(res.data.token);
setTokenID(decoded.userId);
dispatch(setUser(res.data));
});
};
user.js model
const userSchema = mongoose.Schema({
contactName: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
phone: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
passwordHash: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
token: {
type: String,
},
isAdmin: {
type: Boolean,
default: false
},
clubName: {
type: String,
required: true,
},
clubAddress: {
type: String,
required: true,
},
clubEmail: {
type: String,
required: true,
},
clubPhone: {
type: String,
required: true,
},
clubWebsite: {
type: String,
required: true,
},
clubContact: {
type: String,
required: true,
},
})
Your schema doesn't have a field email to filter on.
const user = await User.findOne({
email: req.body.email,
});
Maybe you try clubEmail field. I reproduced the behavior and it looks like that mongoose ignores the filter if the field does not exist in the Schema an just returns the first document in the collection.
E.g.
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({name: "Superman"}, ...
Returns the user with name "Superman".
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({xname: "Superman"}, ...
But when using xname in the filter document which does not exist in my schema neither in the collection as field the query returns the first document in my test collection (its not Superman).
Also look here similar issue: Model.find Mongoose 6.012 always return all documents even though having filter
Issue reported: https://github.com/Automattic/mongoose/issues/10763
Migration Guide to Mongoose 6:
https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict

Mongoose pre hook findOneAndUpdate to modify document before saving

I am using the mongoose pre hook for findOneAndUpdate. I went through the documentation to understand better it's usage. I would like to update the password field before it saves to DB. However, I am not getting the disired result - nothing gets changed. What would be the right approach for using the findOneAndUpdate pre hook to modify a certain field in the doc?
Actual Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: 'p#ssw0rd',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: 'trUstN0oNe',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Desired Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: '0f359740bd1cda994f8b55330c86d845',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: '3dba7872281dfe3900672545356943ce',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Code:
const UserSchema = new Schema({
id: {
type: String,
required: [true, "'id' value is required"]
},
name: {
type: String,
required: [true, "'name' value is required"]
},
configuration: [ConfigModel.schema]
});
const ConfigSchema = new Schema({
email: {
type: String,
required: [true, "Email is required"]
},
password: {
type: String,
required: [true, "Password is required"]
}
});
UserSchema.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.configuration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
return next();
});
You are missing the .save() document command after changing the information inside the document, because you are only using findOne
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.botconfiguration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
await docToUpdate.save() // <---- this line
You dont need the updateMany() here because the ConfigSchema is nested inside the user collection
in userModel you read configuration from ConfigModel so you have to modify the config model not user model it just read and populate the data from config model.

How to save data in mongodb express by referring another document

I have created the following User schema
const UserSchema = mongoose.Schema(
{
fullName: {
type: String,
required: true,
index: true,
},
department: {
type: mongoose.Schema.Types.ObjectId,
ref: "Department",
required: true,
},
},
});
and my Department schema looks like the following
const DepartmentSchema = mongoose.Schema(
{
name: {
type: String,
unique: true,
required: true,
},
description: {
desc: "Description.",
type: String,
},
},
{
strict: true,
versionKey: false,
timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" },
});
Now I'm able to create departments and all is working well in Postman and also my Angular application. But while creating User it only saves the department ID that I've selected or added in my form. This is the data that I've tried to create
user = {
department: "5f806be7c9a3c02c7c61f9f1"
fullName: "John Joshua"
}
and my create route controller looks like this
exports.create = async (req, res) => {
const newUser = new User({
fullName: req.body.fullName,
department: req.body.department,
});
try {
const errors = validationResult(req); // I've used express validator here
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const savedUser = await newUser.save();
res.json(savedUser); // Here I was expecting to return the new saved user with Department details
} catch (err) {
res.status(500).json({ message: err });
}
};
So my response is all good and working well but department only contains the ID with nothing more.
How can I send a response data with Department details? Any help is appreciated.

Accessing a schema inside a schema using Express Router and MongoDG

I'm trying to create a route where it takes in a parameter for a username and then displays that users information. Only thing is, the username is in the user schema from when the user signs up. The profile schema references the user schema. How do I use the username parameter in the findOne call to display the users profile data?
User schema:
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
Profile schema:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users"
},
name: {
type: String
},
image: {
type: String
},
bio: {
type: String
},
location: {
type: String
},
website: {
type: String
},
social: {
youtube: {
type: String
},
facebook: {
type: String
},
instagram: {
type: String
},
twitter: {
type: String
}
}
});
module.exports = User = mongoose.model("profile", ProfileSchema);
Route:
router.get("/user/:username", (req, res) => {
const errors = {};
Profile.findOne({ user: req.params.user.username })
.populate("user", "username")
.then(profile => {
if (!profile) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile);
})
.catch(err => res.status(404).json(err));
});
Please try this :
router.get("/user/:username", async (req, res) => {
const errors = {};
try {
const profile = await User.aggregate([
{ $match: { username: req.params.username } },
{ $lookup: { from: "profile", localField: "_id", foreignField: "user", as: "userProfile" } },
{ $project: { userProfile: { $arrayElemAt: ["$userProfile", 0] }, username: 1, _id:0 } }
]).exec();
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
} catch (error) {
console.log('Error in retrieving user from DB ::', error);
return res.status(404);
}
})
Try using aggregate, firstly you check-in user table for getting details of a specific username then fetch the profile details as below using lookup, if no profile found after unwind the document will not be fetched and you can check on aggregate result's length as aggregate always return an array in result :
User.aggregate([
{$match:{ username: req.params.user.username }},
{$lookup:{from:"profile",localField:"_id",foreignField:"userId",as:"profileData"}},
{$unwind:"$profileData"},
{$project:{profileData:1,username:1}}
{$limit:1}
])
.then(profile => {
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
})
You can do it in 2 steps.
Look for users containing username in userSchema, get it's id.
Then in promise, use that id to, look for profileSchema contains.
router.get("/user/:username", (req, res) => {
users.findOne({ username: req.params.username }).then(_user=>{
profile.findOne({ user: _user._id }).populate('user').then(_profile => {
res.json(_profile);
})
})
});
This code will look for username in userSchema and look for userSchema's id in profileSchema then returns profileSchema populated with user.

How can I query an Array for a subdocument with mongoose?

I am looking to query a subdocument that contains an array.
You can think of this as an application similar to medium.com or tumblr.com:
I have a user schema, and User's have many Posts. My post schema has a 'tags' key which is an array I am trying to create a route which will display all posts that have a certain tag.
So for instance,
GET /user/posts/:tag
GET /user/posts/dogs
might return back the data for all posts (by any user) that have the tag 'dogs'
{
description: 'dogs are cool',
title: 'huskies',
tags: ['dogs','animals','outdoors'],
original_poster: '34255352426'
},{
description: 'My puppies are nice',
title: 'Puppies',
tags: ['pugs','dogs','small'],
original_poster: '44388342426'
},{
description: 'I like cats more than dogs',
title: 'Cats',
tags: ['kittens','cats','dogs'],
original_poster: '15708213454'
}
Here is my User Schema:
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
PostSchema = require('./post-model.js');
let UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: false, //only required for local users
unique: false,
default: null
},
following: {
type: [Number],
required: false,
unique: false
},
followers: {
type: [Number],
required: false,
unique: false
},
posts: [PostSchema],
})
module.exports = mongoose.model('User',UserSchema);
Here is my Post Schema:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
let PostSchema = new Schema({
description: {
type: String,
required: false,
unique: false
},
title: {
type: String,
required: false,
unique: false
},
tags: {
type: [String],
required: true
},
original_poster: {
type: String,
required: true
}
});
module.exports = PostSchema;
EDIT:
to clarify my question, imagine that this was a regular js object, here is what a function to return all data relevant to a specific tag:
let users = [];
let user1 = {
username: 'bob',
posts: [
{
title: 'dogs r cool',
tags: ['cats','dogs']
},
{
title: 'twd is cool',
tags: ['amc','twd']
}]
};
let user2 = {
username: 'joe',
posts: [{
title: 'mongodb is a database system that can be used with node.js',
tags: ['programming','code']
},
{
title: 'twd is a nice show',
tags: ['zombies','horror-tv','amc']
},
{
title: 'huskies are my favorite dogs',
tags: ['huskies','dogs']
}]
}
users.push(user1);
users.push(user2);
function returnPostByTag(tag) {
let returnedPosts = [];
users.forEach((user,i) => {
user.posts.forEach((post) => {
if(post.tags.includes(tag)) {
let includespost = {}
includespost.title = post.title;
includespost.tags = post.tags;
includespost.original_poster = user.username;
returnedPosts.push(includespost);
}
})
})
return returnedPosts;
}
If you want to see a full repl where I use a plain js example here it is: https://repl.it/repls/LavenderHugeFireant
You can do the following using Mongoose to return all users where the tags array in any of the subdocuments in the posts array includes tag:
User.find({ 'posts.tags': tag }, function(err, users) {
if (err) {
// Handle error
}
let filteredPosts = users
.map(user => {
user.posts.forEach(post => post.original_poster = user.username)
return user
})
.map(user => user.posts)
.reduce((acc, cur) => acc.concat(cur), [])
.filter(post => post.tags.includes(tag))
// Do something with filteredPosts
// ...such as sending in your route response
})
...where User is your Mongoose model and tag contains the tag string you want to query (perhaps from your route request).
If you are using promises then you could do:
User.find({ 'posts.tags': tag })
.then(users => {
let filteredPosts = users
.map(user => {
user.posts.forEach(post => post.original_poster = user.username)
return user
})
.map(user => user.posts)
.reduce((acc, cur) => acc.concat(cur), [])
.filter(post => post.tags.includes(tag))
// Do something with filteredPosts
// ...such as sending in your route response
})
.catch(err => {
// Handle error
})

Resources