How to findAll by a specific variable in MongoDB? - javascript

I have the following MongoDB collection of bets:
{
id: String,
user_id: String,
type: String,
events: [{
eventID: String,
sport: String,
evento: String,
aposta: String,
estado: String,
odd: String
}],
total_odd: String,
bet_ammount: String,
state: String,
date: String
}
My goal is to send an HTTP request into /bet and reply with all the bets in my Database that correspond to the given "user_id" that is sent in the initial request.
For example, if there were 3 bets that had the user_id with "61e0eb548714ee662c2d76d1", I would want to to send all 3 bets in the body of the reply. I've had a similar request where I would only need to send a specific bet according to it's ID. The code for that is the following:
router.get('/bet', (req, res, next) => {
let token = req.headers.token;
jwt.verify(token, 'secretkey', (error, decoded) =>{
if (error) return res.status(401).json({
title: 'Unauthorized'
})
BetModel.findOne({ user_id: decoded.userId }, (error, bet) => {
if (error) return console.log(error)
return res.status(200).json({
title: 'Bets Grabbed',
bet:{
_id: bet._id,
events: bet.events,
total_odd: bet.total_odd,
bet_amount: bet.bet_amount,
state: bet.state,
date: bet.date,
user_id: bet.user_id
}
})
})
})
});
How could I change this to initially get every bet for that specific user_id and then send them all in the HTTP reply?

You can create another route that changes the findOne query to find, and sends the result to the HTTP reply.
BetModel.find({ user_id: decoded.userId }, (error, bets) => {
if (error) return console.log(error)
return res.status(200).json({
title: 'Bets Grabbed',
bets: bets
})
})
You could then iterate through each of the bets on the frontend.

Related

How can I delete an array entry in MongoDB with a delete API?

I am building an application that manages time sensitive tasks. I have APIs to create tasks and retrieve tasks, and I'm currently trying to get my delete API to work.
An employee has an array in MongoDB that contains todo tasks with text and I'm trying to delete the task by ID in SoapUI. With my current code, the delete request just sort of times out after a minute or so.
Here's my current delete API
router.delete("/:empId/tasks/:id", async (req, res) => {
try {
Employee.findByIdAndDelete(
{
empId: req.params.empId,
},
{
$pull: {
todo: {
_id: req.params.id,
},
},
}
);
} catch (e) {
console.log(e);
res.status(500).send({
message: "Internal server error: " + e.message,
});
}
});
Current schema
let itemSchema = new Schema({
text: { type: String },
_id: { type: String },
});
task.service
deleteTask(empId: number, task: string, _id: number): Observable<any> {
return this.http.delete('/api/employees/' + empId + '/tasks/' + _id);
}
and an example of an employee document in MongoDB
{"_id":{"$oid":"61797a51d15ad09b88d167af"},"empId":"1012", "firstName":"web","lastName":"developer","__v":1,"done":[],"todo":[{"_id":"1","text":"test"}]}
When you use findByIDAndDelete, it deletes that entire document. What you need is to update that particular document using update()/findOneAndUpdate()/findByIdAndUpdate(). Also you should either await/use a callback to return the value, like,
collection.findOneAndUpdate({_id:docId },{
$pull: {"todo":{"_id":yourId} },
},
function(error, data){
if(error){ return error}
else {return data}
})

Node JS preventin users from deleting other user's products

I have a REST API built with Node JS and I'm currently using MongoDB as my database. I want to prevent the users from deleting another user's products and for this I checked if the userId from the decoded token is the same as the product userId.
Product schema
const mongoose = require("mongoose");
const productSchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
userId: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
price: { type: Number, required: true },
productImage: { type: String, required: false },
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true
},
gender: { type: String, required: true }
},
{ timestamps: { createdAt: "created_at" } }
);
module.exports = mongoose.model("Product", productSchema);
The delete product method:
const id = req.params.productId;
Product.findById({ _id: id }).then((product) => {
if (product.userId != req.user._id) {
return res.status(401).json("Not authorized");
} else {
Product.deleteOne({ _id: id })
.exec()
.then(() => {
return res.status(200).json({
message: "Product deleted succesfully",
});
})
.catch((err) => {
console.log(err);
return res.status(500).json({
error: err,
});
});
}
});
};
As you guys see first I'm searching executing the findByID method to access the userId property of the product, then I'm comparing the userId from the response with the userId from the decoded token.
I don't think my method is very efficient since it's running both findById and deleteOne methods.
Can you help me with finding a better solution for this?
as Guy Incognito mentioned, what you are trying to do is an OK thing and you may want to keep it this way in case you want to send a 404 status stating the product they are trying to remove does not exist.
however, if you are trying to do it with only one request
Product.deleteOne({ _id: id, userId: req.user._id })
hope it helps!

