How to validate data from csv file in nodejs? - node.js

My CSV file contains 3 columns: first name,last name,email. I want to add all the valid lines of the CSV file in the users table in the database. For example: if the CSV file has 10 lines where line 2 is missing first name and line 3 has invalid email, I want to see 8 users added to database. After adding the users from the CSV file, it should display how many users were added and how many failed.
My model:
const User = sequelize.define(
"users",
{
first_name: {
type: Sequelize.STRING(60),
allowNull: false,
},
last_name: {
type: Sequelize.STRING(60),
allowNull: false,
},
email: {
type: Sequelize.STRING(255),
allowNull: false,
},
My Controller:
const upload = async (req, res) => {
try {
if (req.file == undefined) {
return res.status(400).send("Please upload a CSV file!");
}
let users = [];
console.log(" __basedir", __basedir);
let path = __basedir + "/uploads/" + req.file.filename;
fs.createReadStream(path)
.pipe(csv.parse({ headers: true }))
.on("error", (error) => {
throw error.message;
})
.on("data", (row) => {
users.push(row);
})
.on("end", () => {
User.bulkCreate(users, {
validate: true,
})
.then(() => {
res.status(200).send({
message:
`Uploaded ${users.length} data successfully from ` + req.file.originalname,
});
})
.catch((error) => {
res.status(500).send({
message: `Fail to import ${users.length} into database!`,
error: error.message,
});
});
});
} catch (error) {
res.status(500).send({
message: "Could not upload the file: " + req.file.originalname,
});
}
};
How can I solve this?

I solve it using fast-csv package
import * as csv from 'fast-csv';
const upload = async (req, res) => {
try {
if (req.file == undefined) {
return res.status(400).send("Please upload a CSV file!");
}
let users = [];
console.log(" __basedir", __basedir);
let path = __basedir + "/uploads/" + req.file.filename;
console.log("path", path);
let emailPattern= /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
let successCount = 0;
let errorCount = 0;
fs.createReadStream(path)
.pipe(csv.parse({ headers: [ undefined,'first_name', 'last_name', 'email', undefined ],renameHeaders: true, ignoreEmpty: true }))
.validate(data => data.first_name !== '' && data.last_name !== '' && emailPattern.test(data.email))
.on("error", (error) => {
throw error.message;
})
.on("data", (row) => {
successCount++;
users.push(row);
console.log(`ROW=${JSON.stringify(row)}`)
})
.on('data-invalid', (row, rowNumber) => {
errorCount++;
console.log(`Invalid [rowNumber=${rowNumber}] [row=${JSON.stringify(row)}]`)
})
.on("end", (rowCount) => {
console.log(`Parsed ${rowCount} rows`)
User.bulkCreate(users, {
validate: true,
})
.then(async() => {
res.status(200).json({
error: false,
success:
`Uploaded ${successCount} row successfully from ` + req.file.originalname,
failed:`Uploaded ${errorCount} row failed from ` + req.file.originalname,
});
})
.catch((error) => {
console.log(error);
res.status(500).json({
error: error.message,
failed: `Fail to import ${users.length} row into database!`,
});
});
});
} catch (error) {
console.log(error);
console.log(error.message);
res.status(500).json({
error: error.message,
failed: "Could not upload the file: " + req.file.originalname,
});
}
};

Related

Post request returns 500 Error on Node app

In my social media application, when a user comments on a post, it throws a 500 Internal Server Error.
The console states POST https://shielded-journey-88539.herokuapp.com/https://us-central1-myapp-1d191.cloudfunctions.net/api/post/3Y7OcHJXXXa0ilBeq35u/comment 500 (Internal Server Error)
When I check the commentOnPost route on Postman, the response returns a Status 200, but the body returns Invalid Host Header.
// Comment on a Post API
exports.commentOnPost = (req, res) => {
if (req.body.body.trim() === '') {
return res.status(400).json({ comment: 'Cannot be empty' });
}
const newComment = {
body: req.body.body,
createdAt: new Date().toISOString(),
postId: req.params.postId,
userHandle: req.user.handle,
profileImage: req.user.profileImage
};
db.doc(`/posts/${req.params.postId}`)
.get()
.then(doc => {
if (!doc.exists) {
return res.status(404).json({ error: 'Post does not exist.' });
}
// after gaining access to document, use prefix reference to update comment count
return doc.ref.update({ commentCount: doc.data().commentCount + 1 })
})
.then(() => { // add newComment to comments collection
return db.collection('comments').add(newComment);
})
.then(() => {
res.json(newComment);
})
.catch(err => {
console.log(err);
res.status(500).json({ error: 'Something went wrong' });
});
};
When I console.log(commentData) inside of dataSlice/submitComment, it returns just the req.body.body and not the rest of the newComment object, from the commentOnPost route.
// submitComment of dataSlice
export const submitComment = (postId, commentData) => dispatch => {
console.log(commentData)
return axios
.post(`/post/${postId}/comment`, commentData)
.then(res => {
dispatch(submitTheComment(res.data))
dispatch(clearErrors());
})
.catch(err => dispatch(setErrors(err.response)))
};
I'm using my own Heroku proxy server.
// App.jsx
axios.defaults.baseURL =
'https://shielded-journey-88539.herokuapp.com/https://us-central1-myapp-1d191.cloudfunctions.net/api';
// package.json
"proxy": "https://shielded-journey-88539.herokuapp.com/https://us-central1-myapp-1d191.cloudfunctions.net/api"
What am I doing wrong?
Can you try this code, and console.log(commentData), and where is commentData?
exports.commentOnPost = (req, res) => {
if (req.body.body.trim() === '') {
return res.status(400).json({ comment: 'Cannot be empty' });
}
const newComment = {
body: req.body.body,
createdAt: new Date().toISOString(),
postId: req.params.postId,
userHandle: req.user.handle,
profileImage: req.user.profileImage
};
console.log("newComment: ", newComment)
db.doc(`/posts/${req.params.postId}`).get()
.then(doc => {
if (!doc.exists) {
return res.status(404).json({ error: 'Post does not exist.' });
}
// after gaining access to document, use prefix reference to update comment count
return doc.ref.update({ commentCount: doc.data().commentCount + 1 });
}).then(() => {
// add newComment to comments collection
db.collection('comments').add(newComment);
res.status(200).json(newComment);
}).catch(err => {
console.log("Error in Catch commentOnPost: ", err);
res.status(500).json({ error: 'Something went wrong' });
});
};

findOneAndUpdate seems to work Robo 3T but POST request results in 'Pending' through Axios

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
})
}

