I'm trying to make a publishment statement to publish
ONLY the author(OP)'s profile avatar. I am thinking of grabbing the _id of the page. And from that page, I will grab the userId which is the author's _id and try to show the profile.
However, I have been very unsuccessful, and currently, I am using the following. Publishing EVERY user's profile avatar.
Publications.js
//Need to filter this to show only OP.
Meteor.publish("userPostAvatar", function() {
return Meteor.users.find( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
return Posts.findOne({_id:this.params._id});
}
});
You can do a simple join in the userPostAvatar publish function like this:
Meteor.publish('userPostAvatar', function(postId) {
check(postId, String);
var post = Posts.findOne(postId);
return Meteor.users.find(post.authorId, {fields: {profile: 1}});
});
This assumes posts have an authorId field - adjust as needed for your use case. Note three important things:
You will need to subscribe with this.params._id just as you did for singlePost.
The join is non-reactive. If the author changes, the avatar will not be republished. Given the general nature of posts I assume this isn't a problem.
I didn't publish the nested field profile.avatar on purpose because doing so can cause weird behavior on the client. See this question for more details.
I believe you can achieve this within the iron:router data context, by finding the post, associated author (whatever the field is), and then the subsequent user avatar. You can return an object to the iron:router data context. Then you can access post and avatar in the template as variables (so you might need to adjust the template output a little).
Publications.js
Meteor.publish("userPostAvatar", function() {
return Meteor.users.findOne( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
var post = Posts.findOne({_id: this.params._id});
var avatar = Users.findOne(post.authorId).profile.avatar;
return {
post: post,
avatar: avatar
};
}
});
Two problems with this method are that you could achieve the same thing with template helpers, and the user publication hasn't been limited to one user (I'm unsure how to do this unless we know the authorId within the waitOn, although maybe you could try moving the logic to there instead of the data context as my example shows).
Related
I am adding a new page to a website, and I am copying the code that already exists and is currently working in the website. Why is the FlowRouter.getParam coming up undefined when it works everywhere else?
client/JobInvoice.js
import { Invoices } from '../../../imports/api/Invoice/Invoice';
Template.InvoicePage.onCreated(function(){
const user = FlowRouter.getParam('_id');
console.log(user);
this.subscribe('invoices', user);
});
lib/router.js
Accounts.onLogout(function(){
FlowRouter.go('home');
});
FlowRouter.notFound = {
action: function() {
FlowRouter.go('/404');
}
};
const loggedIn = FlowRouter.group({
prefix: '/secure'
});
loggedIn.route( '/invoice', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main:
'InvoicePage'});
}
});
What am I missing?
FlowRouter allows you to define routes with dynamic attributes (path-to-regexp), which are often representing document ids or other dynamic attributes.
For example
FlowRouter.route('/invoice/:docId', { ... })
would define a route that matches a pattern like /invoice/9a23bf3uiui3big and you usually use it to render templates for single documents.
Now if you want to access the document id as param docId inside the corresponding Template you would use FlowRouter.getParam('docId') and it would return for the above route 9a23bf3uiui3big.
Since your route definitions lacks a dynamic property, there is no param to be received by FlowRouter.getParam.
A possible fix would be
loggedIn.route( '/invoice/:_id', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main:
'InvoicePage'});
}
});
to access it the same way you do for the other templates.
Readings
https://github.com/kadirahq/flow-router#flowroutergetparamparamname
Here is what I ended up doing and it works.
loggedIn.route( '/invoice/:id', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main: 'InvoicePage'});
}
});
I need some advice for building a correct role schema and management in my meteor-app.
Structure
Im using alanning:roles#1.2.13 for adding role management functionallity to the app.
There are four different user-types: Admin, Editor, Expert and User.
Furthermore there are several modules with different content, i.e. Cars, Maths and Images. Every module is organized in an own meteor-package.
In every module there are several categories, which can be added dynamically by editors.
Categories in modules
Module is structured like this:
elementSchema = new SimpleSchema({
element: {type: String, optional: true}
});
Cars.attachSchema(new SimpleSchema({
title: { type: String },
content: { type: String },
category: { type: [elementSchema], optional: true },
});
As you can see, all available categories are inside of the Collection of the module.
Rights
Admin: Complete rights
Editor: Can edit elements in selected moduls (i.e. editor_1 can edit elements in Cars and Images but not for Maths)
Expert: Can get rights to a complete module or just to some categories of a module (i.e.) expert_1 can edit Images, but only the elements in category "Honda" and "Mercedes" in Cars; no editing to Maths)
User: No editing
This is how I do the authentification technically:
router.js
var filters = {
authenticate: function () {
var user;
if (Meteor.loggingIn()) {
this.layout('login');
this.render('loading');
} else {
user = Meteor.user();
if (!user) {
this.layout('login');
this.render('signin');
return;
}
this.layout('Standard');
this.next();
}
}
}
Router.route('/car/:_id', {
name: 'car',
before: filters.authenticate,
data: function () {
return {
cars: Cars.findOne({ _id: this.params._id })
};
}
});
template
<template name="car">
{{#if isInRole 'cars'}}
Some form for editing
{{else}}
<h1>Restricted area</h1>
{{/if}}
</template>
I put this router.js to every package. Only change is the data function which uses the Collection of each package (Cars, Maths, Images).
Update: As 'Eliezer Steinbock' commented it is necessary to restrict acces to the mongoDB itself. But until now I only did that on the routes.
permissions.js
Cars.allow({
insert: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
},
update: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
}
});
My problems
1) My first problem is how to use roles and groups. What would be the best way for using groups? And the second problem is, that there are no fixed categories in the modules. Right now I have no idea for a useful role/group schema.
2) How do I check for the roles? As there are different roles which can get access: admin, editor and expert. Also I got the problem with these experts who just get access to defined categories of this module.
3) Wouldn't it be better to make the permission.js more general. I mean, is it possible to make a dynamic function, so I don't have to put everywhere the same code? How do I implement the roles in the permission.js in a useful way?
if the logic for the permissions is the same you could just define it once in permissions.js
App = App || {}; // We are using Namespaces, so you don't have to.. but it's good
App.Permissions = {
insert: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
},
update: function(userId) {
var loggedInUser = Meteor.user()
if (loggedInUser && Roles.userIsInRole(loggedInUser, ['admin','editor'])) return true;
}
}
And then you can use it for your Collections:
Cars.allow(App.Permissions); // Or
Cars.allow(App.Permissions.getPermissionsForGroup('cars'))
Define roles somewhere..
Roles
// Give user the role "editor" in "cars" group
Roles.addUsersToRoles(someUserId, ['editor'], 'cars');
Roles.addUsersToRoles(someOtherId, ['admin'], 'cars');
Which you can prepare in permissions.js like this:
Permissions
App = App || {};
App.Permissions = {
insert: function(userId) {...},
update: function(userId) {...},
getPermissionsForGroup: function(group) {
return {
insert: function(userId, doc) {
// Only admin can insert
return Roles.userIsInRole(userId, "admin", group);
},
update: function(userId, doc, fields, modifier) {
// Editor & Admin can edit
return Roles.userIsInRole(userId, ["editor","admin"], group);
},
remove: function(userId, doc) {
// Only admin can remove
return Roles.userIsInRole(userId, "admin", group);
}
}
}
In this example admins can insert and update.. and editors can only update, but insert.
Regarding the documentation of alanning:roles you define and use roles like this:
// Super Admin definition..
Roles.addUsersToRoles(superAdminId, ['admin'], Roles.GLOBAL_GROUP);
Roles.addUsersToRoles(joesUserId, ['manage-team','schedule-game'], 'manchester-united.com')
Roles.addUsersToRoles(joesUserId, ['player','goalie'], 'real-madrid.com')
Roles.userIsInRole(joesUserId, 'manage-team', 'manchester-united.com') // => true
Roles.userIsInRole(joesUserId, 'manage-team', 'real-madrid.com') // => false
Yeah, make sure, that the permission logic will be included before your Collection definition.. obviously :)
I'm creating a set of routes, for example
/ - should render home page template
/items - should items page template
/items/weeARXpqqTFQRg275 - should return item from MongoDB with given _id
This is example of what I'm trying to achieve
Router.route('items/:_id', function () {
var item = return myItems.find(:_id);
this.render(item);
});
[update - solved]
solved this by using Router.map on server side instead of Router.route
Router.map(function () {
this.route('post', {
path: 'items/:_id',
where: 'server',
action: function(){
var id = this.params._id;
var json = myItems.findOne({_id: id});
this.response.setHeader('Content-Type', 'application/json');
this.response.end(JSON.stringify(json, null, 2));
}
});
});
There are several problems with your code.
First, it seems you want to get the _id parameter from the url and don't know how. It's stored on this.params, so it's this.params._id.
Second, the first parameter you should send to find is a MongoDB query that in your case would be { _id: this.params._id }.
Third, that's not how you render something in Iron Router. The string parameter on the render method is the name of the template you want to render, not the item.
Assuming that myItems is a valid collection and your template is called showItem, your code should be something like:
Router.route('items/:_id', {
name: 'showItem',
data: function () {
return myItems.find({ _id: this.params._id });
}
});
Try something like this:
Router.map(function () {
this.route('items/:myItemId', {
data: function(){
return myItems.findOne({_id: this.params.myItemId});
}
});
});
good luck!
Currently when auto publish is removed, only {{currentUser.profile.name}} works.I'm trying to get {{currentUser.profile.first_name}} and the avatar from Facebook but have not been able to do so. Here is my code...
On the Server side:
Meteor.publish('userData', function() {
if(!this.userId) return null;
return Meteor.users.find(this.userId, {fields: {
'services.facebook': 1
}});
});
On Iron Router:
Router.configure({
waitOn: function() {
return Meteor.subscribe('userData');
}
});
From my understanding, I see that Meteor is publishing all the userData and then subscribing to it via Iron Router. What I don't understand is why this is not working -- as I think {{currentUser.profile.first_name}} should work but isn't.
Like Richard suggests, when a user is created, you can copy the services document to the profile doc.
Accounts.onCreateUser(function(options, user) {
// We still want the default hook's 'profile' behavior.
if (options.profile) {
user.profile = options.profile;
user.profile.memberSince = new Date();
// Copy data from Facebook to user object
user.profile.facebookId = user.services.facebook.id;
user.profile.firstName = user.services.facebook.first_name;
user.profile.email = user.services.facebook.email;
user.profile.link = user.services.facebook.link;
}
return user;
});
Your publication to get their first name and Facebook ID would look like this...
/* ============== Single User Data =============== */
Meteor.publish('singleUser', function(id) {
check(id, String);
return Meteor.users.find(id,
{fields: {'profile.facebookId': 1, 'profile.name': 1, 'profile.firstName': 1, 'profile.link': 1}});
});
You can access a user's Facebook avatar with a template helper function...
Template.profileView.helpers({
userPicHelper: function() {
if (this.profile) {
var id = this.profile.facebookId;
var img = 'http://graph.facebook.com/' + id + '/picture?type=square&height=160&width=160';
return img;
}
}
});
In your template, you can then use the following helper (provided you are wrapping this in a block that contains user data):
<img src="{{userPicHelper}}" alt="" />
I believe you're trying to access the first_name field from the services subdocument. It should be {{currentUser.services.facebook.first_name}}
If you want to transfer first_name to the profile subdocument, you can have the following event handler:
Accounts.onCreateUser(function(options, user) {
// ... some checks here to detect Facebook login
user.profile.firstName = user.services.facebook.first_name;
user.profile.lastName = user.services.facebook.last_name;
});
I have a game that I know works properly with an ID per the code on the client side. For example if I were to use the below with {{game._id}} it works properly:
Template.gamePage.game = function() {
return GameCollection.findOne({current: true});
};
However, I am trying to gain access to the publications of 'submissions; only for the specific game ID. Console log below returns undefined.
router.js
this.route('gamePage', {
path: '/games/:_id?',
waitOn: function() {
console.log(this.params._id);
return [
Meteor.subscribe('randomQuestions', Random.id()),
Meteor.subscribe('submissions', this.params._id)
];
}
});
I suspect that params._id pulls from games/:_id, however, I would like it so that that it remains games/:_id? so that I do not have an unnecessary long address.
Any thoughts on why I am getting undefined for params._id
I think you have one button for to access a game, for example...
Tracker.autorun(function () {
Session.set('gameCurrent');
});
Template.gamePage.helpers({
allGames: function(){
return GameCollection.find({});
},
getCurrentGame:function(){
return Session.get('gameCurrent');
}
})
// with this action you access a the route with the id specified
Template.gamePage.events({
'click button#game' : function(event,template){
Session.set('gameCurrent',this._id);
Router.go('editEmail',{_id:this._id})
}
})
Remember that Session only works in the client.