nodejs google drive api file download error with readablestream object - node.js

I'm trying to implement a function for downloading a file from the google drive using google drive api V3.
Below is my code.
service.files.get({
auth: this.oauth2Client,
fileId: fileId,
alt: 'media'
}, {responseType: "stream"}, function (err, response) {
console.log(response);
response.data
.on('end',() => {
console.log('Done');
})
.on('error', (err) => {
console.log('Error during download', err);
})
.on('data', d=> {
progress += d.length;
if (process.stdout.isTTY) {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write('Download ${progress} bytes');
}
})
.pipe(dest);
})
When I run the code, I get this error.
response.data.on is not a function
Not just the on function, but the pipe function doesn't work either.
I already checked that response.data is the object of Readablestream.
Any ideas?
UPDATES
Below is the full code for GoogleDriveSyn class.
The getAllFilesInFolder function works fine.
const path = require('path')
const {google} = require('googleapis');
const fs = require('fs');
const service = google.drive('v3');
class GoogleDriveSyn {
constructor(auth) {
var apiKeys = JSON.parse(fs.readFileSync('config.json'));
this.oauth2Client = new google.auth.OAuth2(
apiKeys['CLIENT_ID'],
apiKeys['SECRET_ID'],
"http://localhost:8080"
);
this.oauth2Client.credentials = auth;
this.directory = localStorage.getItem('directory');
}
getAllFilesInFolder(folderId) {
var query = '"' + folderId + '"' + ' in parents and mimeType!="application/vnd.google-apps.folder"';
service.files.list({
auth: this.oauth2Client,
q: query,
pageSize: 50,
fields: 'nextPageToken, files(id, name, modifiedTime, kind, createdTime, thumbnailLink, mimeType, size, webContentLink)'
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
console.log(response);
return response
})
}
downloadFile(fileId, filePath, fileName) {
var fullPath = path.join(this.directory, filePath, fileName);
var dest = fs.createWriteStream(fullPath);
let progress = 0;
service.files.get({
auth: this.oauth2Client,
fileId: fileId,
alt: 'media'
}, {responseType: "stream"}, function (err, response) {
console.log(response);
response.data
.on('end',() => {
console.log('Done');
})
.on('error', (err) => {
console.log('Error during download', err);
})
.on('data', d=> {
progress += d.length;
if (process.stdout.isTTY) {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write('Download ${progress} bytes');
}
})
.pipe(dest);
})
}
}
module.exports = GoogleDriveSyn;
Also, below is the log of response inside downloadFile function.
{config: {…}, data: ReadableStream, headers: {…}, status: 200, statusText: ""}
config: {url: "https://www.googleapis.com/drive/v3/files/1zcxy0wWsPuWINMY8FP_bjR4nrnj6t8eD?alt=media", method: "GET", responseType: "stream", headers: {…}, paramsSerializer: ƒ, …}
data: ReadableStream {locked: false}
headers: {alt-svc: "h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,…3"; ma=2592000,quic=":443"; ma=2592000; v="46,43"", cache-control: "private, max-age=0, must-revalidate", content-disposition: "attachment", content-length: "12", content-type: "text/plain", …}
status: 200
statusText: ""
__proto__: Object

I believe your goal and situation as follows.
You want to download a text file from the Google Drive.
Your script for authorizing works fine. So this.oauth2Client can be used for downloading the file.
Modification points:
In my environment, I confirmed that your script worked. But in your question, you say the error of response.data.on is not a function occurs. So in this modification, downloadFile was modified as follows.
At first, please confirm the version of googleapis for Node.js you are using. In this case, please use the latest version of googleapis for Node.js. In the current stage, the latest version is googleapis#55.0.0.
If you use the old one, I thought that the reason of your issue might be this.
In your script, 'Download ${progress} bytes' is not correct. In this case, please use it as the template literal. By this, the progress information can be seen.
Modified script:
downloadFile(fileId, filePath, fileName) {
var fullPath = path.join(this.directory, filePath, fileName);
var dest = fs.createWriteStream(fullPath);
let progress = 0;
return drive.files
.get(
{ auth: this.oauth2Client, fileId: fileId, alt: "media" },
{ responseType: "stream" }
)
.then((response) => {
return new Promise((resolve, reject) => {
response.data
.on("end", () => {
resolve("\nDone");
})
.on("error", (err) => {
reject(err);
})
.on("data", (d) => {
progress += d.length;
if (process.stdout.isTTY) {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(`Download ${progress} bytes`);
}
})
.pipe(dest);
});
});
}
Reference:
Drive v3 API Samples
You can see the sample script at download.js.

Related

Multipart: Boundary not found

I am sending image selected from Expo Image Picker and other data in Form Data and passing it through Axios to node. But Unfortunately i am getting Error: Multipart: Boundary not found.
I need help.
Selected Image : "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FLevelNextClient-d47662dc-9f84-4299-a322-f00845340c43/ImagePicker/dcca0023-60e3-4c31-a16d-ce01e4ea758e.jpg"
const data = new FormData();
data.append('name', name)
data.append('email', email)
data.append('phone', phone)
data.append('image',
{
uri: image,
name: "UserProfile.jpg",
type: 'image/jpg'
});
const response = await axios.post(
`${ConfigData.SERVER_URL}/auth/updateProfile`,
data,
{headers: { 'Content-Type': 'multipart/form-data'},
});
router.post("/updateProfile", fileUpload.array("file"), async (req, res) => {
const busboy = Busboy({ headers: req.headers});
var saveLocation = '';
busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
saveLocation = path.join(__dirname, 'uploads/videos' + filename);
file.pipe(fs.createWriteStream(saveTo));
});
busboy.on('finish', function() {
(async ()=>{
const user_id = req.body.id;
try {
const updateData=await userModel.updateOne(user_id,
{ name: req.body.name, email: req.body.email ,number: req.body.number,ProfileImageDestination : req.body.image},
);
res.writeHead(200, { 'Connection': 'close' });
res.end({ status: true ,updateData});
} catch (e) {
console.log(e);
res.writeHead(200, { 'Connection': 'close' });
res.end({ status: false, message: "Something Went Wrong" });
}
})()
});
return req.pipe(busboy);
});