Trouble with file upload to database (backend)

I'm trying to upload files to my database but I'm having trouble.
When I try to upload a file to my database I got this error :
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
And the data from my file is not save to my database (see the image) :
I think my problem is here :
const fileData = {
type: req.body.type,
name: req.body.name,
data: req.body.data
};
I tried to copy the function I use for create a user when he register but for a file when someone upload it.
Register (it works) :
users.post("/register", (req, res) => {
const today = new Date();
const userData = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
phone: req.body.phone,
deliveryAddress: req.body.deliveryAddress,
created: today
};
User.findOne({
where: {
email: req.body.email
}
})
//TODO bcrypt
.then(user => {
if (!user) {
bcrypt.hash(req.body.password, 10, (err, hash) => {
userData.password = hash;
User.create(userData)
.then(user => {
res.json({ status: user.email + " registered!" });
})
.catch(err => {
res.send("error: " + err);
});
});
} else {
res.status(400).json({ error: "Email already taken" });
console.log("Email already taken !");
}
})
.catch(err => {
res.status(400).json("Error : " + err);
});
});
File upload (not working) :
app.post("/files", (req, res) => {
const uploadFile = req.files.file;
const fileName = req.files.file.name;
const fileData = {
type: req.body.type,
name: req.body.name,
data: req.body.data
};
uploadFile.mv(`./uploads/${fileName}`, function(err) {
if (err) {
return res.status(500).send(err);
}
res.json({
file: `uploads/${fileName}`
});
});
Upload.findOne({
where: {
name: req.body.name
}
})
.then(file => {
if (!file) {
Upload.create(fileData)
.then(file => {
res.json({ status: file.name + " created !" });
})
.catch(err => {
res.send("error: " + err);
});
} else {
res.status(400).json({ error: "File already uploaded" });
console.log("File already uploaded");
}
})
.catch(err => {
res.status(400).json("Error : " + err);
});
});
I'm not very familiar with backend so...
I tried to change :
const fileData = {
type: req.body.type,
name: req.body.name,
data: req.body.data
};
with
const fileData = {
type: req.files.file.type,
name: req.files.file.name,
data: req.files.file.data
};
But I got an infinite loop and the file is not uploaded to my database (nothing is created).
The upload to the backend (uploads folder) works.
EDIT
When I use req.files.file.something for the fileData it works sometimes, the file is correctly uploaded to database but I got the error again (I think it works when the file is very tiny).
For a 1 Ko file :
Executing (default): INSERT INTO `files` (`id`,`name`,`data`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'a suprimer.html',X'3c21444f43545950452068746d6c3e0d0a0d0a3c21444f43545950452068746d6c3e0d0a3c68746d6c3e0d0a3c686561643e200d0a093c6d65746120636861727365743d227574662d38223e0d0a093c7469746c653e20466169726520756e6520696d6167653c2f7469746c653e0d0a3c2f686561643e0d0a3c626f64793e0d0a0d0a093c703e746573743c2f703e0d0a0d0a093c696d67207372633d2268642e696d6167652e736e6f772e6a706567223e0d0a090d0a0d0a3c2f626f64793e0d0a3c2f68746d6c3e','2020-01-29 10:07:28','2020-01-29 10:07:28');
And in the database :
Why the type is not set up ?
How can I reduce the time of the upload for a bigger file ?
You can try this code below:
app.post("/files", (req, res) => {
const uploadFile = req.files.file;
const fileName = req.files.file.name;
const fileData = {
type: req.body.type,
name: req.body.name,
data: req.body.data
};
Upload.findOne({
where: {
name: req.body.name
}
}).then(file => {
if (!file) {
// upload file to directory
uploadFile.mv(`./uploads/${fileName}`);
// save file to database
Upload.create(fileData)
.then(file => {
return res.json({ status: file.name + " created !" });
}).catch(err => {
return res.send("error: " + err);
});
} else {
return res.status(400).json({ error: "File already uploaded" });
}
})
.catch(err => {
return res.status(400).json("Error : " + err);
});
});
I hope it can help you to upload your file.
I believe this is because you are trying to send two responses in the same call handler. Once in uploadfile.mv then again in Upload.findOne. You cannot return two res.X to the same request.
This thread might be useful: Error: Can't set headers after they are sent to the client

