Is it possible to retrieve specific user data (e.g. profile.name or even something else within profile object) with meteor-presence instead of returning userId? Or should I collect all userIds from Meteor.presences and then query Meteor.users with each userId to get the data I need?
Yes that is the best way to do it. You could use transform in your query too!
Meteor.presences.find({}, {transform:function(doc) {
var user = Meteor.users.findOne({_id:doc.userId});
if(user) doc.profile = user.profile;
return doc;
}
});
You can use this query in your template helper or pretty much anywhere else so that it now has a profile object to match the user. Make sure though all your users in your user collection are published in a safe way.
Related
I'm adding custom data to Meteor user accounts for the first time. I've been able to add custom fields without difficulty and I know they're there because I can see them in Mongol. I am publishing via a global subscription so how do I then go about reading data from individual fields? It seems the syntax is very different from that when using publish/subscribe methods.
So, I have user accounts like this (as seen in Mongol):
"_id": "#################",
"profile": {
"name": "Test User"
},
"customfields": {
"customfield1": [
"A","B","C"
]
}
}
In server/main.js I have the following
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields:{customfields:1}});
});
This seems to be publishing fine. But what code do I use to render the cursor as data? I've been using variations on code like this in client/main.js and having no success:
var stuff = Meteor.users.find(this.userId).fetch();
console.log(stuff.customfield1);
Any help appreciated.
MyCollection.find() returns a cursor whereas MyCollection.findOne() returns an object, i.e. a single mongodb document.
A publication must return a cursor or array of cursors. You publication is fine.
You are basically trying to make the customfields key of the user object visible on the client. (The profile key is automatically published by Meteor).
On the client, where you are doing:
var stuff = Meteor.users.find(this.userId).fetch();
You can simply use:
var stuff = Meteor.user();
or
var stuff = Meteor.users.findOne(Meteor.userId());
Then stuff.customfields will contain what you're looking for.
The second form is way too verbose for me unless you're looking for a different user than the logged in user.
Note: this.userId on the client will not be the userId of the current user, it will be undefined. That only works on the server. That may actually be the root cause of your problem. In addition, your publications must be ready() for the data to be available. This isn't true immediately after login for example.
Since customfield1 is nested in customfields, did you try stuff.customfields.customfield1?
so I already finished all of the actual app for this. I just need to setup the backend. I figured Firebase was the best solution since Parse is no longer a thing. What I wanted was:
Users with profiles - These profiles can be viewed by added friends but only edited (written) to by the actual profile owner.
So I read through the Firebase Docs and still cannot really figure out how to do this. They only have 1 Swift application example that does not do anything similar and the one Obj C twitter one, will not even build. All of their docs still have println for Swift which just makes me think it is not updated frequently.
Does anyone have any good examples / tutorials of this? I keep trying to search for things but nothing is as similar enough to what I want. I am more looking on how to setup the db for each user and access it rather actually using Firebase in Swift.
As I wrote in my comment to your question, this answer is based on what we do in a real social app Impether using Swift + Firebase.
Data structure
Let's assume that you want to store the following information for a single user:
email
username
name
followers - number of people who follow a particular user
following - number of people who a particular user follows
avatar_url - url of their avatar
bio - some additional text
Since in Firebase everything is stored a JSON objects, you can store the above structure under node with path like users/$userId, where $userId is Firebase User UID which is created for each registered user if you use simple email/password Firebase authorization.
Firebase email/password authorization is described in their docs:
https://www.firebase.com/docs/ios/guide/user-auth.html
https://www.firebase.com/docs/ios/guide/login/password.html
Notice that there are both Obj-C and Swift snippets. I find Firebase documentation really great as it helped me a lot when I was building our app.
For the purpose of this answer let's assume that we have user with username jack and Firebase User UID equal to jack_uid (in reality this will be a string generated by Firebase).
Then an example data for this user will be store under a path users/jack_uid and can look like this:
{
"email" : "jack#example.com",
"username" : "jack",
"name" : "Jack",
"followers" : 8,
"following" : 11,
"avatar_url" : "http://yourstoragesystem.com/avatars/jack.jpg",
"bio" : "Blogger, YouTuber",
}
Firebase email/password authorization works really well, but let's be honest, if user wants to sign in into the app, it's a lot better for him to use his username than his email he gave while he registering his account.
In order to do that, we decided to store a mapping from usernames to user ids. The idea is that if user inputs his username and password in a login form, we use that mapping to retrieve his user id and then we try to sign him in using his user id and provided password.
The mapping can be stored for example under a path username_to_uid and looks like this:
{
"sample_username_1": "firebase_generated_userid_1",
"sample_username_2": "firebase_generated_userid_2",
...
"jack": "jack_uid",
"sample_username_123": "firebase_generated_userid_123"
}
Then creating a profile may looks like this and it's done as soon as registration of a new account was successful (this snippet is very close to the exact code we use in the production):
func createProfile(uid: String, email: String,
username: String, avatarUrl: String,
successBlock: () -> Void, errorBlock: () -> Void) {
//path to user data node
let userDataPath = "/users/\(uid)"
//path to user's username to uid mapping
let usernameToUidDataPath = "/username_to_uid/\(username)"
//you want to have JSON object representing user data
//and we do use our User Swift structures to do that
//but you can just create a raw JSON object here.
//name, avatarUrl, bio, followers and following are
//initialized with default values
let user = User(uid: uid, username: username, name: "",
avatarUrl: avatarUrl, bio: "",
followers: 0, following: 0)
//this produces a JSON object from User instance
var userData = user.serialize()
//we add email to JSON data, because we don't store
//it directly in our objects
userData["email"] = email
//we use fanoutObject to update both user data
//and username to uid mapping at the same time
//this is very convinient, because either both
//write are successful or in case of any error,
//nothing is written, so you avoid inconsistencies
//in you database. You can read more about that technique
//here: https://www.firebase.com/blog/2015-10-07-how-to-keep-your-data-consistent.html
var fanoutObject = [String:AnyObject]()
fanoutObject[userDataPath] = userData
fanoutObject[usernameToUidDataPath] = uid
let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images")
ref.updateChildValues(fanoutObject, withCompletionBlock: {
err, snap in
if err == nil {
//call success call back if there were no errors
successBlock()
} else {
//handle error here
errorBlock()
}
})
}
In addition to this you possibly want to store for each user a list of his followers and a separate list of users he follows. This can be done just by storing user ids at a path like followers/jack_uid, for example it can look like this:
{
"firebase_generated_userid_4": true,
"firebase_generated_userid_14": true
}
This is the way we store sets of values in our app. It very convenient, because it is really user to update it and check if some value is there.
In order to count the number of followers, we put this counter into user's data directly. This makes reading the counter very efficient. However, updating this counter requires using transactional writes and the idea is almost exactly the same as in my answer here: Upvote/Downvote system within Swift via Firebase
Read/write permissions
A part of your question is how to handle permissions to data you store. The good news is that Firebase is exceptionally good here. If you go to your Firebase dashboard there is a tab named Security&Rules and this is the place where you control permissions to your data.
What's great about Firebase rules is that they are declarative, which makes them very easy to use and maintain. However, writing rules in pure JSON is not the best idea since it's quite hard to control them when you want to combine some atomic rules into a bigger rule or your app simple grows and there are more and more different data you store in your Firebase database. Fortunately, Firebase team wrote Bolt, which is a language in which you can write all rules you need very easily.
First of all I recommend to read Firebase docs about Security, especially how does permission to a node influences permission for its children. Then, you can take a look at Bolt here:
https://www.firebase.com/docs/security/bolt/guide.html
https://www.firebase.com/blog/2015-11-09-introducing-the-bolt-compiler.html
https://github.com/firebase/bolt/blob/master/docs/guide.md
For example, we use rules for managing users data similar to this:
//global helpers
isCurrentUser(userId) {
auth != null && auth.uid == userId;
}
isLogged() {
auth != null;
}
//custom types, you can extend them
//if you want to
type UserId extends String;
type Username extends String;
type AvatarUrl extends String;
type Email extends String;
type User {
avatar_url: AvatarUrl,
bio: String,
email: Email,
followers: Number,
following: Number,
name: String,
username: Username,
}
//user data rules
path /users/{$userId} is User {
write() { isCurrentUser($userId) }
read() { isLogged() }
}
//user's followers rules
//rules for users a particular
//user follows are similar
path /followers/{$userId} {
read() { isLogged() }
}
path /followers/{$userId}/{$followerId} is Boolean {
create() { isCurrentUser($followerId) && this == true }
delete() { isCurrentUser($followerId) }
}
//username to uid rules
path /username_to_uid {
read() { true }
}
path /username_to_uid/{$username} is UserId {
create() { isCurrentUser(this) }
}
The bottom line is that you write rules you want using Bolt, then you compile them into JSON using Bolt compiler and then you deploy them into your Firebase, using command line tools or by pasting them into dashboard, but command line is way more efficient. A nice additional feature is that you can test your rules by using tools in Simulator tab in your dashboard.
Summary
For me Firebase is a great tool for implementing a system you want. However, I recommend to start with simple features and learn how to use Firebase in the first place. Implementing social app with functionality like for example Instagram is quite a big challenge, especially if you want to do it right :) It's very tempting to put all functionality there very quickly and Firebase makes it relatively easy to do, but I recommend to be patient here.
In addition, take your time and invest in writing tools. For example, we have two separated Firebase databases, one for production and second for testing, which is really important if you want to write unit and UI tests efficiently.
Also, I recommend building permission rules from the beginning. Adding them later may be tempting, but also quite overwhelming.
Last but not least, follow Firebase blog. They post regularly and you can be up to date with their latest features and updates - this is how I learnt how to use concurrent writes using fanout technique.
With the find and update methods i am able to restrict fetching of data restricted to the logged in user by adding a policy that sets req.options.where.owner = userID, therefore i dont need to create custom controllers or models for these methods.
With update and create i can also set req.options.values.owner = userID so that the user cant create or update an object that will belong to another user.
But the problem is that the blueprint findOne controller does not have any options for this kind of filtering, so any logged in user can request an object created and owned by another user.
Is there anyway i can restrict findOne without writing my own controller and query?
Found a solution to the problem, what you can do is to override the default blueprint action by creating a folder named blueprints in your api folder, there you can create a findone.js (lowercase) file,
copy the original blueprint action from /node_modules/sails/lib/hooks/blueprints/actions/findOne.js to /api/blueprints/findone.js
add .where( actionUtil.parseCriteria(req) ); to the query.
Dont forget to change the path of actionutil from require('../actionUtil'); to require('../../node_modules/sails/lib/hooks/blueprints/actionUtil');
Voila, now the findOne action will respect your req.options.where queries.
You can specify blueprint in your policies like this
module.exports = function (req, res, next) {
var blueprint = req.options.action;
if (blueprint === 'findOne') {
// do restriction here
return next();
}
res.forbidden('not allowed to do something');
};
I'm rather forget, is blueprint name findOne or findone.
Im working on ember-cli, how do i change rest call on fly in the rest adapter. If i use path params not query params?for example:
export default DS.RESTAdapter.extend({
namespace:'res/v1/users/id',
pathForType: function() {
return Ember.String.underscore("friends");},});
Based on the user selection from dropdown we get the "id", using the id I need to get user friends from the database.
Could you please suggest a better way to do. My aapplication supports pathparams not the query params
To customize the URL, override the buildURL method in your adapter.
The tricky part is to access related records from the adapter. For example, you request friends for a given user. You work in a friend adapter, but you need to know the user's id to include it in the URL.
For that purpose, use the record property on the snapshot argument of the buildURL method.
Alternatively, you might want to override some of buildURL's underlying methods such as urlForFindQuery, depending on how you request your model from the store. With a find.query(), you will retrieve the id of the user from the query.
If this does not help you, please respond with the way you're trying to fetch friends from the store.
I have created a variable in enviroment.js 'userId'. When ever i select a user
i set config.userId in the controller to the corresponding Id.
config.userId=this.get('selectedUser');
In pathforType of adapter I used this varible
pathForType: function() {
return Ember.String.underscore(config.userId+"/friends");
}
you just need to add an import statement
import config from '../config/environment';
Please suggest me if anyone get to know better way. Thanks all for your responses
buildURL() only takes the type imo. so you have to pass some more jazz.
i did something along the lines of the following in the application adapter
$ ember generate adapter application
app/adapters/application.js
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
findQuery: function(store, type, query) {
var urlQuery = query.theshityouwant;
var reply = this.ajax(this.buildURL(type.typeKey + '/' + urlQuery), 'GET', { headers: all});
return reply;
},
})
});
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 });
});