My nodejs api mixes the codes of simultaneous api requests by clients - node.js

I have a nodejs server file which has an api as shown below to update the profile pictures.
app.post('/updateProfilePic', async(req, res) => {
try {
if (VerifyAPIKey(req.query.key)) {
let userdata = users.find(e => e.Id == req.query.socketId);
if (userdata) {
for (var a = 0; a < users.length; a++) {
const b = a;
if (users[a].IsAuthenticated) {
if (req.query.pub) {
cloudinary.uploader.destroy(req.query.pub, {resource_type: 'image'}, function(err, res) {
// console.log(err, res);
});
}
cloudinary.uploader.upload(req.files.profilePic.tempFilePath, {resource_type: 'image', folder: 'members', eager: [
{width: 25, height: 25, g: 'face', radius: "max", crop: 'fill', format: "png"},
{width: 50, height: 50, g: 'face', radius: "max", crop: 'fill', format: "png"},
{width: 100, height: 100, g: 'face', radius: "max", crop: 'fill', format: "png"},
{width: 250, height: 250, g: 'face', radius: "max", crop: 'fill', format: "png"},
{width: 500, height: 500, g: 'face', crop: 'fill'},
]}, function(err,response) {
if (err) {
console.log(err);
}
if (response) {
const logo = userModel.findOneAndUpdate({
_id: users[b]._id,
},{
PictureUrl: response
}, (err, result) => {
data.status = 200;
data.message = "Your Profile Picture has been updated!"
res.status(200).send(data);
})
}
});
}
}
} else {
data.status = 404;
data.message = "Invalid User!";
res.status(200).send(data);
}
} else {
res.json('Unauthorized request!');
}
} catch(err) {
res.status(400).send(err.message);
}
})
The VerifyAPIKey function is given below
function VerifyAPIKey(key) {
var a = users.find(e=> e.API_KEY == key);
console.log(a)
fs.appendFile('./data/apiRequests.txt', JSON.stringify(a) + "\r\n", function (err) {
if (err) throw err;
});
return Boolean(a);
}
The userdata is in a format as shown below
{
Id: 'FjWs0GZ4MkE_GCmKAAAD',
Ip: '::1',
API_KEY: '590c3789-e807-431b-bfdb-e20b6649e553',
HOST: undefined,
IsAuthenticated: false
}
The problem is the current code causes the response data from cloudinary to mix up between simultaneous requests. I have tested it with two simultaneous requests. Out of the two cloudinary responses whichever comes first is sent back as response to the user who invoked the api later than the two. And the user who invoked the api first get's an error saying cannot set headers after they are sent.
I have tried searching for solution but haven't found any. Can someone please help?

How does data got initiated? data does not seem to be thread-safe and defined outside your async flow. You may want to start from there and make sure data is thread-safe.

Related

How to send PDF file object from Facebook messaging API

I have developed a facebook messenger app in Node.js.
I am using PDFKit to generate a PDF and send it to the user from the messenger bot. The problem I am facing is I am not able to send the generated file object.
generatePDF.js
require('dotenv').config("./env");
const getStream = require('get-stream')
const PDFDocument = require('pdfkit');
const fs = require('fs');
async function createPDF(name) {
const doc = new PDFDocument({
layout: 'landscape',
size: 'A4',
});
doc.rect(0, 0, doc.page.width, doc.page.height).fill('#fff');
``
doc.fontSize(10);
doc.image('src/airtable/assets/corners.png', -1, 0, { scale: 0.585 }, { fit: [doc.page.width, doc.page.height], align: 'center' })
doc
.font('src/airtable/fonts/Pinyon Script 400.ttf')
.fontSize(65)
.fill('#125951')
.text(`${name}`, 240, 240, {
// width: 500,
// align: 'center'
});
doc.end();
return await getStream.buffer(doc)
}
module.exports = { createPDF}
Invoking the above function after receiving specific postback
main.js
const pdf= require('./generatePDF')
name = "John"
const generated_pdf = await pdf.createPDF(name)
sendMedia(sender_psid, generated_pdf )
async function sendMedia(sender_psid, file) {
try {
let response = {
"attachment": {
"type": "file",
"payload": file
}
}
}
callSendAPI(sender_psid, response);
}
catch (e) {
console.log("Error cert ", e)
}
}
function callSendAPI(sender_psid, response) {
// Construct the message body
let request_body = {
"recipient": {
"id": sender_psid
},
"message": response
};
// Send the HTTP request to the Messenger Platform
request({
"uri": "https://graph.facebook.com/v7.0/me/messages",
"qs": { "access_token": process.env.FB_PAGE_TOKEN },
"method": "POST",
"json": request_body
}, (err, res, body) => {
if (!err) {
console.log('message sent!!!');
} else {
console.error("Unable to send message:" + err);
}
});
}
How can I send the file object without a URL and without fetching locally?
any help or advice is appreciated!
There is no such type ("application/pdf"), for sending attachments like a PDF you'd use the file type. Also, as stated in the docs, there is no "filedata" parameter, instead you'd use "payload".
Docs can be found here by the way:
https://developers.facebook.com/docs/messenger-platform/reference/send-api/

