I try to implement a phone number verification by SMS with aws-sdk SNS and Mongoose,
But when I test it with a wrong phone number, like, some gibberish instead of real phone,
the error goes to the console, but then it won't get caught by catch and just crashes it all.
Here is my code
// requires, etc is above here
const verifyPhone = async (req, res) => {
const { phoneNumber } = req.body;
const { userId } = req.user;
let code = generateCode(6);
const session1 = await mongoose.startSession();
try {
session1.startTransaction();
const newCode = await PhoneCode.create(
[
{
userId,
code,
},
],
{ session: session1 }
);
sns.publish(
{
MessageAttributes: {
"AWS.SNS.SMS.SenderID": {
DataType: "String",
StringValue: "Testing",
},
"AWS.SNS.SMS.SMSType": {
DataType: "String",
StringValue: "Promotional",
},
},
Message: "Your code: " + code,
PhoneNumber: "65f76fguyg",
},
(err, result) => {
if (!err) {
session1.commitTransaction().then(() => {
console.log("transaction committed");
res.status(200).json({ message: "SMS sent", result });
});
} else {
console.log(err);
throw new Error(err);
}
}
);
} catch (e) {
await session1.abortTransaction();
res.status(500).json({ message: "Something went wrong!", e });
} finally {
session1.endSession();
}
};
module.exports = verifyPhone;
I suspect that the issue is in throwing the error from inside of sns.publish 's callback, but I don't understand, how else I could do that
Also, if I enter right phone number, the SMS gets delivered, I get the response, but then
mongoose crashes, saying
UnhandledPromiseRejectionWarning: MongoTransactionError: Cannot call commitTransaction after calling abortTransaction
I've just got to a solution. The solution for me was to promisify the sns.publish, after that all went like a charm.
const promisifySNS = (args) => {
return new Promise((resolve, reject) => {
sns.publish(args, (err, result) => {
if (err) {
return reject(err);
}
resolve(result);
});
});
};
Funny enough, if you use more generic promisify, for any function
const promisify = (fn, ...args) => {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) {
return reject(err);
}
resolve(result);
});
});
};
await promisify(sns.publish, opts);
that wouldn't work for some reason.
Related
I am trying to call some function in synchronously using node.js but as per my code its not happening. I am explaining my code below.
viewDraggedFileContent = async(req, res) => {
try{
let id = req.params.id;
if(!id) {
responseObj = {
status: 'error',
msg: 'Please send the mongo id to fetch the file content.',
body: {}
};
res.send(responseObj);
}else{
let response = await findOne(id, 'useCaseFile');
console.log('res', response);
if(response['status'] === 0) {
responseObj = {
status: 'error',
msg: `Error occurred`,
body: response['data']
};
res.send(responseObj);
}else{
const resObj = [{
fileData: response.data.fileData,
_id: response['data']['_id']
}]
responseObj = {
status: 'success',
msg: `Fetched data successfully`,
body: resObj
};
res.send(responseObj);
}
}
}catch(error) {
console.log('Error::', error);
}
}
/**
* 1- Method to fetch single record from mongoDB as per _id.
*/
findOne = async (id, collName) => {
try{
let mongoID = new ObjectId(id);
MongoClient.connect(dbUrl, dbOptions).then(function (client) {
let connObj = client.db(dbName);
connObj.collection(collName).findOne({ _id: mongoID }, function (error, doc) {
if (error) {
client.close();
return {
status: 0,
data: error
};
} else {
client.close();
return {
status: 1,
data: doc
};
}
})
})
}catch(error){
console.log('Error while fetching single record::', error);
}
}
Here I am calling findOne function from viewDraggedFileContent function. My objective is once the required data will return from findOne function then console.log('res', response); should execute but as per my code console.log('res', response); is executing before getting response from findOne. I have also used async---await but still its running asynchronously. Here I need after getting response from findOne function then the console message should display.
You can use async/await in the findOne function
async function findOne(id, collName) {
const client = await MongoClient.connect(url, {
useUnifiedTopology: true,
}).catch((err) => {
console.log("Error while connecting to db", err);
});
if (client) {
try {
let mongoID = new ObjectId(id);
let connObj = client.db(dbName);
let doc = await connObj.collection(collName).findOne({ _id: mongoID });
return {
status: 1,
data: doc,
};
} catch (error) {
console.log("Error while fetching single record::", error);
return {
status: 0,
data: error,
};
} finally {
client.close();
}
}
}
I think that your problem here is that you're not returning a promise so the line calling your findOne function doesn't await anything.
Two solutions here :
When you pass a callback to db.collection.findOne function, it doesn't return anything but, if you don't, you can await a result, like this :
const doc = await connObj.collection(collName).findOne({ _id: mongoID })
Then, you can parse your doc and return it.
The other solution if to return a promise in your findOne function and then, send your result with resolve, like this :
findOne = async (id, collName) => {
return new Promise((resolve, reject) => {
try{
let mongoID = new ObjectId(id);
MongoClient.connect(dbUrl, dbOptions).then(function (client) {
let connObj = client.db(dbName);
connObj.collection(collName).findOne({ _id: mongoID }, function (error, doc) {
if (error) {
client.close();
reject(error);
} else {
client.close();
resolve(doc);
}
})
})
}catch(error){
console.log('Error while fetching single record::', error);
}
}
}
Then, you don't anymore need a status and can use then and catch to get your result.
I am finding it hard to convert this user controllers code to async await. Can someone please help and guide me how can i do it too. So that i can also change any callbacks into async await.
Also if someone can provide a good source so that i can read about async await and how to apply them properly.
const User = require("../models/user")
exports.getUserById = (req, res, next, id) => {
User.findById(id).exec((error, user) => {
if (error || !user) {
return res.status(400).json({
error: "No user was found in DB"
})
}
req.profile = user
next()
})
}
exports.getUser = (req, res) => {
req.profile.salt = undefined;
req.profile.encrypted_password = undefined;
return res.json(req.profile)
}
exports.getAllUsers = (req, res) => {
User.find().exec((error, users) => {
if (error || !users) {
return res.status(400).json({
error: "No users was found in DB"
})
}
return res.json(users)
})
}
exports.updateUser = (req, res) => {
User.findByIdAndUpdate(
{ _id: req.profile._id },
{ $set: req.body },
{ new: true, useFindAndModify: false },
(error, user) => {
if (error) {
return res.status(400).json({
error: "You are not authorized to update this info"
})
}
user.salt = undefined;
user.encrypted_password = undefined;
res.json(user)
}
)
}
It should look something like this:
const User = require("../models/user");
exports.getUserById = async (req, res, next, id) => {
let user = await User.findById(id);
try {
if (!user) {
return res.status(404).json({
error: "No user was found in DB"
});
}
req.profile = user;
next();
} catch (err) {
return res.status(500).json({
error: "Something went wrong"
});
}
};
exports.getUser = (req, res) => {
req.profile.salt = undefined;
req.profile.encrypted_password = undefined;
return res.json(req.profile);
};
exports.getAllUsers = async (req, res) => {
let users = await User.find();
try {
if (users.length < 1) {
return res.status(404).json({
error: "No users was found in DB"
});
}
return res.json(users);
} catch (err) {
return res.status(500).json({
error: "Something went wrong"
});
}
};
exports.updateUser = async (req, res) => {
try {
let user = await User.findByIdAndUpdate(
{ _id: req.profile._id },
{ $set: req.body },
{ new: true, useFindAndModify: false }
);
user.salt = undefined;
user.encrypted_password = undefined;
return res.json(user);
} catch (err) {
return res.status(400).json({
error: "You are not authorized to update this info"
});
}
};
You should send back 404 errors if you cant find any user in the database. 400 means bad request.
You can achieve what you are asking by wrapping the function with Promise. In your example, you should use the solution given by Ifaruki, because mongoose already supports promises.
function waitSeconds(seconds) {
return new Promise(res => {
setTimeout(() => {
res();
}, seconds * 1000)
})
}
async function foo() {
console.log("Hello");
await waitSeconds(5);
console.log("World");
}
Here you can learn more about async in javascript
I'm a bit stumped and was wondering if anyone could help. Whenever I call an axios post, the network tab shows that the request is pending and ultimately fails. When I try the same call through Robo 3T, it updates succesfully.
Can anyone give me some insight? Thank you!
Here's the route I'm using:
router.post('/upvote/reply/id/:id',
// passport.authenticate('jwt', { session: false }),
async (req, res) => {
await Posts.findOneAndUpdate(
{ "comments._id": mongoose.Types.ObjectId(req.params.id) },
{
$inc: { "comments.$.points": 1 },
$push: { "comments.$.upvotedBy": req.user._id },
$pull: { "comments.$.downvotedBy": req.user._id },
},
(err, result) => {
if (err) {
return res.status(404).json({
success: false,
error: err,
message: 'Post not upvoted!',
})
}
else {
return res.status(200).json({
success: true,
data: result
})
}
})
.catch(err => console.log(err))
})
Here's how I'm calling my API route:
handleReplyUpvote = (id) => {
this.setState(prevState => {
const updatedReplies = prevState.replies.map(item => {
if (item._id === id) {
try {
axios
.post(`http://localhost:5000/api/posts/upvote/reply/id/${id}`)
.then(res => {
// console.log(res.data.data[0].comments[0])
console.log(res)
// ...item,
// const {posts} = this.state
// posts.push(res.data)
// this.setState({posts})
})
}
catch (err) {
console.log(err)
}
return {
...item,
// voted: true,
points: item.points + 1
}
}
return item
})
return {
replies: updatedReplies
}
})
// console.log('boops')
}
A little more context code which might help:
const replies = this.state.replies.slice().map((item, i) =>
<div
key={i}
className='replyItem'
>
<Reply
// key={i}
reply={item.reply}
id={item._id}
user_id={item.user_id}
createdAt={item.createdAt}
points={item.points}
handleDelete={() => this.handleDelete(item._id)}
user={this.props.auth}
handleReplyUpvote={() => this.handleReplyUpvote(item._id)}
// handleDownvote={() => this.handleReplyDownvote(item._id.points)}
/>
</div>
)
You are mixing async/await, promises and callbacks. Use either promises or asyns/await, not all. I have fixed few things and it should work. (I didn't test it though)
router.post("/upvote/reply/id/:id", async (req, res) => {
try {
const result = await Posts.findOneAndUpdate(
{ "comments._id": mongoose.Types.ObjectId(req.params.id) },
{
$inc: { "comments.$.points": 1 },
$push: { "comments.$.upvotedBy": req.user._id },
$pull: { "comments.$.downvotedBy": req.user._id },
}
);
return res.status(200).json({
success: true,
data: result,
});
} catch (error) {
return res.status(404).json({
success: false,
error: error.message,
message: "Post not upvoted!",
});
}
});
handleReplyUpvote = async(id) => {
const updatedReplies = [];
for(const item of this.state.replies){
if(item._id === id){
try{
const response = await axios
.post(`http://localhost:5000/api/posts/upvote/reply/id/${id}`)
console.log(response.data);
}catch(error){
console.log(error.message);
}
updatedReplies.push({
...item,
points: item.points + 1;
})
continue;
}
updatedReplies.push(item);
}
this.setState({
replies: updatedReplies
})
}
I have wrote a simple Update function. Its working fine for some minutes and then again its not working. Where I am going wrong? Please help me. I use PUT as my method.
code
accept = (req, res) => {
this._model.update({
user: new mongoose.Types.ObjectId(req.params.uid)
}, {
$set: {
status: 'active'
}
}, (err, obj) => {
if (err || !obj) {
res.send(err);
} else {
res.send(obj);
}
});
}
Model
{
"_id":"5d3189a00789e24a23438a0d",
"status":"pending",
"user":ObjectId("5d3189a00789e24a23438a0d"),
"code":"CT-123-345-234-233-423344",
"created_Date":"2019-07-19T09:13:04.297Z",
"updated_Date":"2019-07-19T09:13:04.297Z",
"__v":0
}
Request
api.abc.com/api/accept/5d3189a00789e24a23438a0d
Sometime it is returing values and sometime null.
You can use the following code to ensure the model is tied to a connection. This could be an issue of connection to the database.
const config = require('./config');
console.log('config.database.url', config.database.url);
return mongoose.createConnection(config.database.url, {
useMongoClient: true
})
.then((connection) => {
// associate model with connection
User = connection.model('User', UserSchema);
const user = new User({
email: 'someuser#somedomain.com',
password: 'xxxxx'
});
const prom = user.update();
// Displays: 'promise: Promise { <pending> }'
console.log('promise:', prom);
return prom
.then((result) => {
// Don't see this output
console.log('result:', result);
})
.catch((error) => {
// Don't see this output either
console.log('error:', error);
});
})
.catch((error) => {
console.log(error);
});
I think you need to use promise or async/await, try this
accept = async (req, res) => {
try {
const result = await this._model.update({
user: new mongoose.Types.ObjectId(req.params.uid)
}, {
$set: {
status: 'active'
}
});
return res.send(result);
} catch (e) {
return res.send(e);
}
};
i need to know how i can write my request to make multiple delete.
the second thing is how can i put async function on my code.
i want to delete a campus and in the same time dele the builings with the same id campus in the JSON
app.delete('/campuses/:id', (req, res)=> {
const id = req.params.id;
const details = { 'campusid': new ObjectID(id) };
db.db('').collection('buildings').remove(details, (err, result)=> {
if (err) {
res.send({ 'error': 'en error has occured' });
} else {
res.send(result);
}
});
const details2 = { '_id': new ObjectID(id) };
db.db('').collection('campuses').remove(details2, (err, result)=> {
if (err) {
res.send({ 'error': 'en error has occured' });
} else {
res.send(result);
}
}
);
})
You can delete like this.
app.delete('/campuses/:id', async (req, res)=> {
try {
const id = req.params.id;
const details = { 'campusid': new ObjectID(id) };
await db.db('').collection('buildings').remove(details);
const details2 = { '_id': new ObjectID(id) };
await db.db('').collection('campuses').remove();
res.send(result);
} catch(err) {
return res.json({
success: false,
message: 'error'
});
}
})
You could make sequential functions where the first one calls the second one. You could then pass on variables to the seconds function (ie. your campus ID).
It could look something like this:
const Query1 = (res, query) => {
const request = new sql.Request();
request.query(query, (err, result) => {
if (err) {
return res.json({
success: false,
message: 'error'
});
} else if (result.recordset[0]) {
let campusID = result.recordset;
Query2(res, campusID, query = 'SELECT bla bla')
}
})
}
const Query2 = (res, campusID, query) => {
const request = new sql.Request();
request.query(query, (err, result) => {
if (err) {
return res.json({
success: false,
message: 'error'
});
} else {
return res.json({
success: true
});
}
})
}
There are various ways to make async call.
You can use promises.
Async Functions.
Sending response without waiting for other tasks.
If you want to make parallel calls you can use bluebird join function
I like the syntax of async functions better than promises, but I use both depending on the situation.
Here is an example of running functions in order before moving to the next function:
async.waterfall([
function(callback1) {
//Do some work, then callback
if (error) {
callback1(errorGoesHere,null);
} else {
callback1(null,successMessageGoesHere);
}
},
function(callback2) {
//Do some work, then callback
if (error) {
callback2(errorGoesHere,null);
} else {
callback2(null,successMessageGoesHere);
}
}
], function (error, success) {
if (error) {
//show an error
}
//all good return the response, etc.
});
If anything in these functions fail, it automatically call the end function to catch the error.