Modify PDF file uploaded in the request body in nodejs - node.js

I'm trying to modify a pdf file that I get from the request then saving it in my server, I achieved to save it first as I received it from the request body and then modifying it, and once more saving it again with the modified version, but I was looking for another approach to modify it in the fly directly after receiving it from the request body then saving it only once in the server. I need to add a logo to the uploaded file, thanks in advance
This is my code
'''
const mongoose = require("mongoose");
const path = require("path");
const ErrorResponse = require("../utils/errorResponse");
const fs = require("fs");
// Form submition with photo
exports.uploadPdf = (req, folderPath, next) => {
if (!req.files) {
console.log("No file");
return next(new ErrorResponse("Please Upload a file", 400));
}
const file = req.files.pdf;
const extention = file.name.split(".").at(-1);
if (extention !== "pdf") {
console.log("It's not a valid PDF file");
return next(new ErrorResponse("Please Upload an pdf file", 400));
}
//Check image size
if (file.size > process.env.MAX_FILE_UPLOAD) {
return next(
new ErrorResponse(
`Please Upload an PDF file less than ${process.env.MAX_PDF_FILE_UPLOAD}`,
400
)
);
}
//Create custom filename
let id;
if (!req.params.id) {
id = mongoose.Types.ObjectId();
} else {
id = req.params.id;
}
file.name = `document_${id}${path.parse(file.name).ext}`;
var dir = `${process.env.FILE_UPLOAD_PATH}/${folderPath}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
file.mv(
`${process.env.FILE_UPLOAD_PATH}/${folderPath}/${file.name}`,
async (err) => {
if (err) {
console.log(err);
return next(new ErrorResponse(`Problem with file upload`, 500));
}
}
);
req.body = {
_id: id,
...req.body,
pdf: file.name,
};
};
'''

Related

How can I send a file from the Backend to download in the browser?