Mongoose update function is only saves doc changes half of the time

The route below first runs a middleware which gives me access to req.user so I can get that users cart_id. Then I get the item which is being added to the cart through req.body (an item is uniquely identified by its id and color). Then I get the cart from the database and try to update it.
If the findIndex() function returns any number but -1, it means the item already is in the cart and I want to increase its quantity. If the function did not find the item, then I want to add it to the cart.
The strange problem I am getting is that when I try adding a new item (the code runs the else block where I push a new item to the cart) everything works and the changes are being saved. HOWEVER, when I try to add an item which is already in the cart, the changes are NOT being saved.
I have tried to console.log the quantity both before and after updating it and it is being updated correctly, but for some reason the changes aren't being saved to the database. Please help.
router.get("/add-to-cart", get_user, async (req, res) => {
try {
const { item, color, quantity } = req.body;
await Cart.findById(req.user.cart_id).then(doc => {
const i = doc.line_items.findIndex(elm => { return (elm.item == item && elm.color == color) });
if (i !== -1) {
console.log(doc.line_items[i].quantity) // 1
doc.line_items[i].quantity += quantity;
console.log(doc.line_items[i].quantity) // 2, but not being saved to database
}
else {
doc.line_items.push({ item, color, quantity }); // does get saved to database
}
doc.save();
}).catch(err => {
throw err.message;
});
res.json({ success: true });
}
catch (err) {
console.log(err);
res.status(500).json({ error: "unknown error" });
}
});
// the front end:
await fetch('/cart/add-to-cart', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ item: 101, color: 'white', quantity: 1 })
}).then(res => res.json())
// the cart schema
{
_id: 123456,
line_items: [
{ item: 101, color: 'white', quantity: 1 },
{ item: 101, color: 'green', quantity: 1 },
{ item: 102, color: 'white', quantity: 1 },
]
}

Upload images - Nodejs Paperclip and S3

