Create a method not limited by subscriptions - meteor

I've created a method that checks if an email already has an account:
insertGroupMember: function(eventId, memberDetails) {
var memberAccount = Meteor.users.findOne({'emails.address': memberDetails.email});
if (memberAccount) {
console.log('Existing User')
} else {
console.log('Create User')
}
}
But will only receive a result when I am subscribed to a publication with all users.emails. How can I achieve the same results without having to publish everyone's email? I think thats kind of bad for security/privacy, right?

You are correct - you don't want to publish all of the users to the client just to accomplish this. The best solution is to create a method defined only on the server, and then call it from the client.
server/methods.js
Meteor.methods({
insertGroupMember: function(eventId, memberDetails) {
...
}
});
client/someTemplate.js
Meteor.call('insertGroupMember', eventId, memberDetails, function (err, result){
...
});

Related

Meteor wrapAsync

I'm trying to implement the following scenario:
1. Client calls a meteor-method.
2. Inside the meteor-method i make an HTTP-Post to a different server.
3. When the HTTP-Call is responded, the meteor method should return true and in the case an error occurs it should return false.
Here is what my meteor method looks like:
uploadUserImage: function(data_url,userid) {
asyncfnc =function(data,uid){
HTTP.post("http://localhost:2000/upload", {
data: {
"data_url": data,
"user_id": uid
}
},function(err,res){
console.log(res);
if (err){
console.log("error");
throw new Error(err.message);
}
else{
console.log("return true");
return true;
}
});
};
var waitForResult = Meteor.wrapAsync(asyncfnc);
var result = waitForResult(data_url,userid);
return result;
}
The HTTP-Call works and I also get into the Callback of the HTTP.post-function.
But on the clientside where I called the meteor-method i don't get into my callback-function. It looks like this:
Meteor.call("uploadUserImage",data_url,Session.get("newUserID"),function (err, res) {
if(err){
console.log(err);
} else {
console.log('response: ', res);
}
});
What am I doing wrong? Why is my meteor-method not returning anything?
Is everything correct with my Meteor.wrapAsync()?
Thanks for your help!
I found a solution, which does not require Meteor.wrapAsync().
var url = "http://localhost:2000/upload";
//synchronous GET
var result = Meteor.http.post(url,{
data: {
"title": "i want to upload a picture",
"data_url": data_url,
"user_id": userid
},timeout:30000});
if(result.statusCode==200) {
console.log(result);
console.log("response received.");
return result;
} else {
console.log("Response issue: ", result.statusCode);
var errorJson = JSON.parse(result.content);
throw new Meteor.Error(result.statusCode, errorJson.error);
}
This makes the HTTP-Post-Call synchronous, so there is no need to wrap async.
You are asking too much in this situation.
Meteor methods can be called synchronously, but it's not advisable if the method is doing a remote call like this.
My feeling is that you are hanging on to a procedural programming model where you want a synchronous result to 1) a call to your server, and 2) a request sent to another remote server. And you want to get a return value from your call. It doesn't work like that.
Meteor protects you to a large degree from dealing with asynchronicity, but sometimes you have to accept that a little more work is required to deal with it correctly.
So my recommendation is to use callbacks for notification.

How to validate data in onCreateUser without losing the form data?

I'm writing an Appliction using Meteor. In this App I want to implement a server-side validation of the user data using Accounts.onCreateUser. There is some data passed which can only be verified on the server side.
At client side I call:
Template.register.events({
'submit form': function (e) {
e.preventDefault();
var attributes = {
username: $("#inputUsername").val(),
password: $("#inputPassword").val(),
confirmation: $("inputConfirmation").val(),
email: $("#inputEmail").val(),
...
};
Accounts.createUser(attributes, function(err){
if (err) {
throwError(err);
} else {
}
});
}
});
And on the server side:
Accounts.onCreateUser(function(options, user) {
if(!verifyData(options))
throw new Meteor.Error(403, "Wrong input");
return user;
});
After the server side verification fails, all form data is lost. What is the best way to keep the data?
I went ahead and reproduced your code on a Meteorpad and from what I can tell, the form data does still persist. You just need to access it via the attributes variable in the client-side.
There may be something I am missing, but i took what you posted above and put it in there.

Add extra user field

In my Meteor app I use the default accounts package, which gives me the default login and registration functionality. Now I want to add an extra field to user, say nickname, and for the logged in user the possibility to edit this information.
For editing the profile I suppose I should be doing something like this:
Template.profileEdit.events({
'submit form': function(e) {
e.preventDefault();
if(!Meteor.user())
throw new Meteor.Error(401, "You need to login first");
var currentUserId = this._id;
var user = {
"profile.nickname": $(e.target).find('[name=nickname]').val()
};
Meteor.users.update(currentUserId, {
$set: user
}, function(error){
if(error){
alert(error.reason);
} else {
Router.go('myProfile', {_id: currentUserId});
}
});
}
});
But I doesn't store the info if I look in Mongo. Also when showing the profile, {{profile.nickname}} returns empty. What is wrong here?
Edit: added collections\users.js to show permissions:
Meteor.users.allow({
update: function (userId, doc) {
if (userId && doc._id === userId) {
return true;
}
}
});
Meteor.users.deny({
update: function(userId, user, fieldNames) {
return (_.without(fieldNames, 'profile.nickname').length > 0);
}
});
Yeah, I believe that should do the job, although I haven't actually run the code. The idea is certainly right.
The main things to be aware of are:
The necessity to allow the user doc to be edited from the client with an appropriate Meteor.users.allow() block on the server, assuming you're going to remove the "insecure" package (which you need to before doing anything in production).
The fact that "by default the server publishes username, emails, and profile", so you'll need to write a Meteor.publish function on the server and subscribe to it if you want to expose any other fields within the user document to the client once you've removed the "autopublish" package (which again, you really should).

