Chaining promise to update a reference document in Mongoose - node.js

Given the following schema:
user
{ uid:12345, name:myname, account=null }
account
{ _id:6789, name:"myaccount", _owner:12345 }
How can I update the user.account to have the value of its referenced field account._owner. When the account document is created I want to find and replace the user.account value. The route I have looks like this:
app.post('/accounts', authenticate, (req, res) => {
var account = new Account({
name: req.body.name,
_owner: req.body._owner,
});
account.save().then((doc) => {
//here i wasnt to update a refernce to a
// an account field in a User document and set
//it to the account.owner created above.
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
In my example when the account is created
I want to update user.account to 6789 (the value of the created account id)

Mongoose handles promises : http://mongoosejs.com/docs/promises.html
So you can simply :
app.post('/accounts', authenticate, (req, res) => {
var account = new Account({
name: req.body.name,
_owner: req.body._owner,
});
account.save()
.then((doc) => User.findOneAndUpdate(
{ uid: req.body._owner },
{ $set: { account: doc._id } },
{ new: true }
)
.then(() => doc);
}).then((account) => {
res.send(account);
}, (e) => {
res.status(400).send(e);
});
});

Another solution would be to attach a hook to the save action of account model
var Owner = require('path/to/owner/model');
var schema = new Schema({name:String,_owner:{type: Schema.Types.ObjectId,ref: 'owner'}}); // ref will be useful if you want to use populate later
schema.post('save', function(account) {
return Owner.findOne({uid:account._owner})
.then(owner => {
owner.account = account._id; // assign account id to user
return owner.save();
})
});
Then you just have to create a new account object and the hook will do it in the background.
app.post('/accounts', authenticate, (req, res) => {
var account = new Account({
name: req.body.name,
_owner: req.body._owner,
});
account.save().then((doc) => {
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
IMO, the routes look cleaner this way, you could try it out.

Related

MongoDB - addToSet not adding element to array

"mongoose": "^5.12.2"
I have a schema named User. This schema has a field named "rol" of type string[] for a multiple rol application (User, Admin, Freetour, BarOwner, etc).
The function that adds a rol to a user is defined like this:
public addRolToUser = (idUser:string, newRol:string):Promise<IUser> => {
try{
return new Promise<IUser>((resolve, reject) => {
User.findByIdAndUpdate(idUser, { addToSet: {rol:newRol} }, {new:true}).then(user => {
return resolve(user);
}).catch(err => {
return reject(err);
});
});
}catch (e) {
throw e;
}
};
However this doesn´t update the "rol" field of the user. The following function should add the rol "FreeTour" to the user with the id returned by "petition.user".
public acceptPetition = async(req:Request, res:Response) => {
try{
return this.solFreeTourService.acceptPetition(req.body.idPetition).then(petition => {
let acceptPromise = new Promise((resolve, reject) => {
// Here I´m invoking the addRolToUser function
return this.userService.addRolToUser(petition.user, "FREETOUR").then((resUser)=>{
// resUser here has the same value for the "rol" field, didn´t get updated.
return resolve(petition);
}).catch(err=>{
return reject(err);
})
})
return acceptPromise.then(petition=>{
return res.status(200).json({petition});
}).catch(e=>{
res.status(400).json({ status: 400, message: "There has been an error." });
});
})
}catch (e) {
res.status(400).json({ status: 400, message: "There has been an error." });
}
}
I don't want repeated values in the "rol" array, hence pushing is not an option.
What am I doing wrong?
first of all, Welcome to StackOverflow! 👋
I have to assume that you might have something not working well together as you say you're using Mongoose and for such I've made a very simple project that you can look into in GitHub
where I create a very simple schema
const UserSchema = mongoose.Schema({
role: [{
type: String
}],
guid: {
type: String,
required: true
},
});
and then make use of the Mongoose API to create, update and find the user
const guid = uuidv4();
// create user
await UserModel.create({ guid });
log('user created');
["admin", "user", "admin"].forEach(async (role) => {
// add role to user
await UserModel.updateOne({ guid }, { $addToSet: { role } });
log(`user role updated with ${role}`);
});
// read user
const newUser = await UserModel.where({ guid }).findOne();
log(JSON.stringify(newUser, null, 2));
and the output is the expected one
user created
user role updated with admin
user role updated with user
user role updated with admin
{
"role": [
"admin",
"user"
],
"_id": "60a2397b1c488d4968d6ed46",
"guid": "26ccacbf-ddbc-4cbf-ac69-2da3235e156b",
"__v": 0
}
fell free to look into the source code, clone, run, and test, and notice that I'm using in fact the Mongo command as $addToSet

findOneAndDelete Mongoose not working MERN stack

GoodDay Experts,
I've tried following code but it did not work, and it gives me null value.. maybe my routes are wrong but basically it works the way on other routes... and here is my backend for delete case: manage.js/actions
export const removeRecipient = (payload) => async (dispatch) => {
try {
const res = await axios.delete(
`${_config.MAT_URL}/api/1/customer/delete`,
payload
);
dispatch({
type: DELETE_CUSTOMER,
payload: res.data,
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { err },
});
}
};
and for my routes which is the mongoose query for findOneAndDelete, under customer.js :
router.delete("/delete", (req, res) => {
Customer.findOneAndDelete({ _id: req.params.id }, (err, Customer) => {
if (!err) {
res.json({ msg: "customer deleted", deleted: Customer });
} else {
console.log("Error removing :" + err);
}
});
});
And for the front end im using "AiOutlineDelete" which was coded as :
const handleDelete = (id) => {
console.log('delete')
removeRecipient(id)
}
<a
id={`delete-${rowIndex}`}
className="anchor-action-delete"
href="#foo"
onClick={(e) => {
e.preventDefault();
handleDelete(row);
}}>
thanks have a great day
There are 2 problems in your code:
req.params.id is meant for urls of the form /delete/:id which is obviously not your route, you should change it to req.query.id instead which matches query parameters in the url such as /delete?id=123.
The default type of _id is ObjectId, under the assumption you did not change this you need to cast your req.query.id which is type string to ObjectId.
It looks like you're using mongoose so here's mongoose syntax:
const mongoose = require("mongoose");
router.delete("/delete", (req, res) => {
Customer.findOneAndDelete({ _id: new mongoose.Types.ObjectId(req.query.id) }, (err, Customer) => {
if (!err) {
res.json({ msg: "customer deleted", deleted: Customer });
} else {
console.log("Error removing :" + err);
}
});
});
For nodejs native Mongo package:
import {ObjectId} from "mongodb";
...
new ObjectId(req.query.id)
I dont see you sent the id to the backend but you are trying to retrieve it from req.params.id try passing the id like "delete/:id" at the end of the link and specify this in the routes aswell.
if that doesnt fix try the below code this for routes
if nothing works check this, In the component you need to send the id(object id) but i see "row" what is the value of row? if the row value is not the id in the database then it wont delete. if this your issue try inspecting the code by keeping breakpoints or write a console.log() to check the value of "row" .
try {
const removedProject = await Customer.remove({
_id: req.params.id
})
res.json(removedProject)
} catch (err) {
res.json({
message: err
})
}

array of objects won't POST into mongodb

I have an array of objects that is defined in mongoose schema as
blacklistGroup: {
userId: { type: String },
username: { type: String }
}
I can't figure out why it won't POST into mongodb.
I have a console.log that shows that it represents it's schema, but it never appears in mongodb? What am I doing wrong?
console.output
req.body.blacklistGroup
[ { userId: '5e2350c7f88cfb331c4f67de', username: 'artist1' },
{ userId: '5e20c5a139a92512cc7df63c', username: 'artist' } ]
[object Object]
app.js
app.post("/api/listings", checkAuth, (req, res, next) => {
console.log("req.body.blacklistGroup");
console.log(req.body.blacklistGroup);
let blacklistGroup = req.body.blacklistGroup;
console.log("req.body.blacklistGroup");
const post = new Post({
blacklistGroup: req.body.blacklistGroup,
});
//saves to database with mongoose
post.save().then(result => {
console.log(result);
res.status(201).json({
message: "Auction listing created successfully!",
postId: result._id
});
});
});
You can store all user at once. use mongoose insertMany
const Post = require('post'); //mongoose schema
app.post("/api/listings", checkAuth,(req, res, next) => {
console.log("req.body.blacklistGroup");
console.log(req.body.blacklistGroup);
let blacklistGroup = req.body.blacklistGroup;
console.log("req.body.blacklistGroup");
const blacklistGroup = req.body.blacklistGroup;
(async function(){
await Post.insertMany(blacklistGroup);
res.status(200).send('Ok');
})();
});
Or you can use
const Post = require('post'); //mongoose schema
app.post("/api/listings", checkAuth,async (req, res, next) => {
console.log("req.body.blacklistGroup");
console.log(req.body.blacklistGroup);
let blacklistGroup = req.body.blacklistGroup;
console.log("req.body.blacklistGroup");
const blacklistGroup = req.body.blacklistGroup;
await Post.insertMany(blacklistGroup);
res.status(200).send('Ok');
});
For More Here
You don't have an array of objects (or at least you don't want one), you have an object with two properties; userId and username. MongoDB is expecting JSON and it looks like you're trying to send it an array containing that object.
Try this:
let blacklistGroup = req.body.blacklistGroup[0];
To process an array of objects passed as req.body.blacklistGroup, you will have to iterate over it, define a new Post for each object and then send it. I think part of the confusion here is that your Schema is called blacklistGroup but it doesn't refer to a group, it refers to one entry.
const dbCalls = blacklistGroup.map(userObject => {
const post = new Post({
blacklistGroup: {
userId: userObject.userId,
username: userObject.username
});
return new Promise((resolve, reject) => {
post.save.then(() => {
resolve();
})
.catch(err => {
reject(err);
})
})
});
Promise.all(dbCalls).then(() => {
res.status(201).json({message: "Auction listings created successfully!"})
})
.catch(err => {
res.status(500).json({error: err})
});

MongoDB/Mongoose search and create of specific sub documents

I'm building a REST API in nodejs/express/mongodb/mongoose. I've started using MongoDB/Mongoose recently and I still have some doubts.
What I'm trying to achieve is to access a specific user bag (a user can have multiple bags) and also I want to be able to add to that bags participants/payers. (a user bag can have multiple participants/payers)
My mongoose user modal contains the rest of the schemas. I created a schema for each one because I believe it would be easier to find a given bag or participant directly because of the ObjectId (not sure if this is correct).
Mongoose Modal/Schemas:
const PayerSchema = new Schema({
name: {
type: String
},
amount: {
type: Number
}
});
const BagSchema = new Schema({
name: {
type: String
},
type: {
type: String
},
payers: [PayerSchema]
});
const UserSchema = new Schema({
name: {
type: String,
required: [true, 'User name field is required']
},
bags: [BagSchema]
});
module.exports = mongoose.model('User', UserSchema);
I was able to create the CRUD controller methods for a new user, but I still not sure on:
Creating a new bag for a specific user (I was able to do this but not sure if it's the right way)
Creating a new participant in a specific bag for a specific user. (addPayer method is wrong need help here)
Check out my controller user/bags/participants methods:
const User = require('../models/userModel');
getAllUserBags: (req, res, next) => {
User.findById({ _id: req.params.id }).then((user) => {
res.send(user.bags);
})
.catch(next);
},
getOneUserBag: (req, res, next) => {
console.log(req.params.bagid);
User.find({ 'bags._id': req.params.bagid}, {"bags.$" : 1}).then((obj) => {
res.send(obj);
})
.catch(next);
},
createBag: (req, res, next) => {
let bag = req.body.bag;
User.findOneAndUpdate(
{_id: req.body.id},
{$push: {bags: bag}
}).then(() => {
//Unnecessary - just to return update user with new bag.
User.findOne({_id: req.body.id}).then((user) => {
res.send(user);
})
}).catch(next);
},
addPayer: (req, res, next) => {
let payer = req.body.payer;
User.find(
{'bags._id': req.params.bagid},
{"bags.$" : 1},
{$push: {payers: payer}
}).then((obj) => {
console.log(obj);
//Unnecessary - just to return update user with new bag.
// User.findOne({_id: req.body.id}).then((user) => {
// res.send(user);
// })
}).catch(next);
}
Thanks for the help
Base on what we discuss, your User schema is good enough for your requirements, as long as making sure that one User document does not exceed the 16MB limit of MongoDB document.
Creating a new bag for a specific user (I was able to do this but not sure if it's the right way)
Yours is fine. However, there are some improvements:
createBag: (req, res, next) => {
User.findByIdAndUpdate(req.body.id, {
$push: { bags: req.body.bag }
}, {
new: true // this will make the query getting the updated document
})
.then(user => {
res.json(user);
})
.catch(next);
})
Creating a new participant in a specific bag for a specific user. (addPayer method is wrong need help here)
Since you decided to nest the 'bags', the bag.id might be duplicated among User documents. See this to understand the possibility. Thus, I recommend using an userId along with bagId:
getOneUserBag: (req, res, next) => {
User.findOne({
_id: req.params.userId,
bags._id: req.params.bagId
})
.then(user => {
if (!user) res.status(404).end();
let bag = user.bags.id(req.params.bagId);
res.json(bag);
})
.catch(next);
}
addPayer: (req, res, next) => {
User.findOneAndUpdate({
_id: req.params.userId,
bags: $elemMatch: {
_id: req.params.bagId
}
}, {
$push: { 'bags.$.payers': req.body.payer } // Use 'positional $' operator along with $elemMatch in the query to update only the matched bag
}, {
new: true // Do not forget the 'new' options to get the updated document
})
.then(user => {
if (!user) res.status(404).end();
res.json(user);
})
.catch(next);
}
and in the router
router.get('/users/:userId/bags/:bagId', getOneUserBag);
router.post('/users/:userId/bags/:bagId/payers', addPayer);
In the getAllUserBags(), you use the wrong syntax for User.findById():
getAllUserBags: (req, res, next) => {
User.findById(req.params.id) // Not { _id: req.params.id }
.then((user) => {
res.json(user.bags);
})
.catch(next);
}

How to link a collection with user?

I am working on MEAN application(Angular2+). I want to maintain a seprate data for each user. As of now the data are like anyone can view any of the details but i want like, If i login and enter details, only I can view those details. Basically I want to link user collection with other collection. Since I am new to Mongo, I have no idea about this.
user.ts
import * as mongoose from 'mongoose'
const userSchema = new mongoose.Schema({
username: String,
email: { type: String, unique: true, lowercase: true, trim: true },
password: String,
role: String
});
cat.ts
import * as mongoose from 'mongoose';
const catSchema = new mongoose.Schema({
name : String,
height: String,
weight: String,
});
I have no idea what is this
base.ts
abstract class BaseCtrl {
abstract model: any;
// Get all
getAll = (req, res) => {
this.model.find({}, (err, docs) => {
if (err) { return console.error(err); }
res.json(docs);
});
}
// Count all
count = (req, res) => {
this.model.count((err, count) => {
if (err) { return console.error(err); }
res.json(count);
});
}
// Insert
insert = (req, res) => {
const obj = new this.model(req.body);
obj.save((err, item) => {
// 11000 is the code for duplicate key error
if (err && err.code === 11000) {
res.sendStatus(400);
}
if (err) {
return console.error(err);
}
res.status(200).json(item);
});
}
// Get by id
get = (req, res) => {
this.model.findOne({ _id: 'req.params.id '}, (err, obj) => {
if (err) { return console.error(err); }
res.json(obj);
});
}
// Update by id
update = (req, res) => {
this.model.findOneAndUpdate({ _id: req.params.id }, req.body, (err) => {
if (err) { return console.error(err); }
res.sendStatus(200);
});
}
// Delete by id
delete = (req, res) => {
this.model.findOneAndRemove({ _id: req.params.id }, (err) => {
if (err) { return console.error(err); }
res.sendStatus(200);
});
}
}
export default BaseCtrl;
Reference project : https://github.com/DavideViolante/Angular-Full-Stack
You need to add user_id field to catSchema. The user_id will be the reference for the user who add/save that cat.
cat.ts
import * as mongoose from 'mongoose';
const catSchema = new mongoose.Schema({
name : String,
height: String,
weight: String,
user_id: String, // Or may be ObjectId
});
And you need to query the user from users collection to retrieve the data everytime.
Or you can use DBRef of mongodb. To implement this in mongoose you can follow this link.
Basically your cat model will be
cat.ts
import * as mongoose from 'mongoose';
const catSchema = new mongoose.Schema({
name : String,
height: String,
weight: String,
user: { type: mongoose.Schema.ObjectId, ref: 'User' }
});
To insert the cat, get the userid from login details/sessions and add to collection.
For you, you need to add the 'user' field to req.body as in base controller you are creating model from req.body.
If you don't want to add to req.body, you can override the insert method for cat controller and manually create the cat model with userid.
controller/cat.ts
import Cat from '../models/cat';
import BaseCtrl from './base';
export default class CatCtrl extends BaseCtrl {
model = Cat;
insert = (req, res) => {
const data = {
title: req.body.title,
height: req.body.height,
weight: req.body.weight,
user: getUser() // Get the logged in userid
};
const obj = new this.model(data);
obj.save((err, item) => {
if (err && err.code === 11000) {
res.sendStatus(400);
}
if (err) {
return console.error(err);
}
res.status(200).json(item);
})
}
}
Just like above, you can modify and filter all your documents according to user.
Edit:
To make it simple, send the current logged in user in the form itself. cats.component.ts
addCat() {
this.addCatForm.value.userid = this.authService.currentUser._id; // You might need to use callback here
... // All other code
}
Revert the cat controller to as it was before.

Resources