When I make the request with Postman or Thunder Client I can save the file, but when I try to do it from the browser I can't save it and the size is even larger than the original.
router.get('/files/download/:id', async (req, res) => {
try {
const { id } = req.params;
const file = await FileArea.findById(id);
if (!file) {
return res.status(404).send('File not found');
}
if (fs.existsSync (file.path)) {
const fileContents = fs.readFileSync(file.path); // read the file from the uploads folder with the path of the file in the database
const readStream = new stream.PassThrough(); // create a stream to read the file
readStream.end(fileContents); // end the stream
res.set('Content-disposition', 'attachment; filename=' + file.name); // set the name of the file to download with the name of the file in the database
res.set('Content-Type', file.type);
const fileToSend = readStream.pipe(res); // pipe the stream to the response
return fileToSend;
} else {
return res.status(404).send('File not found');
}
} catch (error) {
return res.status(500).send('Internal server error');
}
});
With this code from the frontend I try to put what is sent to a blob, which size is larger than the original file.
Example: The original file is 421KB and the Blob is 800KB
async downloadFiles(id: string) {
try {
const { data } = await downloadFile(id);
const blob = new Blob([data], { type: data.type });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', data.name);
document.body.appendChild(link);
link.click();
link.remove();

Cloudinary uploader.upload executed after next async command in file

I'm trying to upload file with Multer and cloudinary node.js api and then remove temp file from hard drive, however file gets deleted before upload finished. Then I commented unlink files uploaded correctly
Here is my controller
const createGame = async (req, res) => {
const options = {
use_filename: true,
unique_filename: false,
overwrite: true,
};
try {
const data = req.body;
const files = req.files;
let posterPath = "";
let videoPath = "";
let distroPath = "";
if (files?.poster?.length) {
posterPath = files?.poster[0]?.path || "";
try {
const newPath = await uploader.upload(posterPath, options);
// Bug - unlink happens before file uploaded
await unlink(posterPath);
posterPath = newPath as string;
} catch (err) {
// Upload failure - remove files from disk
await unlink(posterPath);
posterPath = "";
}
}
if (files?.video?.length) {
videoPath = files?.video[0]?.path || "";
try {
const newPath = await uploader.upload(videoPath, {
...options,
resource_type: "video",
});
// Bug - unlink happens before file uploaded
await unlink(videoPath);
videoPath = newPath as string;
} catch (err) {
// Upload failure - remove files from disk
await unlink(videoPath);
videoPath = "";
}
}
const currentPublisher = await publishersRepository.findOneBy({
id: req.user.id,
});
if (!currentPublisher) {
res.status = 301;
return res.json({
message:
"You must be authenticated as publisher in order to add a game",
});
}
const newGameData = gamesRepository.create({
...data,
poster: posterPath,
video: videoPath,
distro: distroPath,
publisher: currentPublisher,
});
const newGame = await gamesRepository.save(newGameData);
res.status(200);
return res.json({ message: "Game added", data: newGame });
} catch (err) {
console.log("Error creating the game", err);
res.status(400);
return res.json({ message: "Error creating the game" });
}
};
I'm assuming you're using just one multer instance.
From what I looked at the docs of multer, the upload function is not a Promise. So the await function in front of uploader.upload method will not work.
What you need is to promisify your upload function. Here's an example:
const util = require('util');
// your code ...
try {
const promisifiedUploadFunc = util.promisify(uploader.upload);
const newPath = await promisifiedUploadFunc(posterPath, options);
// Bug - unlink happens before file uploaded
await unlink(posterPath);
posterPath = newPath as string;
} catch (e) {
// continue code..
}

Saving uploaded file to Pinata IPFS in NodeJS

I've been trying to save uploaded image files to IPFS in NodeJs , while it seems Pinata saves them, the files are pretty much gibberish (after downloading the images are broken).
My code :
// Nodejs route.
exports.postImage = async (req, res, next) => {
// Using multer to get the file.
fileUploadMiddleware(req, res, async (err) => {
// getting bunch of data from query string.
let meta = {
origin,
originid,
context,
ownerid,
format
} = req.query;
if(!meta.format || !req.files) {
return next(new ErrorResponse("File format not specified", 404));
}
if(!meta.originid) {
meta.originid = uuidv4();
}
// NOTE: is this the right way to get the data of the file ?
const buffer = req.files[0].buffer;
const filename = `${metadata.origin}_${metadata.originid}.${ metadata.format }`;
let stream;
try {
stream = Readable.from(buffer);
// HACK to make PINATA WORK.
stream.path = filename;
}
catch(e) {
logger.logError(e);
return false;
}
const options = {
pinataMetadata: {
name: filename,
keyvalues: {
context: metadata.context,
ownerid: metadata.ownerid
}
},
pinataOptions: {
cidVersion: 0
}
};
try {
var result = await pinata.pinFileToIPFS(stream, options);
console.log("SUCCESS ", result);
return result;
}
catch(e) {
logger.logError(e);
return null;
}
res.status(200).json({
success: true,
data: 'You got access'
})
});
}
So basically creating the stream based on the uploaded file buffer and sending it away to Pinata. Where do I go wrong?
const buffer = req.files[0].buffer;
If you used MemoryStorage. buffer property would be available. It is not available for diskStorage because it will save the file locally.:
const storage = multer.memoryStorage()
const upload = multer({ storage: storage })
Also I think it not req.files[0]
const buffer = req.file.buffer;
after I get the buffer, I convert it to FormData using form-data npm package:
import FormData from "form-data";
const formData = new FormData();
formData.append("file", buffer, {
contentType,
filename: fileName + "-" + uuidv4(),
});
then you send a post request to pinata
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
const fileRes = await axios.post(url, formData, {
maxBodyLength: Infinity,
headers: {
// formData.getBoundary() is specific to npm package. native javascript FormData does not have this method
"Content-Type": `multipart/form-data: boundary=${formData.getBoundary()}`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey,
},
});

file upload not working in react native with multipart form data api nodejs

I am trying to upload image file with react native, by using nodejs multipart api, but the file is not getting sent from the FE. If I console req.files its undefined at server side. Here is my react native code:
var options = {
title: 'Select Image',
storageOptions: {
skipBackup: true,
path: 'images'
}
};
ImagePicker.showImagePicker(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
console.log('User selected a file form camera or gallery', response);
const data = new FormData();
data.append('name', 'avatar');
data.append('file', {
uri: response.uri,
type: response.type,
name: response.fileName
});
const config = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
},
body: data,
};
fetch("http://myapi.com/api/v1/user", config)
.then((checkStatusAndGetJSONResponse) => {
console.log(checkStatusAndGetJSONResponse);
}).catch((err) => { console.log(err) });
}
}
)
and Nodejs code:
const storage = multer.memoryStorage({
destination:(req, file, callback) => {
callback(null, '')
}
});
const upload = multer({ storage: storage }).array('file');
upload(req,res,(err) => {
if(err) {
console.log('ERROR: ',err);
return res.end("Error uploading file.");
}else{
console.log('REQUEST: ',req.files);
}
});
I am not able to upload image with some user data, please let me know what am doing wrong here
Thanks
as you are sending form data in body, it will only hold that form data.
if you want to send form data plus some other data, then try to append form data in another object and then append other data in same object with key value pair.
I have created user register form in which I have some input fields and profile upload.
For upload I have used "ngx-file-drop".
like:-
const body = {};
body['formData'] = formValues;
body['fileData'] = this.fileDataArray;
In this the ts code is like below.
dropped(files: NgxFileDropEntry[]) {
this.fileError = false;
this.files = [];
this.files = files;
for (const droppedFile of files) {
// Is it a file?
if (droppedFile.fileEntry.isFile && this.isFileAllowed(droppedFile.fileEntry.name)) {
this.filesArray.push(droppedFile);
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
const formData: FormData = new FormData();
fileEntry.file((file: File) => {
});
fileEntry.file((file: File) => {
this.dropFilePath.push(droppedFile.relativePath);
// append form data
formData.append('upload', file, droppedFile.relativePath);
this.dropFile.push(file);
this.dropFileFlag = true;
});
} else {
// It was a directory (empty directories are added, otherwise only files)
const fileEntry = droppedFile.fileEntry as FileSystemDirectoryEntry;
this.dropFilePath = [];
this.dropFile = [];
this.files = [];
this.toaster.error('only this file are allowed : [".jpg", ".jpeg",".gif",".png"]', 'Error', {
positionClass: 'toast-top-full-width',
});
break;
}
}
}
and html code is.
<ngx-file-drop [disabled]="isPreview ? true : null" dropZoneLabel="Drop files here" (onFileDrop)="dropped($event)" (onFileOver)="fileOver($event)"(onFileLeave)="fileLeave($event)">
<ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
<span [attr.disabled]="isPreview ? true : null" class="btn">Drop files here or</span>
<span [attr.disabled]="isPreview ? true : null" (click)="openFileSelector()" class="btn btn-link">click to upload</span>
</ng-template>
</ngx-file-drop>
and on form submit you can do like this.
onSubmit() {
this.submitted = true;
if (this.form.invalid || (this.submitted && this.fileError)) {
this.toaster.error('Invalid form data..!!', 'ERROR');
return;
}
const formData = this.form.value;
this.fileDataArray.push(formData.getAll('upload'));
console.log('this.fileDataArray-------->', this.fileDataArray);
const body = {};
body['formData'] = formData;
body['fileData'] = this.fileDataArray;
console.log('body------>', body);
// call below your api function
}