Axios POST request sending nothing with 'multipart/form-data' [React Native - Expo]

Scenario
Front end is basically a React Native (Expo) application where users can issue a report - this includes taking multiple photos and filling in some details.
Backend is just node.js, with Express & Multer.
Problem
I use Axios to send the images and form data through FormData(), however on the server side, req.body and req.files consist of nothing.
One thing here is that sending the SAME data through POSTMAN works COMPLETELY fine, the images were stored into S3 and the form details were stored in the database. It is through the app/emulator that does not work.
I've tried removing the "multipart/form-data" header and this is the output from console.log(req.body) (req.files show undefined):
{
_parts: [
[ 'userId', '1f400069-07d0-4277-a875-cbb2807750c5' ],
[
'location',
'some location'
],
[ 'description', 'Aaaaa' ],
[ 'report-images', [Object] ]
]
}
When I put the "multipart/form-data" header back this output didn't even bother showing up.
What I've Done
I've been searching for solutions for the past hours and none of them worked. Those solutions are:
Adding boundary behind the "multipart/form-data" header
Putting the type to "image/jpeg"
Trimming the file uri to "file://"
Yet none of them works
Here is my code:
React Native Frontend (Expo)
const submitReport = async () => {
setShowLoading(true);
// Validate details (location & images)
if (location === "") {
setShowLoading(false);
showToast(7002);
return;
}
if (images.length === 0) {
setShowLoading(false);
showToast(7004);
return;
}
try {
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
images.forEach(img => {
const trimmedURI = (Platform.OS === "android") ? img.uri : img.uri.replace("file://", "");
const fileName = trimmedURI.split("/").pop();
const media = {
name: fileName,
height: img.height,
width: img.width,
type: mime.getType(trimmedURI),
uri: trimmedURI
};
formData.append("report-images", media);
});
const response = await axios.post(`http://<my-ip-address>:3003/api/report/submit`, formData, { headers: { 'Content-Type': "application/x-www-form-urlencoded" } });
console.log(response)
setShowLoading(false);
}
catch (error) {
console.log(error);
setShowLoading(false);
showToast(9999);
}
};
Backend
// Multer-S3 Configuration
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.AWS_S3_BUCKET_NAME,
contentType: (req, file, callback) => {
callback(null, file.mimetype);
},
metadata: (req, file, callback) => {
callback(null, { fieldName: file.fieldname });
},
key: (req, file, callback) => {
callback(null, `${process.env.AWS_S3_REPORT_IMAGES_OBJECT_PATH}${req.body.userId}/${new Date().getTime().toString()}-${file.originalname}`)
}
}),
fileFilter: (req, file, callback) => {
// Check if file formats are valid
if (file.mimetype === "image/png" || file.mimetype === "image/jpg" || file.mimetype === "image/jpeg") {
callback(null, true);
}
else {
callback(null, false);
return callback(new Error("Image File Type Unsupported"));
}
},
});
router.post("/submit", upload.array("report-images", 3), async (req, res) => {
try {
// req -> FormData consisting of userId, location & description
// multer-s3 library will handle the uploading to S3 - no need to code here
// Details of files uploaded on S3 (Bucket, Key, etc.) will be displayed in req.files
// Analyze from Rekognition
//Add to Database code blablabla
if (result.success === true) {
res.status(200).send({ message: result.data });
}
else if (result.success === false) {
res.status(404).send({ error: ERROR_MESSAGE });
}
}
catch (error) {
console.log(error);
res.status(404).send({ error: ERROR_MESSAGE });
}
});
I'm unsure if this an Axios problem or some problem on my side.
This project is for my Final Year Project.
So after diving through search results in Google, I've found this StackOverflow post: react native post form data with object and file in it using axios
I took the answer provided by user_2738046 in my code and it worked! Combining with Ali's suggestion here is the final code that worked.
const FormData = global.FormData;
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
images.forEach(img => {
const trimmedURI = (Platform.OS === "android") ? img.uri : img.uri.replace("file://", "");
const fileName = trimmedURI.split("/").pop();
const media = {
name: fileName,
height: img.height,
width: img.width,
type: mime.getType(trimmedURI),
uri: trimmedURI
};
formData.append("report-images", media);
});
const response = await axios({
method: "POST",
url: `http://${<my-ip-address>}:3003/api/report/submit`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
transformRequest: (data, error) => {
return formData;
}
});
// If success, clear all text fields
if (response) {
showToast(7005);
setLocation("");
setImages([]);
setDescription("");
}
setShowLoading(false);
You need to change your image uploading code with this one, you also need to install mime npm package.
const formData = new FormData();
formData.append("userId", user.userId);
formData.append("location", location);
formData.append("description", description);
const formData = new FormData();
files = files || [];
if (files.length) {
for (let index = 0; index < files.length; index++) {
const filePayload = files[index];
const file = filePayload.value;
const localUri =
Platform.OS === "android" ?
file.uri :
file.uri.replace("file://", "");
const newImageUri = localUri;
const filename = newImageUri.split("/").pop();
const media = {
name: filename,
height: file?.height,
width: file?.width,
type: mime.getType(newImageUri),
uri: localUri,
};
formData.append(filePayload.name, media);
}
}
const response = await axios.post(`http://<my-ip-address>:3003/api/report/submit`, formData, {
headers: headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});