I want to upload an image to S3 and save to user record with NodeJS, just like the Rails Paperclip gem.
I believe this should be the process, but again I'm quite confused about how this package should work:
receive an image and resize by paperclip
save or update to S3
save file to user in DB
I have a Rails Postgres database, and users can upload an image, stored in S3, and reformatted with Paperclip gem. Here's how it is stored:
irb(main):003:0> user.avatar
=> #<Paperclip::Attachment:0x000055b3e043aa50 #name=:avatar,
#name_string="avatar", #instance=#<User id: 1, email:
"example#gmail.com", created_at: "2016-06-11 22:52:36",
updated_at: "2019-06-16 17:17:16", first_name: "Clarissa",
last_name: "Jones", avatar_file_name: "two_people_talking.gif",
avatar_content_type: "image/gif", avatar_file_size: 373197,
avatar_updated_at: "2019-06-16 17:17:12", #options={:convert_options=>{},
:default_style=>:original, :default_url=>":style/missing.png",
:escape_url=>true, :restricted_characters=>/[&$+,\/:;=?#<>\[\]\
{\}\|\\\^~%# ]/, :filename_cleaner=>nil,
:hash_data=>":class/:attachment/:id/:style/:updated_at",
:hash_digest=>"SHA1", :interpolator=>Paperclip::Interpolations,
:only_process=>[],
:path=>"/:class/:attachment/:id_partition/:style/:filename",
:preserve_files=>false, :processors=>[:thumbnail],
:source_file_options=>{:all=>"-auto-orient"}, :storage=>:s3,
:styles=>{:large=>"500x500#", :medium=>"200x200#",
:thumb=>"100x100#"}, :url=>":s3_path_url",
:url_generator=>Paperclip::UrlGenerator,
:use_default_time_zone=>true, :use_timestamp=>true, :whiny=>true,
:validate_media_type=>true, :adapter_options=>
{:hash_digest=>Digest::MD5},
:check_validity_before_processing=>true, :s3_host_name=>"s3-us-
west-2.amazonaws.com", :s3_protocol=>"https", :s3_credentials=>
{:bucket=>"example", :access_key_id=>"REDACTED",
:secret_access_key=>"REDACTED",
:s3_region=>"us-west-2"}}, #post_processing=true,
#queued_for_delete=[], #queued_for_write={}, #errors={},
#dirty=false, #interpolator=Paperclip::Interpolations,
#url_generator=#<Paperclip::UrlGenerator:0x000055b3e043a8e8
#attachment=#<Paperclip::Attachment:0x000055b3e043aa50 ...>>,
#source_file_options={:all=>"-auto-orient"}, #whiny=true,
#s3_options={}, #s3_permissions={:default=>:"public-read"},
#s3_protocol="https", #s3_metadata={}, #s3_headers={},
#s3_storage_class={:default=>nil},
#s3_server_side_encryption=false, #http_proxy=nil,
#use_accelerate_endpoint=nil>
user.avatar(:thumb) returns:
https://s3-us-west-2.amazonaws.com/example/users/avatars/000/000/001/thumb/two_people_talking.gif?1560705432
Now, I'm trying to allow the user to upload a new/change image through a react-native app, and the backend is Nodejs, which is relatively new to me.
I'm so confused about how to implement this, especially because the examples are all referencing Mongoose, which I'm not using.
Just to show how I'd successfully update the user, here is how to update first_name of the user:
users.updateUserPhoto = (req, res) => {
let id = req.decoded.id
let first_name = req.body.first_name
models.Users.update(
first_name: first_name,
{
where: {
id: req.decoded.id
}
},
).then(response => {
res.status(200).json({ status: 200, data: { response } });
})
.catch(error => {
res.status(500).json({ status: 500, err: error });
})
}
Here is the package I found node-paperclip-s3, and here's what I'm trying to do:
'use strict'
let users = {};
const { Users } = require('../models');
let models = require("../models/index");
let Sequelize = require('sequelize');
let Paperclip = require('node-paperclip');
let Op = Sequelize.Op;
let sequelizeDB = require('../modules/Sequelize');
users.updateUserPhoto = (req, res) => {
let id = req.decoded.id
let avatar = req.body.avatar <- this is a file path
models.Users.plugin(Paperclip.plugins, {
avatar: {
styles: [
{ original: true },
{ large: { width: 500, height: 500 } },
{ medium: { width: 200, height: 200 } },
{ thumb: { width: 100, height: 100 } }
],
prefix: '/users/{{attachment}}/{{id}}/{{filename}}',
name_format: '{{style}}.{{extension}}',
storage: 's3',
s3: {
bucket: process.env.S3_BUCKET_NAME,
region: 'us-west-2',
key: process.env.AWS_ACCESS_KEY_ID,
secret: process.env.AWS_SECRET_ACCESS_KEY,
}
}
})
models.Users.update(
avatar,
{
where: {
id: req.decoded.id
}
},
).then(response => {
res.status(200).json({ status: 200, data: { response } });
})
.catch(error => {
res.status(500).json({ status: 500, err: error });
})
}
I've also tried something like this:
models.Users.update(Paperclip.plugins, {
avatar: {
styles: [
{ original: true },
{ large: { width: 500, height: 500 } },
{ medium: { width: 200, height: 200 } },
{ thumb: { width: 100, height: 100 } }
],
prefix: '/users/{{attachment}}/{{id}}/{{filename}}',
name_format: '{{style}}.{{extension}}',
storage: 's3',
s3: {
bucket: process.env.S3_BUCKET_NAME,
region: 'us-west-2',
key: process.env.AWS_ACCESS_KEY_ID,
secret: process.env.AWS_SECRET_ACCESS_KEY,
}
},
{
where: {
id: req.decoded.id
}
},
).then(response => {
res.status(200).json({ status: 200, data: { response } });
})
.catch(error => {
res.status(500).json({ status: 500, err: error });
})
})
I've tried:
let new_avatar = (Paperclip.plugins, {
avatar: {
styles: [
{ original: true },
{ large: { width: 500, height: 500 } },
{ medium: { width: 200, height: 200 } },
{ thumb: { width: 100, height: 100 } }
],
prefix: `/users/avatars/{{attachment}}/{{id}}/{{filename}}`,
name_format: '{{style}}.{{extension}}',
storage: 's3',
s3: {
bucket: process.env.S3_BUCKET_NAME,
region: 'us-west-2',
key: process.env.AWS_ACCESS_KEY_ID,
secret: process.env.AWS_SECRET_ACCESS_KEY,
}
},
})
let data = {
avatar: new_avatar
}
models.Users.update(
data,
{
where: {
id: req.decoded.id
}
},
).then(response => {
res.status(200).json({ status: 200, data: { response } });
})
.catch(error => {
res.status(500).json({ status: 500, err: error });
})
From the example in the link above, I don't understand how it is saving to S3, or how it's updating the database in the same way the Rails gem is creating that record.
Question : how to save resized images + original in the exact same way that the Rails paperclip gem is saving to S3 AND the user record in the database.
I originally had this open for a 400 point bounty, and am more than happy to still offer 400 points to anyone who can help me solve this. Thanks!!
The below code is for nodeJs.
I have added an api to save an image from frontend to AWS S3.
I have added comments within code for better understanding.
var express = require("express");
var router = express.Router();
var aws = require('aws-sdk');
aws.config.update({
secretAccessKey: config.AwsS3SecretAccessKey,
accessKeyId: config.AwsS3AccessKeyId,
region: config.AwsS3Region
});
router
.route("/uploadImage")
.post(function (req, res) {
//req.files.imageFile contains the file from client, modify it as per you requirement
var file = getDesiredFileFromPaperclip(req.files.imageFile);
const fileName = new Date().getTime() + file.name;
//before uploading, we need to create an instance of client file
file.mv(fileName, (movErr, movedFile) => {
if (movErr) {
console.log(movErr);
res.send(400);
return;
}
//read file data
fs.readFile(fileName, (err, data) => {
if (err) {
console.error(err)
res.send(400);
}
else {
//as we have byte data of file, delete the file instance
try {
fs.unlink(fileName);
} catch (error) {
console.error(error);
}
//now, configure aws
var s3 = new aws.S3();
const params = {
Bucket: config.AwsS3BucketName, // pass your bucket name
Key: fileName, // file will be saved as bucket_name/file.ext
Body: data
}
//upload file
s3.upload(params, function (s3Err, awsFileData) {
if (s3Err) {
console.error(s3Err)
res.send(400);
} else {
console.log(`File uploaded successfully at ${awsFileData.Location}`)
//update uploaded file data in database using 'models.Users.update'
//send response to client/frontend
var obj = {};
obj.status = { "code": "200", "message": "Yipee!! Its Done" };
obj.result = { url: awsFileData.Location };
res.status(200).send(obj);
}
});
}
});
});
});
This is old school, non - fancy solution.Please try it out and let me know.