ejs renderFile condition to check for null

I have a mail route which is using ejs templates to choose different files. Now I want to check for different files and if the files does not exist or null it should choose default "en.ejs" file
inside ejs.renderFile I want to check for if(selectedLanguage !==null{}) but I am not sure how can I do it.
router.post('/mail', (req, res) => {
const subject = 'Test Mail';
const url = process.env.REPORT_URL;
const selectLanguage = req.body.selectedLang
sgMail.setApiKey(SGAPIKEY);
ejs.renderFile(__dirname + `/../ejs/reports/${selectLanguage}.ejs`, {
url: url
},
function (err, data) {
if (err) {
return err;
} else {
const msg = {
to: req.body.sendTo_email,
from: "test#test.com",
subject: subject,
html: data,
cc: req.body.cc,
attachments: [{
content: req.body.pdfBase64,
filename: 'file.pdf',
type: 'application/pdf',
disposition: 'attachment'
}]
};
sgMail.send(msg, error => {
if (error) {
return res.status(400).send({
sent: false
});
} else {
return res.status(200).send({
sent: true
});
}
});
}
});
});
You can check if the template file for the selected language exists (by using fs.access) and fallback to the en.ejs template before calling ejs.renderFile:
const fs = require('fs');
const getTemplateFileName = (lang, callback) => {
const defaultTemplate = __dirname + '/../ejs/reports/en.ejs';
const selectedLangTemplate = __dirname + `/../ejs/reports/${lang}.ejs`;
fs.access(selectedLangTemplate, fs.constants.F_OK, (err) => {
if (err) {
callback(defaultTemplate);
} else {
callback(selectedLangTemplate)
}
})
};
router.post('/mail', (req, res) => {
const subject = 'Test Mail';
const url = process.env.REPORT_URL;
const selectLanguage = req.body.selectedLang
sgMail.setApiKey(SGAPIKEY);
getTemplateFileName(selectLanguage, (filename) => {
ejs.renderFile(filename, { url }, function (err, data) {
if (err) {
return err;
} else {
const msg = {
to: req.body.sendTo_email,
from: "test#test.com",
subject: subject,
html: data,
cc: req.body.cc,
attachments: [{
content: req.body.pdfBase64,
filename: 'file.pdf',
type: 'application/pdf',
disposition: 'attachment'
}]
};
sgMail.send(msg, error => {
if (error) {
return res.status(400).send({
sent: false
});
} else {
return res.status(200).send({
sent: true
});
}
});
}
})
});
});

