Set admin account using Meteor to view all tasks - javascript

I'm new to Meteor and just walked through the To-Do list tutorial available here (https://www.meteor.com/tutorials/blaze/creating-an-app). I removed autopublish and set the display function in such a way that all tasks were private (that is, users could see only see their own tasks.)
Now, I'd like to change it up and set one account as an admin account. The admin can view everyone's tasks, but no one else can see anything (not even their own tasks). I'm trying to do this using the alanning-roles package, which I already downloaded in the app folder.
In my tasks.js file, I've inserted the lines:
const mod = 'E9Y4qtFXK2qQGAGq3'; // this is the userId of the account that I wish to make admin
Roles.addUsersToRoles(mod, 'moderator');
Then, instead of just displaying all the tasks, I enclose the command to display all tasks in an if-statement:
if (Meteor.isServer) {
if (Roles.userIsInRole(this.userId,'moderator')) {
Meteor.publish('tasks', function tasksPublication() {
return Tasks.find();
});
}
}
This should display all tasks if you are logged in as the moderator/admin, and nothing otherwise. But when I run this code, no tasks show up even when I am logged in as the admin. I am certain that the userId I set is correct, and that there are tasks in the collection. Does anyone have any ideas of what the problem might be?
(Or, any other suggestions on how to do this? Doesn't have to use alanning-roles -- I just thought that would be simplest)
Thanks so much
-C
EDIT: If I replace "this.userId" with "mod" in the line:
if (Roles.userIsInRole(this.userId,'moderator')){...}
then all the tasks appear. So it appears that the problem is with the output of this.userId.

You need to move where you are checking if current user is a 'moderator' to inside the publish function:
Currently in your code, when you access this.userId the server is starting up, and this.userId will be undefined. So the code in your if statement is not being executed, so the publish function is not created, and no clients will be able to subscribe to this data.
Try this:
if (Meteor.isServer) {
Meteor.publish('tasks', function tasksPublication() {
if (Roles.userIsInRole(this.userId, 'moderator')) {
return Tasks.find({});
}
});
}
Now, on startup the Meteor.isServer block runs, creating the tasks publication, with the code to check the role inside it. This function will now be called every time a client subscribes to it, and in this context this.userId will be the current client's user ID.
Also, don't place the alanning-roles package's source code in your app's folders - include the package either by running meteor add alanning:roles, or via npm with npm install #alanning/roles --save

You should use Meteor.userId() instead of this.userId:
if (Meteor.isServer) {
if (Roles.userIsInRole(Meteor.userId(),'moderator')) {
Meteor.publish('tasks', function tasksPublication() {
return Tasks.find();
});
}
}
As as rule of thumb, always use Meteor.userId(), except inside a publication, where you should use this.userId

Related

how do I stop a website from automatically logging me out when I'm using cypress?

I'm using cypress to automate testing at my companies' website, and upon logging in, clicking on any of the interactive elements immediately logs me out. however, this is only when I run it through cypress. manually clicking around still works. What could I do to fix this?
this is for a website built with javascript.
I expect the website to not log me out.
describe('signing in', function() {
it('Visits the safe and reliable sign-in page', function() {
cy.visit('https://testing.safeandreliable.care/sign-in')
cy.get('[id="at-field-username_and_email"]').type('bcramer#safeandreliablecare.com')
cy.get('[id="at-field-password"]').type('******')
cy.contains('Sign In').click()
})
it('signs into the default entity', function(){
cy.get('[id="help-text-board"]').click({force:true})
cy.wait(9000)
})
})
What you should know is that Cypress clears the state of browser every time it starts a new it(). So something what is done in the first it() is not now in the second it(). In your case, the login is in the first it, in the second it the application is not logged in anymore. To take care that Cypress stays logged in, you should move the login step to a before() or a beforeEach() (depends wether you want to login once per describe or per every it.
Following your post it seems you like it to login once and stay logged in, so the before() does the job for you. Your code would look like this:
describe('signing in', function() {
before('Logijn to the application', function() {
cy.visit('https://testing.safeandreliable.care/sign-in')
cy.get('[id="at-field-username_and_email"]').type('bcramer#safeandreliablecare.com')
cy.get('[id="at-field-password"]').type('******')
cy.contains('Sign In').click()
})
it('signs into the default entity', function(){
cy.get('[id="help-text-board"]').click({force:true})
cy.wait(9000)
})
it('next test', function () {
// do other test but still logged in
})
})
You need to add the following (among with the other changes proposed in https://stackoverflow.com/a/56593040/8928727):
beforeEach(() => {
Cypress.Cookies.preserveOnce('whatever session id')
})
If the above is missing you would still have your cookies deleted between tests, and in this case that's not what you want. Read more here: https://docs.cypress.io/api/cypress-api/cookies.html#Preserve-Once
Install this on your project's devDependencies:
npm i --save-dev cypress-localstorage-commands
Add this at the top of your Cypress' support file (in cypress/support/e2e.js ):
import "cypress-localstorage-commands"
In the cypress.config.js file add this like:
module.exports = {
e2e: {
setupNodeEvents(on, config) {
require("cypress-localstorage-commands/plugin")(on, config)
return config
}
}
}
Then you need to add this in before hook:
cy.clearLocalStorageSnapshot()
In beforeEach hook:
cy.restoreLocalStorage()
At last add this in afterEach hook:
cy.saveLocalStorage()
For details https://dev.to/javierbrea/how-to-preserve-localstorage-between-cypress-tests-19o1

Meteor - Not updating in real time anymore

Just started using Meteor and am loving it thus far.
I'm making a variation of the tutorial "todo" app, but with my own customization.
More or less the changes I made so far have been:
Changed directory structure to client, server, public, lib/collections folders
Made the task collection through collection2 schema rather than dynamically creating with Tasks.insert(title = text,etc etc)
So to the question.
These are my client/server files -
server.js
Meteor.publish("tasks", function () {
return Tasks.find({}, {sort: {checked: 1}});
});
client.js
// This code only runs on the client
Meteor.subscribe("tasks");
Template.task.events({
"click .toggledone": function () {
// Set the checked property to the opposite of its current value
Tasks.update(this._id, {
$set: {checked: ! this.checked}
});
},
"click .delete": function () {
Tasks.remove(this._id);
}
});
And its being displayed in the standard html
<ul>
{{#each tasks}}
{{> task}}
{{/each}}
</ul>
So basically as it is now, my server is sorting the tasks on the basis of whether it is checked (completed) or not, ascending.
It works currently, when I click a task on the list, it turns red (my configuration) and registers as completed, but the list doesn't sort automatically like it did originally (before I made the directory changes).
For it to update, I need to refresh the page which is not what I want.
I'm new to this and i think it has something to do with my publish/subscribe but I can't seem to find anything to help me online.
Can anyone point me in the right direction for this?
Thanks so much.
I think you are confused between the publication/subscription named tasks and the collection tasks for the loop on the template. When you want to render the tasks on the template, it should be returned from a helper. Consider that publication/subscription making the correct part of the database available on the client and then, you retrieve the correct data from that part to display.
Normally, I make the naming convention for the publication like the SQL query so it is easier to interpret
Meteor.publish("select-from-tasks", function () {
return Tasks.find({});
});
and then,
Meteor.subscribe("select-from-tasks");
You will need to have a helper to return the correct data, like I said above
Template.task.helpers({
tasks: function() {return Tasks.find({}, {sort: {checked: 1}});}
});

How to improve data handling with meteor

Still on my meteor app, i'd like to now how to improve my data handling from minimongo.
Used to SQL / PHP, I'd like to know how to find() an object from my minimongo collection only one time, and access each of its properties with helpers, without having to re-access the collection each time.
Until now, what I did was something like that :
Template.profile.helpers({
name: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.name;
},
phone: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.phone;
}
[...]
});
But it's getting boring and i guess there must be a more efficient way to deal with it, something where I could load my entire users information only one time, and then display with a helper taking one parameter, to display the data like that : {{data name}}, {{data phone}}
With only one helper like that :
Template.profile.helpers({
data: function(aString){
if (aString == "phone)
return [...].phone;
}
}
[...]
});
Of course, I can use a session value, but I'm not sure it's as relevant as I could do.
Another thing : how to end a Meteor session ? Because with PHP, the session ends at the closure of the browser, and cookies stay for a given duration, but with meteor session, i never have to reconnect as day after day, my logs seems to stay.
Would someone guide me through this or give me some good habits / tips ? I'm still reading the doc, but it's quite huge :S
Thanks you !
One thing to note here -- you're not actually sending a request to the server when you call Mongo.Collection.find() on the client -- that's getting handled by the miniMongo instance on the client, so it's not really that inefficient. Also, in this particular instance, the current user is always available as Meteor.user() -- no need to use Meteor.users.find({_id: Meteor.userId()}); (see docs for more info)
However, if you really wanted to cache that value, you could do something like this:
// JS
var data; // scoped so it's available to the whole file
Template.profile.onCreated({
data = Meteor.users.findOne({_id: Meteor.userId()}); // above scope makes this new definition available to the whole file
});
Then, to do what you're describing with the string arguments, you can do something like...
Template.profile.helpers({
data: function(aString) {
if (data) { return data[aString]; }
}
});
Perhaps a better option even is to send the Template a data context -- something like this in your HTML file:
{{> profile currentUser}} <!-- currentUser returns Meteor.user() -->
Which is directly available to your helpers as this:
Template.profile.helpers({
data: function(aString) {
return this[aString];
}
});
You can also pass data contexts through IronRouter if you're using that for your routing.
As for the Meteor session, the model is different than the model for PHP. In PHP, you store session information on the server, and every time you access the server, the browser (or client, more generally) sends along the session ID so it can look up anything pertaining to your session (see this question for a potentially better explanation). Meteor keeps track of clients that are connected to it, so the session will stay open as long as your browser is open. If you need to manipulate Meteor user sessions, take a look at this question.
I recommend finding the collection helpers package on Atmosphere. This will enable you to write currentUser.name in a template and it'll automatically return users name helper function returns.
There is no problem working like this. It doesn't matter if the function is called multiple times. It won't hurt your apps performance.
PHP and meteor sessions are different. A meteor session lasts for as long as browser window remains open or the page is refreshed. You are right that meteor sessions are not the way to go for your problem.

Meteor: How to detect if users authenticated

In my app I use accounts-github. Works perfect, but I have one problem.
In one of my templates I do
Template.bar.rendered = function () {
if (Meteor.user()) {
// setup stuff
}
}
The problem is that if the user initially is not logged in this code is not executed (thats ok). But when the user authenticates this code is not executed again. So the question is how can I listen for this change inside a template (doesn't have to be in inside the rendered function!)?
You could use Deps.autorun. (http://docs.meteor.com/#deps_autorun)
Usually Deps.autorun would run for your whole Meteor app. If you want to make it so that it only runs per template you would need to create and stop it in the rendered and destroyed template callbacks
e.g
var loginRun;
Template.bar.rendered = function() {
loginRun = Deps.autorun(function() {
if(Meteor.user()) {
//Stuff to run when logged in
}
});
}
Template.bar.destroyed = function() {
loginRun.stop();
}
If you don't need it to run per template (need it to run just once for you app on any template, then you can use the Deps.autorun on its own, anywhere in your client side code.
Meteor.user() is reactive, it would ensure that the Deps.autorun callback runs again when it changes, so you could theoretically use it to do things when the user logs in or out.
Other alternatives is there is a package on atmosphere that provides login and logout hooks, though they basically would use the Deps.autorun like above to work anyway. See https://github.com/BenjaminRH/meteor-event-hooks
My solution for similar problem was to
Attach an event to template where the login happens
Re-render template if login is succesful so the Template.bar.rendered is called
E.g.
Template.bar.events({
'click .loginButton' : function() {
if( Meteor.call.Login( username, pw ) )
{
$('#bar').html( Meteor.render( Template.bar ));
//jQuery is optional
}
});

How should I update user.profile on subsequent login (something similar to Accounts.onCreateUser)?

I've been poking around in the Accounts packages, using a modified version of the ever-fabulous EventedMind Customizing Login screencast.
I modified it to use facebook instead of github, and I noticed something when trying to update user.profile information. Specifically, I'm looking for the right way/place to handle changes to user.profile.
Let's say, for example, that I authenticate as a FB user for the first time. When I do this, the CreateUser event will fire.
Using Accounts.onCreateUser(...), I can populate additional information from the FB graph into the profile, like so:
Accounts.onCreateUser(function(options,user){
var accessToken = user.services.facebook.accessToken,
result;
result = Meteor.http.get("https://graph.facebook.com/"+user.services.facebook.username, {
params: {
access_token:accessToken,
fields: ['picture', 'name','first_name','last_name','username','link','location','bio','relationship_status','email','timezone','locale']
}
});
if (result.error){
throw result.error;
}
user.profile = result.data; //lazily adding everything
return user;
});
This works just fine when the user is created. It's nice and clean.
But now let's say that some of the information changes. For example, let's say that the profile picture changes. If I log out and then back in to the meteor application, Accounts.onCreateUser(...) doesn't fire, because the user already exists. It's not being created again, it's being modified.
I need to update the user.profile on subsequent logins, or at least check for changes and then modify as needed. I'd ideally like to do this in similar fashion to .onCreateUser. Maybe with a .onModifyUser or something...
I can figure a couple of ways to do this using some checking and/or client-side code, but I'm wondering if there is an already-existing server hook that would be cleaner.
Any recommendations on the cleanest way to handle this situation?
Thanks in advance.
If you're manually calling the login functions you can pass a callback as the last parameter which will get called on the client after the login completes. See: http://docs.meteor.com/#meteor_loginwithpassword.
Meteor.loginWithFacebook({}, function (err) { /* make a Meteor method call here */ });
There are no documented server side callbacks at the moment.

Categories