I have a User controller that has a create method that checks the database for email and username uniqueness before creating the user (this is to work-around a bug in the mongodb adpater for SailsJS that doesn't honour the unique attribute flag - version 0.10.5).
The code looks like the following:
User.find({ email: req.body.email }, function (err, user) {
if(user) {
return res.badRequest('Unique email constraint. Email is already used.');
}
});
User.create(req.body).exec(function (err, user) {
// Code to catch and manage err or new user
}
What I expect is that if the email already exists in the database (mongodb), to send a 400 using res.badRequest(), then execution to end.
What happens is that the response is sent, but then control moves to User.create() - execution doesn't end. I suspect that return res.badRequest is returning control back to the calling function (User.findOne), and execution continues from there.
I tried using res.badRequest().end() but that leaves the client hanging (there is no response), and using res.end() after the return res.badRequest() generated 'header send' errors.
How do I have execution of this request end if an existing email is found?
First of all, your findOne is here a find. That's not related to your problem, but it is slightly confusing, and you should ensure you are getting data in the format you expect.
As for finishing the request after marking it bad, I have not used sails, but I was able to end execution in the past by using res.send(). EDIT: after looking at the docs, it seems this is done for you by .badRequest(), so ignore that part.
That said, even THAT is not actually your problem. Your problem is that you start an asynchronous User.find(), and then you immediately start running User.create() (also asynchronously), and so your request doesn't get marked bad until after you have already attempted to create a new user.
What you need to do is one of two things:
Use promises (NOTE: this is how it works for Mongoose; Sails may be different) to only run User.create() after User.find() has completed. e.g;
var userQuery = User.findOne({ email: req.body.email }).exec();
userQuery.addBack(function(err, user) {
if(!!user) res.badRequest('...');
else create_user();
});
Put your user creation logic inside of your findOne block. e.g.;
User.findOne({ email: req.body.email }, function(err, user) {
if (user) { // or perhaps you want if (!err)
User.create(...);
} else {
// handle error
}
});
Personally, I would advise that you use promises (especially later, when you have long chains of requests happening one on top of the other), but take your pick.
Related
Hopefully someone can help me solve what I am sure is a rookie mistake.
I am trying to adapt an authentication app originally based on mongodb, to work with sequelize\MSSQL instead, but getting tied up in knots with trying to blend a callback-based working example with
seqeulize's promised based approach.
Both MongoDb\Sequelize offer a findOne() method.
Original (working) code referencing MongoDb collection:
module.exports.getUserByUsername = function(username,callback){
var query = {username: username};
User.findOne(query,callback);
}
The callback in this case is from a separate calling module and is the standard verify password of passport.js's local-strategy.
Since the sequelize findOne() method expects a where clause I had hoped the following would be an out of the box solution:
module.exports.getUserByUsername = function(username,callback){
var query = {where: {username: username}};
User.findOne(query,callback);
}
This outputs a functional query into the console.log, but the callback doesn't fire, so the page hangs.
Looking at the respective API docs it appears that sequelize findOne() is exclusively promise based whereas MongoDb findOne() returns a promise only if where a callback function is not passed to the findOne() method, otherwise flow is handed to the callback when one is provided as is the case with the working example.
I tried the following adaptation to work with a sequelize promise (and quite a number of permutations thereof calling the callback function within the .then() clause etc)., but all fail with a hanging page: :
module.exports.getUserByUsername = function(username,callback){
var query = {where: {username: username}};
return User.findOne(query).then(user => {
console.log(user.get({ plain: true }));
return user.dataValues;
//callback(user.dataValues)
}).finally(() => {
console.log('done!')
});
}
The console.log(user.get()) spools out the correct details showing the database query executed correctly returning the required user data, so I feel that I'm very near to finding the right syntax to delivering this back to the passport callback.
Any help would be much appreciated!
Add raw property to true like this, and you can get the user object
User.findOne({ where : {username:username}, raw: true}).then( user => {
return user
})
I am encountering a weird issue here...
After I seem to successfully insert some data into my db.collection I cant seem to get it to reflect using db.collection.find().fetch().
Find below the code I insert into my chrome console:
merchantReviews.insert({merchantScore: "5.5"}, function() {
console.log("Review value successfully inserted");
});
This yields:
"9sd5787kj7dsd98ycnd"
Review value successfully inserted
I think returned value "9sd5787kj7dsd98ycnd" is an indication of a successful db collection insert. Then when I run:
merchantReviews.find().fetch()
I get:
[]
Can anyone tell me what is going on here?
Looking forward to your help.
There are two possibilities here: either the insert fails on the server even though it passes on the client, or you haven't subscribed to your collection.
In case the insert fails on server (most likely due to insufficient permissions, if you have removed the insecure package but have not declared any collection.allow rules), the client code still returns the intended insert ID (in your case, "9sd5787kj7dsd98ycnd"). The callback is called once the server has confirmed that the insert has either failed or succeeded. If it has failed, the callback is called with a single error argument. To catch this, you can instead insert the document like this:
merchantReviews.insert({merchantScore: "5.5"}, function(error) {
if (error) {
console.error(error);
} else {
console.log("Review value successfully inserted");
}
});
If this still logs successful insert, then you haven't subscribed to the collection, and you have removed the autopublish package. You can read about Meteor publish-subscribe system here. Basically, you have to publish the collection in server-side code:
Meteor.publish('reviews', function () {
return merchantReviews.find();
});
And in server code (or your js console) you need to subscribe to the collection with Meteor.subscribe('reviews'). Now calling merchantReviews.find().fetch() should return all documents in the collection.
I'm currently running SailsJS on a Raspberry Pi and all is working well however when I execute a sails.models.nameofmodel.count() when I attempt to respond with the result I end up getting a empty response.
getListCount: function(req,res)
{
var mainsource = req.param("source");
if(mainsource)
{
sails.models.gatherer.find({source: mainsource}).exec(
function(error, found)
{
if(error)
{
return res.serverError("Error in call");
}
else
{
sails.log("Number found "+found.length);
return res.ok({count: found.length});
}
}
);
}
else
{
return res.ok("Error in parameter");
}
},
I am able to see in the logs the number that was found (73689). However when responding I still get an empty response. I am using the default stock ok.js file, however I did stick in additional logging to try to debug and make sure it is going through the correct paths. I was able to confirm that the ok.js was going through this path
if (req.wantsJSON) {
return res.jsonx(data);
}
I also tried adding .populate() to the call before the .exec(), res.status(200) before I sent out a res.send() instead of res.ok(). I've also updated Sails to 11.5 and still getting the same empty response. I've also used a sails.models.gatherer.count() call with the same result.
You can try to add some logging to the beginning of your method to capture the value of mainsource. I do not believe you need to use an explicit return for any response object calls.
If all looks normal there, try to eliminate the model's find method and just evaluate the request parameter and return a simple response:
getListCount: function(req, res) {
var mainsource = req.param("source");
sails.log("Value of mainsource:" + mainsource);
if (mainsource) {
res.send("Hello!");
} else {
res.badRequest("Sorry, missing source.");
}
}
If that does not work, then your model data may not actually be matching on the criteria that you are providing and the problem may lie there; in which case, your response would be null. You mentioned that you do see the resulting count of the query within the log statement. If the res.badRequest is also null, then you may have a problem with the version of express that is installed within sailsjs. You mention that you have 11.5 of sailsjs. I will assume you mean 0.11.5.
This is what is found in package.json of 0.11.5
"express": "^3.21.0",
Check for any possible bugs within the GitHub issues for sailsjs regarding express and response object handling and the above version of express.
It may be worthwhile to perform a clean install using the latest sailsjs version (0.12.0) and see if that fixes your issue.
Another issue may be in how you are handling the response. In this case .exec should execute the query immediately (i.e. a synchronous call) and return the response when complete. So there should be no asynchronous processing there.
If you can show the code that is consuming the response, that would be helpful. I am assuming that there is a view that is showing the response via AJAX or some kind of form POST that is being performed. If that is where you are seeing the null response, then perhaps the problem lies in the view layer rather than the controller/model.
If you are experiencing a true timeout error via HTTP even though your query returns with a result just in time, then you may need to consider using async processing with sailjs. Take a look at this post on using a Promise instead.
I am struggling to receive pubsub events in my client. The client store (reflux) gets the data from a project using its id. As I understand it this automatically subscribes the Sails socket for realtime events (from version 0.10), but I don't see it happening.
Here's my client store getting data from sails
(this is ES6 syntax)
onLoadProject(id) {
var url = '/api/projects/' + id;
io.socket.get(url, (p, jwres) => {
console.log('loaded project', id);
this.project = p;
this.trigger(p);
});
io.socket.on("project", function(event){
console.log('realtime event', event);
});
},
Then I created a test "touch" action in my project controller, just to have the modifiedAt field updated.
touch: function(req, res){
var id = req.param('id');
Project.findOne(id)
.then(function(project) {
if (!project) throw new Error('No project with id ' + id);
return Project.update({id: id}, {touched: project.touched+1});
})
.then(function(){
// this should not be required right?
return Project.publishUpdate(id);
})
.done(function() {
sails.log('touched ok');
res.ok();
}, function(e) {
sails.log("touch failed", e.message, e.stack);
res.serverError(e.message);
});
}
This doesn't trigger any realtime event in my client code. I also added a manual Project.publishUpdate(), but this shouldn't be required right?
What am I missing?
-------- edit ----------
There was a complication a result of my model touched attribute, since I set it to 'number' instead of 'integer' and the ORM exception wasn't caught by the promise error handling without a catch() part. So the code above works, hurray! But the realtime events are received for every instance of Project.
So let me rephrase my question:
How can I subscribe the client socket to an instance instead of a model? I could check the id on the client side and retrieve the updated instance data but that seems inefficient since every client receives a notification about every project even though they only should care about a single one.
----- edit again ------
So nevermind. The reason I was getting updates from every instance is simply because at the start of my application I triggered a findAll to get a list of available projects. As a result my socket got subscribed for all of them. The workaround would be to either initiate that call via plain http instead of a socket, or use a separate controller action for retrieving the list (therefor bypassing the blueprint route). I picked the second option because in my case it's silly to fetch all project data prior to picking one.
So to answer my own question. The reason I was getting updates from every instance is simply because at the start of my application I triggered a findAll to get a list of available projects. As a result my socket got subscribed for all of them.
The workaround would be to either initiate that call via plain http instead of a socket, or use a separate controller action for retrieving the list (therefor bypassing the blueprint route). I picked the second option because in my case it's silly to fetch all resources data prior to selecting one.
Here's the function I used to list all resources, where I filter part of the data which is not relevant for browsing the list initially.
list: function(req, res) {
Project.find()
.then(function(projects) {
var keys = [
'id',
'name',
'createdAt',
'updatedAt',
'author',
'description',
];
return projects.map(function(project){
return _.pick(project, keys);
});
})
.catch(function (e){
res.serverError(e.message);
})
.done(function(list){
res.json(list);
}, function(e) {
res.serverError(e.message);
});
},
Note that when the user loads a resource (project in my case) and then switches to another resource, the client is will be subscribed to both resources. I believe it requires a request to an action where you unsubscribe the socket explicitly to prevent this. In my case this isn't such a problem, but I plan to solve that later.
I hope this is helpful to someone.
I have created a simple, minimalistic diary app.
On the client, I use
Meteor.subscribe('entries', Meteor.userId());
to subscribe to the entries created by the user (stored in a mongodb collection). I pass the users ID to the publish function (on the server):
Meteor.publish('entries', function(userID) {
return Entries.find({userId: userID});
});
After login, Meteor.userId() isn't falsy anymore, because it's a reactive data source. However, the relevant data is not being published. I fixed that by auto-running the subscribe function:
Tracker.autorun(function() {
Meteor.subscribe('entries', Meteor.userId());
});
It works, but I feel it's a bad solution.
So here comes the question:
How should one publish user-relevant data in general? There must be a better way to do this, than passing the users ID to the publish-function. Also, isn't it insecure?
By the way, would love to hear some feedback on the app
You don't need to pass the userId from the subscription. Inside the publish function you can use this.userId to get the current user. You can also just return an empty array if the user is not logged in.
Meteor.publish("entries", function () {
if (!this.userId) return [];
return Entries.find({ userId: this.userId });
});