How to remove images from Cloudinary by Node.js Express?

Upload and set default controller functions are working perfectly. However, we are trying to implement delete image from Cloudinary as well. How can it be done?
In the documentation it was confusing. Here is the code:
const cloudinary = require('cloudinary');
const HttpStatus = require('http-status-codes');
const User = require('../models/userModels');
cloudinary.config({
cloud_name: 'name',
api_key: 'key',
api_secret: 'secret'
});
module.exports = {
UploadImage(req, res) {
cloudinary.uploader.upload(req.body.image, async result => {
await User.update(
{
_id: req.user._id
},
{
$push: {
images: {
imgId: result.public_id,
imgVersion: result.version
}
}
}
)
.then(() =>
res
.status(HttpStatus.OK)
.json({ message: 'Image uploaded successfully' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error uploading image' })
);
});
},
DeleteImage(req, res) {
cloudinary.uploader.destroy(req.params.image, async result => {
await User.update(
{
_id: req.user._id
},
{
$pull: {
images: {
imgId: result.public_id,
imgVersion: result.version
}
}
}
)
.then(() =>
res
.status(HttpStatus.OK)
.json({ message: 'Image deleted successfully' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error deleting image' })
);
});
},
async SetDefaultImage(req, res) {
const { imgId, imgVersion } = req.params;
await User.update(
{
_id: req.user._id
},
{
picId: imgId,
picVersion: imgVersion
}
)
.then(() =>
res.status(HttpStatus.OK).json({ message: 'Default image set' })
)
.catch(err =>
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error occured' })
);
}
};
We are using Node.js Express with Mongoose. How can we include extra function here that will remove images?
There are two options to delete an image from cloudinary:
By using the admin API. For example in Node:
cloudinary.v2.api.delete_resources(['image1', 'image2'],
function(error, result){console.log(result);});
Using our upload API:
cloudinary.v2.uploader.destroy('sample', function(error,result) {
console.log(result, error) });
Please note that using our admin API is rate limited and you might want to use the second option.
it just because your req.params.image is like https:https://res.cloudinary.com/your/image/upload/v1663358932/Asset_2_bdxdsl.png
instead write your delete request like so :
cloudinary.v2.uploader.destroy('Asset_2_bdxdsl', function(error,result) {
console.log(result, error) })
ps: Asset_2_bdxdsl is your image name without prefix .png !!

Resources