I'm a complete newbie, and I've been fiddling with the Meteor 1.0 sample todo list app to connect google oauth to it.
When I do so the page no longer renders properly because {{username}} is not set at all.
https://docs.meteor.com/#/full/meteor_users says "username: a unique String identifying the user." but the oauth stuff doesn't create one for you.
Connect service to existing meteor account talks about linking an already existing account to another service, but in this case I just want to use the external service.
https://stackoverflow.com/questions/25182903/meteor-facebook-registration uses onCreateUser() to manually set
user.username = user.services.facebook.name
but this isn't portable across services nor to guarantee uniqueness.
https://github.com/aldeed/meteor-collection2 defines the User schema so that username is mandatory.
When I dump the users table (some fields removed) the google account doesn't have a username, and there is no field that can really take on that value automatically as there could be a clash. Email could be used but I'd rather the username wasn't the email address. Do I just force the user to specify a desired username?
meteor:PRIMARY> db.users.find()
{
"_id" : "YNWt2cATMsKFG7od6",
"createdAt" : ISODate("2014-11-05T11:08:00.406Z"),
"services" : {
"password" : {
},
},
"username" : “a_user”
}
{
"_id" : "CyQsJqcez3kWTRHyQ",
"createdAt" : ISODate("2014-11-05T12:09:40.139Z"),
"profile" : {
"name" : “Alice User”
},
"services" : {
"google" : {
"email" : “a_user#example.com",
"family_name" : “User”,
"gender" : “female",
"given_name" : "Alice”,
"id" : "1115",
"name" : “Alice User,
}
}
}
What is the correct way of handling this?
This is how I did it myself with facebook and google
Accounts.onCreateUser(function (options, user) {
if (options && options.profile) {
user.profile = options.profile;
}
if (user.services) {
var service = _.pairs(user.services)[0];
var serviceName = service[0];
var serviceData = service[1];
console.log("serviceName", serviceName)
if (serviceName == "facebook") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.first_name, "last_name": serviceData.last_name, "avatar": getFbPicture(serviceData.id)};
}
else if (serviceName == "google") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.given_name, "last_name": serviceData.family_name, "avatar": getGooglePicture(serviceData.id)};
}
}
console.log("user created :", user)
return user;
});
I do not use username but I use email so that I'm sure that it will be unique. After that
I could allow the user to set his username or display name like Stackoverflow or other services do.
However you could use the email as username and again let the user change it later.
In my application, I am using this to handle the same problem.
username = user.services.facebook.name
user.username=generateUsername(username)
generateUsername = function(username) {
var count;
username = username.toLowerCase().trim().replace(" ", "");
count = Meteor.users.find({"profile.un": username}).count();
if (count === 0) {
return username;
}
else {
return username + (count + 1)
}
This is will create a unique username. After successful signup you can allow the users to change the username and check your db for its existence.
In my application I use
if(user.services.facebook)
this.user = user.services.facebook.name
if(user.services.google)
this.user = user.services.google.name
Related
I want to access users emails in from the client,
here is what I have done, in the server side:
Meteor.publish('userList', function (){
return Meteor.users.find({}, {fields:{emails: 1, profile: 1}});
});
In the client side:
Template.usersManagement.onCreated(function () {
var self = this;
self.autorun(function() {
self.subscribe('userList');
});
});
And the template helper to retrieve the users:
Template.usersManagement.helpers({
allUsers: function() {
console.log(Meteor.users.find({}).fetch())
return Meteor.users.find({}).fetch()
}
})
In the usersManagement template:
{{#each allUsers}}
<h1>{{this.emails.address}}</h1>
<h1>{{this.profile.name}}</h1>
{{/each}}
The users names are displayed but the email isn't and no errors showing in the console.
Here is the look on how the users stored in the database:
{
"_id" : "m7admvZc32Jr3SeiE",
"createdAt" : ISODate("2017-12-27T21:24:48.927Z"),
"services" : {
"password" : {
"bcrypt" : "$2a$10$wv6KsRp6s91A.0mHH89Q0eT3jrZmJjKJhw8SIH9c8c8OpwMrXyGMC"
}
},
"emails" : [
{
"address" : "222#222.com",
"verified" : false
}
],
"profile" : {
"name" : "222",
"createdAt" : ISODate("2017-12-27T21:24:48.707Z"),
"updatedAt" : ISODate("2017-12-27T21:24:48.707Z"),
"group" : "admin"
},
"status" : {
"online" : false
}
}
My question is, how can I retrieve the emails of the users?
EDIT
Here is how the emails are retrieved (from the console):
There is another field (0) below the emails. I tried this.emails.0.address it didn't work (desperate attempt)
Your desperate attempt almost nailed it, try this:
this.emails.[0].address
An item in the Meteor.users collection store emails as an array, for you to be able to store more than one email-address per user. So since it is so, the right way would be:
<h1>{{this.emails[0].address}}</h1>
I have an app with checklists where I want to be able to share checklists with other users. Those users might not be registered yet in my app. Currently my database is structured as follows:
{
"checklists" : {
"-KQKfnuGEhhoSIXyFj71" : {
"admins" : [
{ "C9fdXO6jWTZIxzgbhy0K10TEBqx1" : true },
{"ZQKHTbTlVMQwnZzFkQUcJF84SaA3" : true }
],
"users" : {
"-KQKfnuGEhhoSIXyFj74" : {
"email" : "admin1#example.com",
"uid" : "C9fdXO6jWTZIxzgbhy0K10TEBqx1",
"active" : true,
"name" : "Moses"
},
"-KQKfnuGEhhoSIXyFj75" : {
"email" : "admin2#example.com",
"uid" : "ZQKHTbTlVMQwnZzFkQUcJF84SaA3",
"active" : true,
"name" : "John"
},
"-KQKfnuGEhhoSIXyFj76" : {
"email" : "user1#example.com",
"uid" : "1ZxxYFGzoCPIc7Am07GVRxSN7xT2",
"active" : true,
"name" : "Kate"
},
"-KQKfnuGEhhoSIXyFj77" : {
"email" : "user2#example.com",
"active" : false
}
},
"data" : {
"task1" : "hello",
"task2" : "good bye"
}
}
}
}
My bolt file is as follows:
// admin can do both read/ write to the while checklist
path /checklists/{checklistId} {
read() { isChecklistAdmin(checklistId) }
write() { isChecklistAdmin(checklistId) }
}
// users of checklist can do read/write on data only
path /checklists/{checklistId}/data {
read() { isChecklistUser(checklistId) }
write() { isChecklistUser(checklistId) }
}
// users of checklist can add/ remove themselves from the list by updating active flag
path /checklists/{checklistId}/users/{checklistUserId} {
// all checklist users can read other users
read() { isChecklistUser(checklistId) }
// only admin (implicit) or the user can accept/reject (active) invitation or update their details
write() { isChecklistUser(checklistId) && ref.parent().email == auth.email }
}
// is authenticated user in the list of checklist admins?
isChecklistAdmin(checklistId) { isSignedIn() && root.checklists[checklistId].admins[auth.uid] != null }
// is authenticated user in the list of checklist users (test email ==)
isChecklistUser(checklistId) { isSignedIn() && root.checklists[checklistId].users['*'].email == auth.email }
isSignedIn() { auth != null }
The flow I had in mind was:
Admin adds a node under users which has the email of the invited user
Admin sends an email invite (I would have liked to use Firebase invites but I don't see how to integrate it in this scenario - would love suggestions).
User registers/ logins and updates active flag to true
My question: if you look at the isChecklistUser function, you will find I am using a "wildcard" ("*") since I do not know what that value is. Admin cannot set it at invitation since the user might not be registered yet.
I am having 2 applications 'service[server-app]' and 'website[client-app]' connected with meteor cluster and I connected 'service' application with mongodb. I am using accounts-ui for login and signup through 'website' application. Below provided is my code and is this the right way to do so
website application
Accounts.onCreateUser(function(options, user) {
var usr = ddpconn.call('CreateUser', user); // ddp cluster connection to the service app
return usr;
});
service application
Meteor.methods({
CreateUser: function (user) {
var invited = Invitations.findOne({"user_id":user._id});
if(invited){
// Updates the subdoc of invited user
Meteor.users.update({"_id": invited.user_id,"rules._id":usr.rules[0]._id},{ $inc:
{"rules.$.lineup": 1} });
Invitations.update({"user_id":invited.user_id},{$set:{"updated_at":new Date()}});
// Adds a rules subdoc to the user which creates an account
user.rules = [ { "_id" : new Mongo.ObjectID()._str, "lineup" :1} ]
return user;
}
} else {
// Adds a rules subdoc to the user which creates an account
user.rules = [ { "_id" : new Mongo.ObjectID()._str, "lineup" : 1} ]
return user;
}
}
});
Right now I have a working messaging system developed in Meteor where users can send private messages to each other.
The server looks like this:
// .. lot of code
Meteor.publish("privateMessages", function () {
return PMs.find({ to: this.userId });
});
PMs.allow({
insert: function(user, obj) {
obj.from = user;
obj.to = Meteor.users.findOne({ username: obj.to })._id;
obj.read = false;
obj.date = new Date();
return true;
}
});
// .. other code
When the user subscribes to privateMessages, he gets a mongo object that looks like this:
{ "to" : "LStjrAzn8rzWp9kbr", "subject" : "test", "message" : "This is a test", "read" : false, "date" : ISODate("2014-07-05T13:37:20.559Z"), "from" : "sXEre4w2y55SH8Rtv", "_id" : "XBmu6DWk4q9srdCC2" }
How can I change the object to return the username instead of the user id?
You need to do so in a way similar to how you changed username to _id. You can create a utility function:
var usernameById = function(_id) {
var user = Meteor.users.findOne(_id);
return user && user.username;
};
Edit:
If you don't want to poll minimongo for each message, just include username instead of _id inside your message object. Since username is unique, they will be enough.
If in your app you allow users to change username, it might be a good idea to also keep the _id for the record.
In one of larger apps I've been working with we kept user's _id in the model (to create links to profile etc.), as well as cached his profile.name (for display purposes).
I suggest adding the collection helpers package from atmosphere. Then create a helper for PMs called toUser that returns the appropriate user.
Then you can get the name using message.user.name.
I am using Meteor and people can connect to the site via Facebook. I'm using people username to identify them. But, some of them don't have a username. For example, a new user has null as a username. What I'm trying to do is, if the person has a username then OK we use their username. If not, I wanna use their Facebook id as their username. The problem is, my if condition is not working properly. If the person has a username, the if condition considers that the person doesn't. The weird thing is, if I do a console.log of the username before the if condition, it will show the username. But once in the if, it considers that the username is null. Here is the code :
Accounts.onCreateUser(function(options, user) {
var fb = user.services.facebook;
var token = user.services.facebook.accessToken;
if (options.profile) {
options.profile.fb_id = fb.id;
options.profile.gender = fb.gender;
options.profile.username = fb.username
console.log( 'username : ' + options.profile.username);
if ( !(options.profile.username === null || options.profile.username ==="null" || options.profile.username === undefined || options.profile.username === "undefined")) {
console.log('noooooooo');
options.profile.username = fb.id;
} else {
console.log('yessssssss');
options.profile.username = fb.username;
}
options.profile.email = fb.email;
options.profile.firstname = fb.first_name;
user.profile = options.profile;
}
sendWelcomeEmail(options.profile.name, options.profile.email);
return user;
});
With this code, if I log in with my Facebook that has a username. The condition will show 'noooooooo' but the console.log( 'username : ' + options.profile.username); will show my username. Why does it do that? :l
It's because creating is called before logging and logging is asynchronous .. so you cannot ensure that your if will be or will not true/false. Your putting information from fb service is redundant, because all these informations are already saved with user.
http://docs.meteor.com/#meteor_user
You should obtain information about user after he is loggedIn because in that moment you will be able to recognise what kind of identifier you can use username/id.
//Server side
Meteor.publish("userData", function () {
return Meteor.users.find({_id: this.userId});
// You can publish only facebook id..
/*return Meteor.users.find({_id: this.userId},
{
fields: {
'services.facebook.id': true
}
}
);*/
});
//Client side
Meteor.subscribe("userData");
// .. you can see more informations about logged user
console.log(Meteor.users.find({}).fetch());