I am not able to get the result of callback function outside it's scope

I am trying to Retrieve MySQL result outside the callback function scope but I am not able to get the result of the callback function outside it's scope.
I'm using the npm pdfkit :
function generateExamDetails(doc) {
const query = "SELECT rc_name, rc_empid, DATE_FORMAT(CURDATE(), '%M %d %Y') AS t_date FROM table1 WHERE pc_id = ?"
console.log("query: ", query, " data: ", pcIDdata)
const getDataQuery = (pcid) =>{
return new Promise((resolve, reject) =>{
connection.query(query, pcid, (error, result) => {
if (error) {
reject(error)
}
console.log("Audit Report1 ", util.inspect(result, false, null, true /* enable colors */))
resolve(result);
})
})
}
var outerheaderResult;
getDataQuery(pcIDdata).then(headerResult =>{
outerheaderResult = headerResult;
}).catch((e) =>{
console.log('e: ',e)
})
console.log("Audit Report2 ", util.inspect(outerheaderResult, false, null, true /* enable colors */))
doc
.fontSize(12)
.text("Some text", 10, 80, { align: "left" })
.text("Some text", 300, 80, { align: "right" })
.text(`Verified by: ${outerheaderResult.rc_name}`, 10, 100, { align: "left" })
.text(`Emp id: ${outerheaderResult.rc_empid}`, 377, 100)
.text(`Verified on(date) ${outerheaderResult.t_date}`, 10, 120, { align: "left" })
.text("Organization:", 377, 120)
}
And I have the error :
Audit Report2 undefined
TypeError: Cannot read property 'rc_name' of undefined
You getDataQuery function is asynchronous so outerheaderResult will be undefined at the time that it's accessed.
You can move your logic inside the then block and just use headerResult directly:
getDataQuery(pcIDdata)
.then(headerResult => {
console.log("Audit Report2 ", util.inspect(headerResult, false, null, true /* enable colors */))
doc
.fontSize(12)
.text("Some text", 10, 80, { align: "left" })
.text("Some text", 300, 80, { align: "right" })
.text(`Verified by: ${headerResult.rc_name}`, 10, 100, { align: "left" })
.text(`Emp id: ${headerResult.rc_empid}`, 377, 100)
.text(`Verified on(date) ${headerResult.t_date}`, 10, 120, { align: "left" })
.text("Organization:", 377, 120)
})
.catch((e) => {
console.log('e: ',e)
})
From your snippet, getDataQuery will move into callback queue and last lines of function will be executed by this time outerheaderResult is declared and its value will null
outerheaderResult value will be null since getDataQuery is async function. ( You might not know, whether it will be resolved or rejected )
getDataQuery(pcIDdata).then(headerResult =>{
outerheaderResult = headerResult;
console.log("Audit Report2 ", util.inspect(outerheaderResult, false, null, true /* enable colors */))
doc
.fontSize(12)
.text("Some text", 10, 80, { align: "left" })
.text("Some text", 300, 80, { align: "right" })
.text(`Verified by: ${outerheaderResult.rc_name}`, 10, 100, { align: "left" })
.text(`Emp id: ${outerheaderResult.rc_empid}`, 377, 100)
.text(`Verified on(date) ${outerheaderResult.t_date}`, 10, 120, { align: "left" })
.text("Organization:", 377, 120)
}).catch((e) =>{
console.log('e: ',e)
})
It would be fine.