meteor.js : find users by email

In my meteor.js app, I'm trying to write a simple admin page which can find a user by his/her email address.
I can see that in the Meteor.users collection there is an 'emails' array, which has objects like so
{ address : 'foo#foo.com',
verified : false
}
Normally in Mongodb I can search inside this 'emails' array like so :
Meteor.users.find({ emails.address : 'foo#foo.com' });
But this query is throwing an error :
While building the application:
client/admin.js:224:41: Unexpected token .
Aka Meteor doesn't like the nested query...
Any ideas on how to query the Meteor.users collection by email address ?
You can also use what you had, just put it in quotes:
Meteor.users.find({ "emails.address" : 'foo#foo.com' });
If on the server, Meteor has a special function for this :
Accounts.findUserByEmail(email).
I believe this is the recommended way.
Emails holds an array of emails. Each email has an address.
Try { emails: { $elemMatch: { address: "foo#foo.com" } } }.
Information on $elemMatch is here.
Information on emails as an array is here.
By default, Meteor only publishes the logged in user and you can, as you mention, run queries against that user. In order to access the other users you have to publish them on the server:
Meteor.publish("allUsers", function () {
return Meteor.users.find({});
});
And subscribe to them on the client:
Meteor.subscribe('allUsers');
And run the following command
Meteor.users.find({"emails": "me#example.com"}).fetch()
OR
Meteor.users.find({"emails.0": "me#example.com"}).fetch()
Refer this
If you want to find all emails inside Accounts array, and do an insensitive query:
const hasUser = Meteor.users.findOne({
emails: {
$elemMatch: {
address: {
$regex : new RegExp(doc.email, "i")
}
}
}
});
One possible workaround, if this works on the server but not the client, is to use a users_by_email method on the server:
if (Meteor.isServer) {
Meteor.methods({
'get_users_by_email': function(email) {
return Users.find({ emails.address: email }).fetch();
}
});
}
if (Meteor.isClient) {
foo_users = Meteor.call('get_users_by_email', 'foo#bar.baz');
}

Dealing with context of server responses in realtime web applications

Finding it hard to describe this issue - so please edit if you know more relevant terms.
I'm building a web application which essentially uses Redis (PubSub) + Node.js + Socket.IO as a distribution server.
I have two-way communication working with no issues - but I need to be able to make a request to the server from the client (asynchronously) and deal with the response while still processing other irrelevant responses that might come in before it.
This is what I have so far, but I'm not particularly happy with this approach:
Server
// Lots of other code
redis.psubscribe('*');
redis.on("pmessage", function(pattern, channel, message) {
// broadcast
});
io.on('connection', function(client) {
client.on('message', function(message) {
switch(message.method) {
// call relevant function
}
});
});
function object_exists(object_id) {
// do stuff to check object exists
client.send({method: 'object_exists', value: object_exists});
}
Client
var call = Array();
$(document).ready(function() {
socket.connect();
socket.on("message", function(obj){
console.log(obj);
call[obj.method](obj.value);
});
});
function object_exists(object_id) {
socket.send({method: 'object_exists', value: object_id});
// Set a function to be called when the next server message with the 'object_exists' method is received.
call['object_exists'] = function(value) {
if(value) {
// object does exist
}
}
}
tl;dr: I need to 'ask' the server something and then deal with the response using Socket.IO.
You don't specifically say why you are unhappy with your approach, but it looks to me like you are almost there. I am not really sure what you are trying to do with the call array, so I just took it out for clarity.
Basically, you just need to set up a switch statement to act as a message router on each side of the socket connection and fire off the appropriate methods based in incoming messages. Send enough state with the message itself so you can handle the work without any additional context. In your reworked code, I send the object_id to the server and back again to the client.
///SERVER
// Lots of other code
redis.psubscribe('*');
redis.on("pmessage", function(pattern, channel, message) {
// broadcast
});
io.on('connection', function(client) {
client.on('message', function(message) {
switch(message.method) {
case 'object_exists':
object_exists(message.objectId);
break;
}
});
});
//Takes an id an returns true if the object exists
function object_exists(object_id) {
// do stuff to check object exists
client.send({method: 'object_exists', objectId: object_id, value: object_exists});
}
///CLIENT
$(document).ready(function() {
//setup the message event handler for any messages coming back from the server
//This won't fire right away
socket.on("message", function(message){
switch(message.method) {
case 'object_exists':
object_exists(message.objectId, message.value);
break;
}
});
//When we connect, send the server the message asking if object_exists
socket.on("connect", function() {
socket.send({method: 'object_exists', objectId: object_id});
});
//Initiate the connection
socket.connect();
});
//Get's called with with objectId and a true if it exists, false if it does not
function object_exists(objectId, value) {
if(value) {
// object does exist, do something with objectId
}
else {
// object does not exist
}
}
If you want to see a bunch more code in the same stack doing work similar to what you are trying to accomplish, check out my nodechat.js project.

Resources