How to download images from the database?

I need to upload files and images no larger than 2 megabytes in the database. But there is a problem downloading images. All images that are downloaded break and do not open. No such problems with text files.
Result of the downloaded image:
file 2.png
2.png: data
Uploading images this way:
module.exports.upload = async function (req, res) {
const sysFileObj = {
COMMENTS: req.body.COMMENTS,
NAME: req.file.originalname,
MIMETYPE: req.file.mimetype,
FILE_CONTENT: req.file.buffer
};
try {
await SysFiles.create(sysFileObj);
res.status(201).json(sysFileObj);
} catch (e) {
errorHandler(res, e);
}
};
multer:
const multer = require('multer');
const storage = multer.memoryStorage()
let obj = {
storage: storage,
limits: {
fileSize: 1024 * 1024 * 2
}
};
var upload = multer(obj)
module.exports = upload;
And here there is a problem when download:
module.exports.download = async function (req, res) {
try {
let sysFile = await SysFiles.findById(req.params.SYSFILE_ID);
var fileContents = Buffer.from(sysFile._props.FILE_CONTENT);
var readStream = new stream.PassThrough();
readStream.end(fileContents);
res.set('Content-disposition', 'attachment; filename=' + sysFile._props.NAME);
res.set('Content-Type', sysFile._props.MIMETYPE);
readStream.pipe(res);
} catch (e) {
errorHandler(res, e);
}
};
What am I doing wrong? Please tell me. I must say right away that I need to upload the image to the database without any links to any folder where the images will be stored.
Note:
But, by the way, when I downloaded the uploaded image using “SQL
Developer”, the image opens without any not problems.
You must store image content-Type
And image data
const formidable = require('formidable');
const _ = require('lodash');
const fs = require('fs');
// loading formidable library
let form = new formidable.IncomingForm();
// taking file extension
form.keepExtensions = true;
//processing for upload image
form.parse(request_data,(err,fields,files) => {
//if error when uploading image
if (err) {
return res.status(400).json({
err: 'Image could not bd uploaded'
})
}
// validating all fields without image
const {name, description, price, category, quantity, shipping} = fields;
if(!name || !description || !price || !category || !quantity || !shipping) {
return res.status(400).json({
err: 'All Fields are required'
})
}
// lets continue when there are no error
// form have available photo
if(files.photo){
//1 kb = 1000
//1 MB = 1000000
// check image size
if(files.photo.size > 2000000){
return res.status(400).json({
err: 'Image should be less than 2 MB'
})
}
// there are no error found then execute it
TAKING_AS_YOUR_VALRIABLE.data = fs.readFileSync(files.photo.path);
TAKING_AS_YOUR_VALRIABLE.contentType = files.photo.type
}
// finaly save product
//save it your own way
const FINAL_DATA = {...fields,...TAKING_AS_YOUR_VALRIABLE}
})
now show photo code
if(TAKING_AS_YOUR_VALRIABLE.data){
res.set('Content-Type',TAKING_AS_YOUR_VALRIABLE.contentType);
res.send(TAKING_AS_YOUR_VALRIABLE.data);
}

Resources