how to make or correct PDF data in response Nodejs

Getting response %PDF-1.4\n%����\n6 0 obj\n<</Type/XObject/Subtype/Image/Width 29/Height 29/Length 125/ColorSpace/DeviceGray/BitsPerComponent 1/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns 29/Rows 29>>>>stream\n&����)�9_��~G?�A�G����?�q��(r�\b(�q�GA9�<?T\r���TD<D\�c��A�,Db������ ���\b����t\bdw�$�I/�\b.�"��G�
How to fix this response as readable pdf form please guide
const PDFDocument = require("pdfkit");
router.post("/get/PDF", (req, res) => {
axios({
method: "POST",
url: "http://143.155.55.11:8080/getPDF",
data: {
id: 1,
size: "A4",
},
headers: {
"Content-Type": "application/json",
},
})
.then(function (result) {
const doc = new PDFDocument();
let filename = "download";
// Stripping special characters
filename = encodeURIComponent(filename) + ".pdf";
// Setting response to 'attachment' (download).
// If you use 'inline' here it will automatically open the PDF
res.setHeader(
"Content-disposition",
'attachment; filename="' + filename + '"'
);
res.setHeader("Content-type", "application/pdf");
const content = encodeURIComponent(result.data);
doc.y = 300;
doc.text(content, 50, 50);
doc.pipe(res);
doc.end();
})
.catch(function (error) {
return res.status(500).json({
data: error,
status: "error",
message: "Something went wrong.",
});
});
});
router.post("/get/PDF", (req, res) => {
axios({
method: "POST",
url: "http://143.155.55.11:8080/getPDF",
data: {
id: 1,
size: "A4",
},
headers: {
"Content-Type": "application/json",
},
})
.then(function (result) {
const base64Str = Buffer.from(result.data).toString("base64");
let decodedBase64 = await base64topdf.base64Decode(
base64Str,
"download.pdf"
);
const fileContent = fs.readFileSync("download.pdf");
return res.status(200).json({
status: "success",
message: "Data send successfully.",
});
})
.catch(function (error) {
return res.status(500).json({
data: error,
status: "error",
message: "Something went wrong.",
});
});
});

How export sheets to pdf and upload it in one specific folder?

I'm trying to convert one google sheet into a pdf file. Actually, that, seems ok. But i can't put it directly in one specifics folder ...
Can you help me ?
const getData = await getSpreadSheetData(newSpreadsheetsId);
if (!getData) {
// nop
return;
}
let url = getData.data.spreadsheetUrl;
if (!url) {
// nop
return
}
url = url.replace(/edit$/, '');
const url_ext = 'export?exportFormat=pdf&format=pdf&portrait=true'
url = url + url_ext;
const dest = fs.createWriteStream('test.pdf');
await g.drive.files.export(
{
fileId: `${newSpreadsheetsId}`, // Please set the file ID of Google Docs.
mimeType: "application/pdf"
},
{ responseType: "stream" },function(err, response) {
if (err) {
console.log(err);
return;
}
if (!response) {
// nop
return
}
response.data
.on("end", function() {
console.log("Done.");
})
.on("error", function(err) {
console.log("Error during download", err);
return process.exit();
})
.pipe(dest);
})
getSpreadSheetData retrieve me all the data from one spreadsheetID
I'm not an expert with pipe etc ...
I have trying some options like this link :
Github - google Drive export pdf in Landscape
And i don't want this file on my server, or transiting by my server ... :/
after few hours there is the solution :
g = auth
const exportAsPdfInFolder = await g.drive.files.export(
{
fileId: fileId,
mimeType: 'application/pdf',
alt: 'media',
},
{ responseType: 'stream' },
async (err, result) => {
if (err) console.log(err);
else {
const media = {
mimeType: 'application/pdf',
body: result?.data,
};
await g.drive.files.create(
{
requestBody: {
name: newTitlePDF,
parents: [folderParentId],
},
media: media,
fields: 'id',
},
async (err: any, file: any) => {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id: ', file.data.id);
}
},
);
}
},
);
Reference:
Files: create

How to get progress status while uploading files to Google Drive using NodeJs?

Im trying to get progress status values while uploading files to google Drive using nodeJs.
controller.js
exports.post = (req, res) => {
//file content is stored in req as a stream
// 1qP5tGUFibPNaOxPpMbCQNbVzrDdAgBD is the folder ID (in google drive)
googleDrive.makeFile("file.txt","1qP5tGUFibPNaOxPpMbCQNbVzrDdAgBD",req);
};
googleDrive.js
...
makeFile: function (fileName, root,req) {
var fileMetadata = {
'name': fileName,
'mimeType': 'text/plain',
'parents': [root]
};
var media = {
mimeType: 'text/plain',
body: req
};
var r = drive.files.create({
auth: jwToken,
resource: fileMetadata,
media: media,
fields: 'id'
}, function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
// r => undefined
console.log("Uploaded: " + r);
}
});
},
...
i followed this link but got always an undefined value
How about this modification?
Modification point:
It used onUploadProgress.
Modified script:
makeFile: function (fileName, root,req) {
var fileMetadata = {
'name': fileName,
'mimeType': 'text/plain',
'parents': [root]
};
var media = {
mimeType: 'text/plain',
body: req
};
var r = drive.files.create({
auth: jwToken,
resource: fileMetadata,
media: media,
fields: 'id'
}, {
onUploadProgress: function(e) {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(e.bytesRead.toString());
},
}, function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log("Uploaded: " + file.data.id);
}
});
},
Note:
If you want to show the progression as "%", please use the file size.
It was confirmed that this script worked at googleapis#33.0.0.
References:
axios
test of google/google-api-nodejs-client
In my environment, I'm using the script like above. But if this didn't work in your environment and if I misunderstand your question, I'm sorry.

Resources