Meteor private messaging between users - meteor

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.

Related

Is Firebase Database searchable using objects instead of references, with AngularFire $firebaseArray $keyAt(recordOrIndex)?

After a user logs in using $firebaseAuth, Google sends the user's displayName, email, and photoURL. I then want to look up the user's account in my Firebase database. I can't use $getRecord(key) because Google doesn't tell me the user's key. It appears that I should use $keyAt(recordOrIndex), and then use $getRecord(key). $keyAt(recordOrIndex) works fine with an index. $keyAt(recordOrIndex) works fine with a record that I retrieved with $getRecord(key). I can't get $keyAt(recordOrIndex) to work with an object that I made from the user data that Google returned using $firebaseAuth.
I tried both the complete object (displayName, email, photoURL) and an object consisting of only the email address. The latter is what I would prefer to use. Neither worked.
app.controller('LoginModalInstanceCtrl', ['$scope', '$location', '$uibModalInstance', '$firebaseArray', '$firebaseObject', '$firebaseAuth', function($scope, $location, $uibModalInstance, $firebaseArray, $firebaseObject, $firebaseAuth) {
// Create Firebase3 reference
var ref = firebase.database().ref();
// Set up Firebase Auth
$scope.authObj = $firebaseAuth();
var authData = $scope.authObj.$getAuth();
$scope.authData = authData;
// Google OAuth login handler
$scope.loginGoogle = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$signInWithPopup("google")
.then(function(authData) {
$scope.authData = authData;
console.log(authData);
console.log("Your displayName is:", authData.user.displayName);
console.log("Your email is:", authData.user.email);
console.log("Your photoURL is:", authData.user.photoURL);
var record = {
displayName: authData.user.displayName,
email: authData.user.email,
photoURL: authData.user.photoURL
};
var emailObject = {
email: authData.user.email
};
// look up account
var users = $firebaseArray(ref.child('users'));
users.$loaded()
.then(function() {
console.log("Array loaded!");
var key1 = users.$keyAt(1);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var record = users.$getRecord(key1);
console.log(record); // Object with displayName, email, and photoURL
var key1 = users.$keyAt(record);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var objectKey = users.$keyAt(object);
console.log(objectKey); // null
var emailKey = users.$keyAt(emailObject);
console.log(emailKey); // null
});
$uibModalInstance.close(); // close modal window
$location.path('/languagetwo/'); // return to the homepage
}).catch(function(error) {
console.error("Authentication failed:", error);
});
};
Should I use $firebaseObject instead of $firebaseArray:
var user = $firebaseObject(ref.child('users').child( SOMETHING HERE? ));
The answer appears to be no, you can't search Firebase Database using AngularFire. (Maybe AngularFire 2 has search, I didn't look.) What I did instead was to use "plain vanilla" Firebase:
var users = firebase.database().ref('users');
users.orderByChild('email').equalTo(authData.user.email).once('value').then(function(snapshot) {
console.log(snapshot.val());
});
The first line sets up the Firebase ref and is the similar to as before, except that I'm going straight to the users array, instead of using $FirebaseArray to get to the users array.
The second line is a completely different syntax. First, you have to specify the order that you want the returned object to be in. Yes, it returns an object, not an array. I tried snapshot.val().length() and found that it's not an array. What orderByChild('email') does is to access the 'email' property of the objects in the 'users' array.
Next we do the query. equalTo(authData.user.email) returns only the objects in which the email address from $FirebaseAuth equals the email address in our 'users' array.
Next, once('value') creates a promise and waits for the async data. I tried using on() but couldn't get it to work, too many arguments or something. once() requires an argument, which can be value, child_added, child_changed, child_removed, or child_moved. The value argument is for getting data from a location without changing the child nodes.
We can then set up our then promise fulfillment. You can call the returned data anything. Here it's called snapshot.
Lastly snapshot.val() provides the data from the database, looking just like it does in the Firebase Console.

Firebase - How to permanently save user profile?

Firebase Console only allows to set email address and password, there is no option to save user's profile but this can be done using code:
user.updateProfile({
displayName: "Chinmay Sarupria"
}).then(function() {
console.log(user.displayName);
}, function(error) {
console.log(error);
});
If this is the way to save user data permanently then it is impossible to write code for every user just to save their displayName like this or is doing via code permanent, at the moment it is working for me but I'm not sure if it will remain like that forever.
Ofcourse, I could save the user data in realtime database and then fetch it based on user's uid but if saving user data in the user variable is possible then that is much better than getting the data from database.
After you reference your user object you can update values under the UID for that user.
var rootRef = new Firebase('https://yourapp.firebaseio.com');
// Check the current user login status and redirect if not logged in
var user = rootRef.getAuth();
if (user) {
var user = rootRef.getAuth();
var userRef = rootRef.child('users').child(user.uid);
... do something with the logged in user...
}
function writeData () {
var user = rootRef.getAuth();
var userRef = rootRef.child('users').child(user.uid);
var profileRef=userRef.child('profile').push();
profileRef.update ({
name: "Tony",
position: "Developer"
});
};
This should give your user profile a structure something like this:
}
"users" : {
"067f75bf-4a07-473e-82e5-d9a5ee11be17" : {
"profile" : {
"-KN2dG5X4lLpp0fwfsXK" : {
"name" : "Tony",
"position" : "Developer"
}
}
}
}
Note the push() function gives you the randomly generated key. You may not need it.
Hope this helps.