REST API: PUT but no need all data in json

This is my controller to update put user data. The controller accepts up to 4 values. I would like to do so if I send only a name to this route, This will change only the name and the rest will remain unchanged. (it will not be empty). Is it possible to do this? Do I have to do it in redux-saga, i.e. if it is empty, give it up-to-date
// Update basic User's Data
exports.setUserdata = (req, res) => {
const id = req.params.userId;
User.update(
{
password: req.body.password,
name: req.body.name,
surname: req.body.surname,
email: req.body.email,
},
{ where: { id } },
)
.then(() => {
res.status(200).json({ success: true });
})
.catch(() => {
res.status(500).json({ error: 'Internal server error' });
});
};
Pass params you want to update and don't pass other keys.
If req.body contains only name key, you can just pick up those 4 keys from req.body.
const updateParams = _.pick(req.body, ['name', 'password', 'surname', 'email'])
User.update(updateParams, { where: { id } })
If req.body has other properties with value null or undefined, you can filter them after picking.
const updateParams = _.chain(req.body).pick(['name', 'password', 'surname', 'email']).filter().value()
User.update(updateParams, { where: { id } })
Of course it depends on the ORM you are using but I believe most ORM don't update attributes which are not passed at all.

MongoDB: remove skill from user document

So I have an issue. I have a MongoDB user document and this document has a property skills, which is an array filled with objects.
What I want to do now is the following. The client sends a request to delete one of these skills. So I send the skill ID and then I want to remove the skill from the user. How could I do this?
What I currently have: every item in the skills property array has a skill property which is an objectID. When this objectID matches the objectId that the client sent, then we want to remove this skill from the user
const removeSkill = async (req, res) => {
try {
const { userId, params: { skillId } } = req;
const user = await User.findByIdAndUpdate({ _id: userId }, {
$pull: { "skills.skill": skillId }
}, {
new: true
});
return res.status(200).json({
message: 'succesfully removed skill',
user
});
} catch (err) {
return sendErr(res, err);
}
};
What the user mongodb document looks like
The error I get
:no_entry: Error:
MongoError: Cannot use the part (skill) of (skills.skill) to traverse the element ({skills: [ { _id: ObjectId('5c8729be12e1cc05c04ea182'), name: "javascript", skill: ObjectId('5c8729be
12e1cc05c04ea181'), points: 22 }, { _id: ObjectId('5c8729dc12e1cc05c04ea184'), name: "node.js", skill: ObjectId('5c8729dc12e1cc05c04ea183'), points: 14 }, { _id: ObjectId('5c872a6c12e1c
c05c04ea186'), name: "css", skill: ObjectId('5c872a6c12e
First of all you shouldn't be using 2 Object ID in one object. The beauty of MongoDB is auto generated ObjectID. For your question (using skill ObjectID)
const user = await User.findByIdAndUpdate(
{ _id: userId },
{ $pull: { "skills": { skill: skillId }}},
{ new: true}
);
I believe u are receiving ObjectID from your params. If so,Something like this should help.

Populate subdocument in parent document Array Mongoose

I'm very new to JavaScript and Mongoose. I'm building a small project using express, mongoose and node.js.
I have a mongoose model - Client that has an Array of Transactions
var Client = mongoose.model('Client', {
name: {
type: String,
required: true,
minlength: 1
},
email: {
type: String
},
phone: {
type: Number
},
createdAt: {
type: Number,
default: null
},
transactions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' }],
_creator: {
type: mongoose.Schema.Types.ObjectId,
required: true
}
});
module.exports = {Client};
This is Transaction model:
var Client = require('./client');
var Transaction = mongoose.model('Transaction',{
_creator : { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
amount : {type: Number, min: 0},
date : {type: Number,default: null},
total: {type: Number,default: null}
});
module.exports = {Transaction};
When I POST a new Transaction it goes through and saves in db:
app.post('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
var transaction = new Transaction({
amount: req.body.amount,
date: new Date().getTime(),
total: req.body.total,
_creator: req.params.id
})
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
}, (e) => {
res.status(400).send(e);
});
});
I am also able to GET all the transactions associated with the client:
app.get('/clients/:id/transactions', authenticate, (req, res) => {
var id = req.params.id;
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
Transaction.find({
_creator: id
}).then((transactions) => {
res.send({transactions});
}).catch((e) => {
res.status(400).send();
});
});
But when I make a GET call to '/clients' - Array of Transactions is empty:
{
"clients": [
{
"_id": "1095d6de3867001108b803",
"name": "Peter",
"email": "peter#gmail.com",
"phone": 1232321,
"_creator": "5321df6d57868ec7001108b801",
"__v": 0,
"transactions": [],
"createdAt": null
} ]
}
And this is the GET call to /clients
app.get('/clients', authenticate, (req, res) => {
Client.find({
_creator: req.user._id,
})
.populate('transactions.transaction')
.then((clients) => {
res.send({clients});
}, (e) => {
res.status(400).send(e);
console.log('Unable to get clients', e);
})
});
I know that I'm likely doing something completely wrong but I don't know where I need to look for my mistake. Please help!
I would check if the client exist before adding a transaction. A transaction needs a client first.
Forewarn, I'm not a fan of then and catch so this answer does not use it. I normally use async.js when dealing with multiple asynchronous operations.
Anyways, I would do it like
app.post('/clients/:id/transactions', authenticate, (req, res) => {
Client.findOne({ _id: req.params.id }, (err, client) => {
if (err)
return res.status(400).send(err);
if (!client)
return res.status(400).send(new Error('No client'));
Transaction.create({
amount: req.body.amount,
date: new Date(), // I don't think you need .getTime()
total: req.body.total,
_creator: client._id
}, (err, transaction) => {
if (err)
return res.status(400).send(err);
client.transactions.push(transaction._id);
client.save(err => {
if (err)
return res.status(400).send(err);
res.json(transaction);
});
});
});
});
Good idea to also turn on debugging mode to see your queries: mongoose.set('debug', true).
You might also find using timestamps option for Transaction schema more useful than explicitly using date field
To get clients with their transactions
app.get('/clients', authenticate, (req, res) => {
Client.find({ _creator: req.user._id }).populate('transactions').exec((err, clients) => {
if (err)
return res.status(400).send(err);
res.json(clients);
});
});
so first of all i don't exactly know what _creator key in Client model representing, it's probably user identifier who has some clients, but if I'm wrong please correct me.
Honestly I don't know why you are making two way document connection, (keeping client in transactions, and also keeping transactions in clients) in my opinion first option is better for mongodb and using that you can easily get transaction's list with find, or mongodb aggregation, but you can't get data using populate.
In second option you need to remember that one document could have maximum 16MB. And also keeping thousands of transactions in one array is not well for performance. Think about example that you have 5000 transaction and you want to show list with pagination (50 records per page), with array option you have to get whole document, and splice array to 50 records. In first option you could use mongodb skip and limit. Please think about it.
Returning to question, mistake you are doing is here:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
transactions: req.body.transaction
});
res.send(doc);
Here you don't exactly say how this document should have to updated about.
Mongoose in method findOneAndUpdate using mongodb findAndModify method. But params are used from update.
https://docs.mongodb.com/manual/reference/method/db.collection.update/
And also documentation says that you what params like:
Query#findOneAndUpdate([query], [doc], [options], [options.passRawResult], [options.strict], [callback])
So first query param is mongo query to find one document in database, next param is object with updating query, and after that you could send some additional options in third param. So your code should looks like this:
transaction.save().then((doc) => {
Client.findOneAndUpdate({
_id: id,
_creator: req.user._id,
}, {
$addToSet: {
transactions: doc._id,
}
});
res.send(doc);
You could use addToSet or push both are putting element into array, but addToSet avoiding duplicates in array. And as you se we push new transaction identifier into this array. And after all you only populate transaction key.
I hope I helped. If you have any questions please ask.

Categories