webshot/phantom pdf, printing only second half of the page

I'm working with webshot npm module to create pdf file of my page
This is my page
and
I'm getting this as output in pdf
my settings are
var options = {
renderDelay:10000,
"paperSize": {
"format": "Letter",
"orientation": "portrait",
"border": "1cm"
},
shotSize: {
width: 'all',
height: 'all'
},
shotOffset: {
left: 0
, right: 0
, top: 0
, bottom: 0
}
};
webshot(url, fileName, options, function(err) {
fs.readFile(fileName, function (err,data) {
if (err) {
return console.log(err);
}
fs.unlinkSync(fileName);
fut.return(data);
});
});
this.response.writeHead(200, {'Content-Type': 'application/pdf',"Content-Disposition": "attachment; filename=generated.pdf"});
this.response.end(fut.wait());
For meteor guys this is my server side root
this.route('generatePDF', {
path: '/api/generatePDF',
where: 'server',
action: function() {
var webshot = Meteor.npmRequire('webshot');
var fs = Npm.require('fs');
Future = Npm.require('fibers/future');
var fut = new Future();
var fileName = "generated_"+Random.id()+".pdf";
var userid = (Meteor.isClient) ? Meteor.userId() : this.userId;
console.log(userid);
// var username = Meteor.users.findOne({_id: userid}).username;
var url = "url";
var options = {
renderDelay:10000,
"paperSize": {
"format": "Letter",
"orientation": "portrait",
"border": "1cm"
},
shotSize: {
width: 'all',
height: 'all'
},
shotOffset: {
left: 0
, right: 0
, top: 0
, bottom: 0
}
};
webshot(url, fileName, options, function(err) {
fs.readFile(fileName, function (err,data) {
if (err) {
return console.log(err);
}
fs.unlinkSync(fileName);
fut.return(data);
});
});
this.response.writeHead(200, {'Content-Type': 'application/pdf',"Content-Disposition": "attachment; filename=generated.pdf"});
this.response.end(fut.wait());
}
});
Am I missing anything here? Any help appreciated
You can try to change paperSize to:
"paperSize": {
width: '612px',
height: '792px',
margin: 'XXpx'
},
If anyone else will have trouble with that - I had the same issue. I am not able to tell you exactly why, but the problem was that I was using bootstrap and the wrapper of my page had the "container" class. After removing this class the whole page was rendered - without removing it it just rendered around the half of the page.

Resources