Why is this returning false? (MongoDB) - javascript

I can't figure out why data is returning false here. I have the console log statement and it's telling me there isn't data. I'm searching for a document with 'userId' which is the id of the person sending the message/command which is also in every document to differentiate. Any help would be greatly appreciated.
module.exports.addItem = async (userId, item) => {
profileSchema.findOne(userId, async(err, data)=> {
console.log('There is data: ' + !!data)
if(data) {
const hasItem = Object.keys(data.items).includes(item)
console.log('Has item: ' + !!hasItem)
if(!hasItem) {
data.items[item] = 1
}
else {
data.items[item]++
}
await profileSchema.findOneAndUpdate(userId, data)
}

I'm not sure about your data model or what libraries are you using, but I think the issue is profileSchema.findOne(userId, async(err, data)=> {...}).
You should use an actual query as the first parameter, e.g. profileSchema.findOne({userId: userId}, async(err, data)=> {...}) (or, using the shorthand version, profileSchema.findOne({ userId }, async(err, data)=> {...}))

Related

Sequelize update via PATCH, how to process each possible result

I'm creating a rest api for CRUD operations using Sequelize and MySql. I'm using a controller to run an update on a PATCH request to update fields of a product. It technically works, but I feel like there is a more elegant way to handle this.
Sequelize's update method will return an array of objects depending on the results. Array[0] is the number of rows affected by the update (should just be one in my case, as I'm updating by id). Array[1] will return an object with details about the update as well as all the old values and new values. Here's how I'm handling that currently:
//products.controller.js
//Update a single product using id (PUT/PATCH)
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
//Item not found
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({
success: false,
status: 404, //Not found
message: `Product with id ${id} not found. Update failed.`,
});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
success: true,
status: 200,
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
// if rowsAffected[0] !== 1 then it failed.
res.status(200).send({
success: false,
status: 200, //Not Modified
message: `No fields have changed. Product not updated.`,
});
}
})
.catch((err) => {
res.status(500).send({
success: false,
status: 500,
message:
err.message || "Something went wrong while updating the product.",
});
});
}
As you can see, first I'm checking to see if the the update function returns the product details (meaning it successfully found it in the database). If not then sending 404. Then I check the affected rows. If 1 then success, if 0 then nothing changed. Finally I'm catching any server errors.
I feel like there is a better way rather than having to break down the update function's return (like Object.entries(rowsAffected[1]).length === 0)
This is ok if this is the only way you can check the effects of the update. What I can suggest is putting an abstraction above it.
First thing that checking (rowsAffected[0] === 1) does not make much sense, since the update is idempotent and you end up with the same resource state no matter what the actual values are. If you insist, then I would not pair success: false with a 200 ok status, because failure is failure and it requires an error message and 4xx or 5xx status. So either delete it or convert it into a proper error. Hard to find such a status code, but maybe using 409 conflict is ok in these cases https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 though I would just remove this part of the code. I keep it for the sake of the example.
As of the success and status properties in the body, they don't make much sense either, because they travel in the header, and it is evident from the HTTP standard that 2xx means success, 4xx and 5xx means error. So I would remove those too.
If you don't want to support detailed error codes and exception types and parameters, then just send the error messages and the body can be even a string instead of an object.
Sending the err.message to the consumers is a bad idea by unexpected errors. You don't know what you send out. You need to log them and send something general instead. Communicating errors is always a higher abstraction level stuff, many times. As of the Product with id ${id} not found. Update failed. here adding the id is not necessary, because the request contains it.
So atm. the code looks like this:
const patch = (req, res) => {
const id = req.params.id;
Product.update(req.body, { where: { id }, individualHooks: true })
.then((rowsAffected) => {
if (Object.entries(rowsAffected[1]).length === 0) {
res.status(404).send({message: `Product not found. Update failed.`});
return;
}
//if rowsAffected[0] === 1 then success
if (rowsAffected[0] === 1) { //row changed
res.status(200).send({
message: `Product updated.`,
id: id,
payload: req.body,
});
} else {
res.status(409).send({message: "No fields have changed. Product not updated."});
}
})
.catch((err) => {
res.status(500).send({message: "Something went wrong while updating the product."});
});
}
We can go further by mapping status codes to status messages and extracting the possibly repeating parts of the story into separate functions.
const patch = (req, res) => {
const id = req.params.id;
const statusMessages = {
200: "Product updated."
404: "Product not found. Update failed."
409: "No fields have changed. Product not updated.",
500: "Something went wrong while updating the product."
};
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
function successHandler(res, statusMessages, callback){
return function (){
let body = callback();
body.message = statusMessages[200];
res.status(200).send(body);
};
}
function apiErrorHandler(res, statusMessages){
return function (err){
let statusCode = 500;
if (err instanceof NotFoundError)
statusCode = 404;
else if (err instanceof NotUpdatedError)
statusCode = 409;
res.status(statusCode).send({
message: statusMessages[statusCode]
});
};
}
function updateStatusVerification(rowsAffected){
return new Promise((resolve, reject) => {
if (Object.entries(rowsAffected[1]).length === 0)
reject(new NotFoundError);
else if (rowsAffected[0] !== 1)
reject(new NotUpdatedError);
else
resolve();
});
}
class ApiError extends Error {}
class NotFoundError extends ApiError {}
class NotUpdatedError extends ApiError {}
We can move the status messages to the documentation. So you will end up with something like this and some utility functions:
const patch = (req, res) => {
const id = req.params.id;
statusMessages = docs.product.update.statusMessages;
Product.update(req.body, { where: { id }, individualHooks: true })
.then(updateStatusVerification)
.then(successHandler(res, statusMessages, () => {
return {
id: id,
payload: req.body,
};
}))
.catch(apiErrorHandler(res, statusMessages));
}
We can go even further if this is a frequent pattern:
const patch = (req, res) => {
const id = req.params.id;
handleUpdate(
Product.update(req.body, { where: { id }, individualHooks: true }),
() => {id: id, payload: req.body},
docs.product.update.statusMessages
);
}
function handleUpdate(dbUpdatePromise, successCallback, statusMessages){
dbUpdatePromise.then(updateStatusVerification)
.then(successHandler(res, statusMessages, successCallback))
.catch(apiErrorHandler(res, statusMessages));
}
So it can be as abstract as you like, it really depends on your needs and what the current usage allows. You can decide how many and what kind of layers you need based on actual use cases and repetitions.

Cannot Execute SQL Query in a .map()

I have an endpoint that receives an array in the req.body. I need to fetch that array and for each element of that array, i need to execute the SQL Update Query. This is the code:
const approveShifts = (req, res) => {
try {
const { guard_id } = req.params;
const { shiftIDs, isBooked } = req.body;
shiftIDs.map((shift_id) => {
connection.query(
`UPDATE shift SET isBooked=${isBooked}, fk_guard=${guard_id} WHERE shiftID=${shift_id}`,
(err, rows) => {
if (!err) {
res.status(200).json({
success: true,
message: `Successfully Approved Shift #${shift_id} for Guard #${guard_id}`,
});
} else {
res.status(404).json({
success: false,
message: "Shift Not Found!",
});
}
}
);
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message,
});
}
};
This is my req.body:
{
"shiftIDs": [64],
"isBooked": 1
}
The issue is, no matter what kind of testing i do, the only output i get is "Shift Not Found!" from the else statement of the query. Nothing else happens. I can't get it to work. Can someone guide me ?
A couple of things here- firstly I recommend you use prepared statements instead of string templates for your query:
// (assuming this is mysql client?)
connection.query('UPDATE shift SET isBooked = ?, fk_guard = ? WHERE shiftID = ?', [isBooked, guard_id, shift_id], (err, rows, fields) => {...})
// if it's the mysql2 client, use connection.execute() instead of connection.query()
This works by replacing each ? with the value in the array, in order. This will help avoid SQL injection problems.
Secondly, you can do this in 1 query instead of mapping by using the IN SQL operator because you are setting the same value for isBooked and fk_guard for every shiftID:
// assuming shiftIDs is an array
connection.query('UPDATE shift SET isBooked = ?, fk_guard = ? WHERE shiftID IN (?)', [isBooked, guard_id, shiftIDs], (err, rows, fields) => {...});
And as someone else said, you should console.log(err) right before res.status(404) to see what the error is. And by the way, if the shift doesn't exist, no rows will be updated but no error will be thrown either, so your response wouldn't be 404.

Res value is null in an app.get call done from vue.js front-end to express back-end

I am calling this code from the front-end and confirmed that there is a proper db connection and that the Id value is properly passed, and that there is a corresponding value in the database, but for some reason, res is null. What am I missing?
app.get("/api/walletlogin/user/:userId", (req, res) => {
id = req.params.userId
var query = {_id: id}
db.collection("Users").findOne(query, (err, result) => {
if (result) {
console.log(result.userName)
} else {
console.log('No User')
}
})
Here is the front-end call:
axios.get('/api/walletlogin/user/' + accounts)
.then((response) => {
console.log('Logged in With ' + accounts)
router.push('/account')
})
.catch((errors) => {
console.log('Cannot log in')
})
}).catch((err) => {
console.log(err, 'err!!')
})
You could try to convert your id to an objectID.
var ObjectId = require('mongodb').ObjectId;
var id = ObjectId(req.params.userId);
to search by id, you must use the ObjectID class from the mongodb package. Here is an example invented by me, it does not reflect the real work, but I hope it will become clear on it:
const { ObjectID } = require('mongodb');
const id = '5ee4f69bfa0b960de8aec158'; // in your example is req.params.userId
db.collection('users').findOne({ _id: new ObjectID(id)}, (error, result) => {
if (error) {
throw error;
}
console.log(result);
})
I am adding the details of the issue initially encountered in case someone else would experience it in the future. The value that is passed from the front-end is a cryptocurrency address. For some reason, some of the characters passed were upper-case, while the same address had been stored in the database with these same characters as lower case. Thus, one needs to add code to make sure that the case of the letters found in the respective addresses is ignored.
J

Unable to update data values on mongoDb native findOneAndUpdate query

exports.update_activity_status = (req, res) => {
const {
campaign_id,
leadId,
leadActivity,
status,
} = req.body;
const client = mongoClient.connect(`${process.env.MONGO_URL}`, {
useUnifiedTopology: true,
});
client.then((cli) => {
cli
.db(`${process.env.DATABASE_NAME}`)
.collection(`${process.env.ACTIVITY_COLLECTION}`)
.findOneAndUpdate(
{
"data.campaign_id": mongoose.Types.ObjectId(campaign_id),
"data.leads.$._id": mongoose.Types.ObjectId(leadId),
// "data.leads._id" : leadId
},
{
$set: {
"data.leads.$.leadActivity": leadActivity,
"data.leads.$.status": status,
},
},
{
returnNewDocument: true,
}
)
.then((result) => {
console.log("UPDATED RESULT", result);
res.json(result)
})
.catch((err) => console.log("new err", err));
});
};
In my query, I need to update the status and leadActivity of the users whose leadId matches the following conditions:
campaign_id that matches the document (this is working fine).
leadsId that matches inside that particular document (not working).
First I tried using only db.find({"data.campaign_id": mongoose.Types.ObjectId(campaign_id)}) and it returns me the data that matches only the first condition as specified above. As soon as I try it along with second condition and findOneAndUpdate({....}), accessing the nested data after including secondary filter condition returns null.
Here's is what my document object looks like:
Any help to resolve this is appreciated. Thanks in advance.
The second condition for matching the _id in the leads-array is incorrect, you need to change it to:
"data.leads._id": mongoose.Types.ObjectId(leadId)

Having issues editing an existing DB entry with Sails and Waterline

I'm using SailsJS as an API with Waterline connected to a MongoDB. I'm trying to put together an endpoint to edit existing DB entries but can't seem to get it to work and I'm hitting a wall as to why.
My route:
'post /edit/safety/:id': {
controller: 'SafetyController',
action: 'editSafety'
},
My controller function:
editSafety: function editSafety(req, res) {
var id = req.params.id;
Safety.findOneById(id).then((err, safety) => {
if (err) {
res.send(500, err);
return;
}
if (!safety) {
res.send(404, err);
return;
}
safety.title = req.body.title;
safety.description = req.body.description;
safety.status = req.body.status;
safety.save((err, updatedSafety) => {
if (err) {
re.send(500, err);
return;
}
res.send(200, updatedSafety);
});
});
},
Any push in the right direction would be greatly appreciated.
I don't recognize the Safety.findOneById method - is this something you have custom built? If not, then it is likely your problem.
Try swapping it for either:
Safety.findOne(id)
or
Safety.findOne({id: id})
Note that the returned object will be a model instance if the record exists, and undefined otherwise. If you decide to go with Safety.find instead then the returned value will be an array containing all models matching the query.
Looks like the main issue was transposing the response and err objects. It was successfully completing the query, but loading it into the err object which gets caught and a 500 error is thrown. So I changed that and simplified in a few other places.
editSafety: function editSafety(req, res) {
var id = req.params.id;
Safety.findOne(id).then((response, err) => {
var safety = response;
if (err) {
res.send(500, err);
return;
}
if (!response) {
res.send(404, err);
return;
}
safety.title = req.body.title;
safety.description = req.body.description;
safety.status = req.body.status;
Safety.update({
id: id
}, safety)
.then((result) => {
res.json(200, 'Ok!');
})
.catch((err) => {
sails.log.error('SafetyController.editSafety', err);
})
});
},

Categories