I've created following code, however i can't seem to figure out how to update the image if findOneAndUpdate find result and beside that it seems like result.save is being executed before put_form_url. How can i achieve such a function where if it exist it will update all properties and upload new image to s3 and if not it will create a new object with s3 upload.
router.post('/:id/:name/:birth/:country/:image', function(req, res, next) {
var params = req.params;
var accessHeader = req.headers;
process.env.AWS_ACCESS_KEY_ID=''
process.env.AWS_SECRET_ACCESS_KEY=''
AWS.config.region = 'eu-west-1';
var s3 = new AWS.S3();
User.findOneAndUpdate({"_id": params.id}, {$set:{"name": params.name, "birthday": params.birth, "country": params.country}}, {new: true}, function(error, result) {
if (!error) {
// If the document doesn't exist
if (!result) {
// Create it
put_from_url(params.image, new Date().toString, function(err, res) {
result = new User({
_id: params.id,
name: params.name,
birthday: new Date(params.birth),
country: params.country,
image: "url" + params.id
});
});
}
// Save the document
result.save(function(error) {
if (!error) {
res.json({ message: 'User created!' });
} else {
res.send(err);
}
});
}
});
});
Upload function
function put_from_url(url, key, callback) {
request({
url: url,
encoding: null
}, function(err, res, body) {
if (err)
return callback(err, res);
uploader.putObject({
Bucket: "",
Key: "/" + key,
ContentType: res.headers['content-type'],
ContentLength: res.headers['content-length'],
Body: body // buffer
}, callback);
})
}
Okay, it seems you have two parts of the question.
how do you achieve it to store an image if there is no user and update if there is already a user.
So in either case you have to store the image, so why not to do something like either first you update the image and then you update/insert the user or do the vice-versa. personally I would choose the former.
router.post('/:id/:name/:birth/:country/:image', function(req, res, next) {
var params = req.params;
var accessHeader = req.headers;
process.env.AWS_ACCESS_KEY_ID=''
process.env.AWS_SECRET_ACCESS_KEY=''
AWS.config.region = 'eu-west-1';
var s3 = new AWS.S3();
put_from_url(params.image, params.id, function(err, res) {
result = new User({
_id: params.id,
name: params.name,
birthday: new Date(params.birth),
country: params.country,
image: "url" + params.id
});
// Save the document
result.save(function(error) {
if (!error) {
res.json({ message: 'User created!' });
} else {
res.send(err);
}
});
});
few notes is that for key you were using date but I just used id so that it can be updated with new image later on. s3 object can be used and updated by unique key. if you want to do something like keep an old copy of images, you better use key like params.id/(new Date().toString) so old images would not be overwritten
Secondly, save function inherently updates if the object is there based on _id else it creates a new one.
it seems like result.save is being executed before put_form_url
It surely will, javascript is inherently asynchronous which means if one code is taking too long, it will execute next statement. when you make a call to s3, its an I/O call which will try to upload an image to s3 and it will be handled, meanwhile your next line of code which in your case was result.save would be executed.
There are two ways. i> wrap the next call in the callback of previous call. and far better and superior ii> use promises.
Promises are sort of future event where you can tell upload to s3 then save into DB.
Promises have little learning curve, but it's great, clean and really helpful. q is one such promise module.
Related
I would like to improve my skills in Node JS. Right now I'm interested in how to cleanly separate the service and router layers of an application so I can avoid something like code duplication.
The create method of a user scheme shall serve as an example.
In UserService.Js I store the following method for this:
function createUser(req, res, next) {
let user = new User({
userID: req.body.userID,
userName: req.body.userName,
password: req.body.password,
isAdministrator: req.body.isAdministrator
});
user.save().then(function() {
res.send(user);
}).catch(err => {
console.log(err);
});
}
The following code is stored in UserRouter.Js:
router.post('/publicUser', userService.createUser)
The code works, but the separation of concerns is not respected. How do I write the create function now with a callback function ?
My attempt looks like this:
UserService.js
function createUser() {
let user = new User
return user;
}
UserRoute.js
router.post('/publicUser',function(req,res,next){
let newOne=userService.createUser()
newOne.userID=req.body.userID
newOne.userName=req.body.userName
newOne.password=req.body.password
newOne.isAdministrator=req.body.isAdministrator
newOne.save().then(function() {
res.send(newOne);
}).catch(err => {
console.log(err);
})})
It works. But does a more elegant way exist ?
Below is the code to give you an idea of implementation. You can further enhance as per the complexity and requirements.
UserRouter.Js
// import service here
router.post('/publicUser', createUser)
async function createUser(req, res, next) {
try {
const response = await UserService.createUser(req.body);
res.send(response); // Enhance 'res' object here and return as per your requirement.
} catch (error) {
// log error
res.status(500).send(''); // Enhance 'res' object here with error and statuscode and return as per your requirement.
}
}
UserService.Js
async function createUser(body) {
// check emptiness if any for body and throw proper errors.
let userModelData = UserModel.getUserInsertPayload(body);
return UserRepository.save(userModelData);
}
UserRepository.js
// all code related to persistance. This is separate layer.
async function save(user) {
// Do any enhancement you need over the payload.
return User.save(user);
}
UserModel.js
// import User here. Create a payload related to User specific requirements.
function getUserInsertPayload(body) {
return new User({
userID: req.body.userID,
userName: req.body.userName,
password: req.body.password,
isAdministrator: req.body.isAdministrator
});
}
i am trying to upload multiple images from a form to a webpage but the problem is that it is creating new entries at each step of the for loop if i take it out of the loop then the req.body.ca.video1 becomes undefined. how do i create the entry only one time with all the images.
the post route
router.post("/n/v/n", upload.array('images'), middleware.isloggedin, async function(req, res) {
try {
req.body.ca.video1 = [];
for (var i = 0; i < req.files.length; i++) {
console.log(req.files[i].path);
cloudinary.v2.uploader.upload(req.files[i].path, { resource_type: "auto" }, function(error, result) {
req.body.ca.video1.push({
url: result.secure_url,
format: result.format
});
req.body.ca.author = {
id: req.user._id,
username: req.user.username
}
req.body.ca.created = new Date();
////if i get out of this then req.body.ca becomes undefined
const campground = await Campground.create(req.body.ca);
});
/////here the video1 goes undefined
}
req.flash("success", "it is uploading");
res.redirect("/c");
} catch (err) {
req.flash("error", err.message);
res.redirect("back");
}
});
That's because cloudinary.upload is async. Outside of the loop is executed before the request ends.
You need to wait for the request to finish to be able to access it.
Also you shouldn't declare new variables on the request body, unless you need them on another middleware on the pipeline.
I have 2 collections setup as below, Dates and Streets.
What I would like to achieve is to, query Streets by a param StreetName and look that up to find it's unique ID and then query the other collection by that ID to pull back all the dates that match.
My route is set up to /wasteDate/:StreetName. Here's what I have:
model.js
var DateSchema = new Schema({
date: {
type: Date
},
street_id: {
type: String,
}
});
var StreetSchema = new Schema({
name: {
type: String
}
});
routes.js
module.exports = function(app) {
var wasteCollections = require('../controllers/wasteController');
app.route('/wasteDate/:streetName')
.get(wasteCollections.get_dates_by_street_name);
};
controller.js
var mongoose = require('mongoose'),
ColDate = mongoose.model('Dates'),
that = this,
Street = mongoose.model('Streets');
(...)
exports.manual_get_dates_by_street = function (id) {
var wasteDates = ColDate.find({ street_id: id }).lean();
return wasteDates;
};
exports.get_dates_by_street_name = function (req, res) {
Street.find({
name: req.params.streetName
}, function(err, street) {
var query;
var theStreetId = street[0].id;
if (err) res.send(err);
query = that.manual_get_dates_by_street(theStreetId);
res.json(query);
});
};
at the moment i'm getting a circular reference error on the JSON.
I don't think I'm doing it the right way and think I may need to amend my schema?
Any help appreciated
You can either use (1) find twice or (2) aggregation.
Here's the first way:
exports.manual_get_dates_by_street = function (id, callback) {
// you are dealing with asynchronous operations, so you have to wait for the callback
// to execute before you can get the data
ColDate.find({ street_id: id }).lean().exec(callback);
};
exports.get_dates_by_street_name = function (req, res) {
// you are expecting one result, so use findOne instead of find
Street.findOne({ name: req.params.streetName }, function (err, street) {
// make sure you handle errors properly such as stopping execution of
// the next lines or else you may get unexpected errors
if (err)
return res.send(err);
// we pass a callback that will be executed once results (or an error) are found
that.manual_get_dates_by_street(street._id, function (err, dates) {
res.json({ dates: dates });
});
});
};
I never used it but I think mongoose-models may resolve your problem. https://github.com/SportZing/mongoose-models
Another possible approach is to put the second query function as a callback of the first.
Hi i am trying to use two selects in one JS file in node js and sql server. I am unable to figure out the syntax for this. I need a select to get all the persons from a table and another select to count the total number of persons in that table.Will it be possible to put those two selects in a single JS file. If so can someone help me with the syntax?
Here is the code i tried and i am getting the error
"cant Set headers after they are sent"
var sql = require("mssql");
var dbConfig = {
server: "XXXXX",
database: "XXXXX",
user: "XXXXX",
password: "XXXX",
port: 1433
};
exports.list = function(req, res){
sql.connect(dbConfig, function (err) {
if (err) console.log(err);
var request = new sql.Request();
request.query('select * from PERSON', function (err, recordset) {
if (err)
console.log(err)
else
console.log(recordset)
res.render('personinfo_itwx', { data: recordset });
});
request.query('select count(*) from PERSON', function (err, recordset) {
if (err)
console.log(err)
else
console.log(recordset1)
res.render('personinfo_itwx', { data: recordset1 });
});
});
};
#Aditya I'm not sure it's the best way to do so, although I would simply make two different requests, in order to achieve what you need. As I mentioned my in my comment, easiest way, would be to use (for instance) async library. And here's example you've asked for.
WARNING: I did not look at mysql docs
const async = require('async')
// {
async.series([
function(next)
{
new sql.Request()
.query('SELECT * from PERSON', next(err, resultList))
},
function(next)
{
new sql.Request()
.query('SELECT COUNT(*) from PERSON', next(err, count))
}
], (err, result) =>
{
/*
err: String
- if any of the shown above return an error - whole chain will be canceled.
result: Array
- if both requests will be succesfull - you'll end up with an array of results
---
Now you can render both results to your template at once
*/
})
// }
Surely, if you want manipulate with errors or results once you get them - you always may push error and results to new function, play with your data, and return the callback afterwards. Like so:
function(next)
{
new sql.Request()
.query('SELECT * from PERSON', (err, resultList) =>
{
if (err)
{
return next(err, null)
}
/*
data manipulation
*/
return next(null, resultList)
})
},
{ text: undefined,
done: false,
_id: 529e16025f5222dc36000002,
__v: 0 }
PUT /api/todos/529e16025f5222dc36000002 200 142ms - 68b
I keep getting this error when trying to do an update for my simple CRUD todo list. When I submit the update, the change doesn't appear on screen, although the put says it's a 200. Not sure what steps to take so that I don't get this "undefined" error and so I can have the update show up on screen.
EDIT: Included more code
This is the back-end node code:
app.put('/api/todos/:_id', function(req, res) {
Todo.findById(req.params._id, function(err, todos){
todos.text = req.body.text;
console.log(todos);
todos.save(function() {
if (!err) {
res.send(todos);
} else if (err) {
res.send(err);
}
Todo.find(function(err, todos) {
if (err)
res.send(err);
res.json(todos);
});
});
});
});
This is the Angular front-end code:
$scope.updateTodo = function(id) {
$scope.newItem = prompt("Please enter your new item:", "");
$http.put('/api/todos/' + id, {formData: $scope.newItem}).success(function(data) {
$scope.todos = data;
});
$http.get('/api/todos').success(function(data) {
$scope.todos = data;
});
};
I think it's because of this:
$http.put('/api/todos/' + id, { formData: $scope.newItem} )
^^^^^^^^
You're passing a single formData parameter with the request, yet in your Express code, you use this:
req.body.text
Either try this:
req.body.formData.text
Or don't use the formData parameter at all and pass $scope.newItem directly.
Besides that, your Express code is a bit messy: it might send back multiple responses and it doesn't check for errors on the save (as #PaulGray also pointed out).