meteorjs meteor-user-status mizzao package: cannot get other users

Trying to implement mizzao/meteor-user-status user status package, but only getting the status for the currently logged in user (me). Others show undefined.
Code in /server folder:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
});
});
Code in /client folder:
Meteor.subscribe('userStatus');
Template.member.helpers({
isOnline: function(username) {
console.log(username); //gets each username, not just logged me logged in
var currentUser = Meteor.users.findOne({
username: username
});
console.log(currentUser.status.online); //gets undefined for other users other than me
return currentUser.status.online; //returns undefined for other users
}
});
Maybe this needs to reference "userStatus" somehow instead of Meteor.users.findOne(...), but since global object was not created, e.g. like UsersOnline = new Mongdb.Collection - I don't know how to implement this.
Getting error in Chrome debugging tool: TypeError: Cannot read property 'status' of undefined, obviously, it only gets currently logged in user.
Edit: To clarify: I get other users from a custom "profiles" collection in Mongo that I created specifically to overcome the issue of not being able to read "users" collection, due to security, I guess. So I get the list of profiles and supply the user name to this function that supposed to use mizzao package to see if they are online. Since the package reads from "users" collection, it only returns current user (me).
I would be totally OK if I could modify the package for it to write to my "profiles" collection, if someone suggests how to do that.
Another update:
I ran the count on the server, and it returns only one (me, I guess).
var cursor = Meteor.users.find({
"status.online": true
}, {
fields:{
_id: 1,
status: 1,
username: 1
// Any other fields you may need
}
});
console.log(cursor.count());
If you have a publication like this:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
});
});
it will return users that are online only. So you get undefined for users other than you, because they are not logged-in.
Try logging in as a different user at the same time (i.e. using different browser), and see whether you can now see the status of that user.
You can always change your publication to:
Meteor.publish('userStatus', function() {
return Meteor.users.find({}, {fields: {username : 1, status : 1}});
});
and you will get statuses for all users. But if you only need to check the "online" flag (i.e. you don't need other features of the package), it is better to only send data about the online users and consider not published user as offline.
I guess you trying to read users before subscription is ready.
Server:
Meteor.publish('userStatus', function() {
return Meteor.users.find({
"status.online": true
}, {
fields:{
_id: 1,
status: 1,
profile: 1,
username: 1
// Any other fields you may need
}
});
});
Client (you should return cursor, which will re-run once subscription is ready):
Meteor.subscribe('userStatus');
Template.member.helpers({
isOnline: function(username) {
console.log(username); //gets each username, not just logged me logged in
var cursor = Meteor.users.find({username: username});
if(cursor.count()){
var currentUser = cursor.fetch()[0];
if(currentUser.status && currentUser.status.online){
console.log(currentUser.status.online); //gets undefined for other users other than me
return currentUser.status.online; //returns undefined for other users
}
}
return {};
}
});

Modify data in Meteor.publish before sending down to client

I have the following use case:
I have a users table in MongoDB on the backend, which is a separate service than the frontend. I use DDP.connect() to connect to this backend service.
Each user has a set of "subjects"
Each subject in the users table is referenced by id, not name. There is a separate table called "subjects" that holds the subjects by id.
I want to publish the user down to the client, however I want the published user to be populated with the subjects first.
I tried the following, inspired by this blog post:
// in the backend service on port 3030
Meteor.publish('users', function(userId) {
var _this = this;
Meteor.users.find({
_id: userId
}).forEach(function(user) {
user.profile = populate(user.profile);
console.log(user);
_this.changed('users', userId, {
_id: userId,
profile: user.profile
});
});
_this.ready();
});
// in the client
var UserService = DDP.connect('http://localhost:3030');
var UserServiceProfile = UserService.subscribe('users', Meteor.userId());
console.log(UserServiceProfile);
This gives the following error on the backend:
Exception from sub users id akuWx5TqsrArFnQBZ Error: Could not find element with id XhQu77F5ChjcMTSPr to change.
So I tried changing _this.changed to _this.added. I don't get any errors, but the changes aren't showing up in the client minimongo, even though I can see that the populate function worked through the console.log(user) line.
I'm not sure how you'd fix your code, but you might not need to. It looks like you want the https://atmospherejs.com/maximum/server-transform package.
Add the server-transform package to your project, and replace your code with this (I'm going to assume you also have underscore added to your project, and the subjects collection in your database corresponds to a global variable called Subjects in your code.):
Meteor.publishTransformed('users', function(userId) {
return Meteor.users.find({
_id: userId
}).serverTransform({
'profile.subjects': function(doc) {
var subjects = [];
_(doc.profile.subjects).each(function(subjectId) {
subjects.push(Subjects.findOne(subjectId));
});
return subjects;
}
});
});

Meteor - How To Extract Key Name From Collection?

I have the following in my initialize file to get the values loaded in the database on startup:
Meteor.startup(function() {
if(typeof Person.findOne() === 'undefined') {
Person.insert({
name: "",
gender: ["male", "female", "prefer not to say"],
age: 0
});
}
});
And then in the server/abc.js I have:
Meteor.methods({
checkPerson: function (input) {
for (var key in Person) {
if (input === key) {
...
}
}
}
});
This meteor method checkPerson is called in the client side with a string value being passed as its only argument(input).
I want to check this 'input' string value against the name of the key in the Person Collection.
Person has a key called 'gender'. So for instance, if the 'input' holds the string value 'gender' then the if statement should be true but in my case it comes as false and hence the code inside the if statement is never executed.
Any help/guidance with this will be appreciated.
UPDATE
I searched on mongodb documentation and found here: http://docs.mongodb.org/manual/reference/operator/query/exists/ and also using some help from this thread: (using $exists in Mongo with dynamic key names and the native driver for node)
that I could do something like this:
var checkThis = {};
checkThis[input] = { $exists : true };
var p = Person.findOne(checkThis);
So if it finds one then 'p' holds the record or else it will be undefined. But still the above code does not work.
If I were to put directly:
var p = Person.find({gender: {$exists: true} });
then it works.
So I need assistance in getting the code to work with the variable 'input'.
Mongo is a schemaless database - you can insert any document structure you like into a collection and the data store won't complain. Therefore Person won't be able to indicate which fields conform to the pattern.
The most common way people deal with this problem is to use a package which provides a schema layer on top of mongo. With meteor, a popular choice is SimpleSchema, and its related package AutoForm. SimpleSchema allows you to define which fields should be allowed into a collection, and AutoForm gives you a set of helpers to enforce them in your UI.
If, instead, you prefer not to use a package you could do something like the following:
person.js
var REQUIRED_FIELDS = {
name: String,
gender: ['male', 'female', 'prefer not to say'],
age: Number
};
Person = new Meteor.Collection('person');
Person.isValid = function(person) {
try {
check(person, REQUIRED_FIELDS);
return true;
} catch (_error) {
return false;
}
};
Meteor.methods({
'person.insert': function(person) {
check(person, REQUIRED_FIELDS);
return Person.insert(person);
}
});
my-template.js
Template.myTemplate.events({
submit: function() {
var person = {
name: $('#name').val(),
gender: $('#gender').val(),
age: parseInt($('#age').val(), 10)
};
if (Person.isValid(person))
Meteor.call('person.insert', person);
else
alert('invalid person');
}
});
Here we are using meteor's check package to do some basic field validation. By adding an isValid helper to the Person collection, we can validate the schema without the need for a method call. Best of all we can reuse the same check when inserting a new document.

Resources