How to handle this kind of not found error in iron-router? - meteor

For example, I have a Posts collection, which has userId indicates this post belongs to whom.
And I have a route like this:
Router.route('/:username/posts', {
waitOn: function(){
return Meteor.subscribe('posts', username); // A
// var user = Meteor.users.find({username: username}); //B
// if(user) {
// return Meteor.subscribe('posts', user._id);
// } else {
// return null; // ???
// }
}
});
And publish
Meteor.publish("posts", function(userId){ // C
check(userId, String);
return Posts.find({userId: userId});
});
Meteor.publish("posts", function(username){ // D
check(username, String);
if(user) {
return Posts.find({userId: user._id});
} else {
return null; // ???
}
});
I'm confused how to handle like GET /notexistusername/posts ??

You can configure a notFoundTemplate for iron-router either globally or on a per-route basis.
Globally:
Router.configure({
layoutTemplate: 'layout',
notFoundTemplate: 'notFound',
loadingTemplate: 'loading'
});
In a specific route (also I've cleaned up your route code):
Router.route('/:username/posts', {
notFoundTemplate: 'notFound',
data: function(){
var userId = Meteor.users.find({ username: this.params.username })._id;
return Posts.find({ userId: userId });
},
waitOn: function(){
var userId = Meteor.users.find({ username: this.params.username })._id;
return Meteor.subscribe('posts', userId);
}
});
Your notFound template would typically be your 404 page.
Also you can simplify your publication to:
Meteor.publish("posts", function(username){
check(username, String);
return Posts.find({username: username});
});
Since if there are no matches the find will return a null cursor and that will tell the route there's no data.

Related

How do you do client side routing when saving via methods?

I want to save some data and show it in a view template. So I want to do like the example below but using methods.
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
post._id = Posts.insert(post);
Router.go('postPage', post);
}
});
I tried this:
'insertClubData': function(clubname, capacity, description, homepage){
var currentUserId = Meteor.userId();
var club = {
clubname: clubname,
description: description,
capacity: parseInt(capacity),
homepage: homepage,
createdAt: new Date(),
visitors: 0,
occupancy: 0,
trend: "club-1",
createdBy: currentUserId
}
club._id = clubs.insert(club);
Router.go('club', club);
},
but I get the error:
Exception while invoking method 'insertClubData' TypeError: Object
function router(req, res, next) { I20160425-14:04:55.724(2)? //XXX
this assumes no other routers on the parent stack which we should
probably fix
I understand that this is because Router.go is a client side method. But I also understand that you should avoid server side routing. So what's the most elegant solution?
This is my route:
Router.route('/club/:_id', {
name: 'club',
template: 'club',
data: function(){
return clubs.findOne({_id: this.params._id})
}
});
How about, you call the method from the client and in the callback on success you do the routing. For example:
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('insertPost', post, function(error, id) {
if (error) {
alert(error)
} else {
Router.go('postPage', {_id: id});
}
});
}
});
and on the server
Meteor.methods({
insertPost: function(post) {
// do checks
id = Posts.insert(post);
return id;
}
});
Does that work for you?

Adding collection items as routes in Meteor

I have a meteor project where all my users have their own profile page setup in this way using routes:
Routes code:
Router.route('/#:username', {
name: 'profile',
controller: 'ProfileController'
});
ProfileController = RouteController.extend({
template: 'profile',
waitOn: function() {
return Meteor.subscribe('userProfile', this.params.username);
},
data: function() {
var username = Router.current().params.username;
return Meteor.users.findOne({
username: username
});
}
});
Server code:
Meteor.publish('users', function() {
return Meteor.users.find({}, {fields: {username: 1, emails: 1, profile: 1, roles: 1}});
});
Meteor.publish('userProfile', function(username) {
// Try to find the user by username
var user = Meteor.users.findOne({
username: username
});
// If we can't find it, mark the subscription as ready and quit
if (!user) {
this.ready();
return;
}
// If the user we want to display the profile is the currently logged in user
if(this.userId === user._id) {
// Then we return the curresonding full document via a cursor
return Meteor.users.find(this.userId);
} else {
return Meteor.users.find(user._id, {
fields: {
profile: 0
}
});
}
});
I want to do something similar with a pages collection that I've set up. Creating the collection works and the collection page has an _id field that is made upon creation.
Right now the program works nicely for users where mysite.com/# works. Now I want the same thing to work for mysite.com/&
I've basically attempted to do the exact same thing as I did in the above code with the user name but it wasn't working. I've checked to make sure my creation of the collection items are working and they are. But somehow I can't figure out how to do this same thing with collections since I'm relatively new to using routes.
This is what I've attempted:
Here's my routes:
var pageRoute = '/&:_id';
Router.route(pageRoute, {
name: 'page',
controller: 'PageController'
});
PageController = RouteController.extend({
template: 'page',
waitOn: function() {
return Meteor.subscribe('Page', this.params._id);
},
data: function() {
var _id = Router.current().params._id;
return Meteor.pages.findOne({
_id: _id
});
}
});
Server code:
Meteor.publish('pages', function() {
return Pages.find({});
});
Meteor.publish('Page', function(_id) {
// Try find the page by _id
var page = Meteor.pages.findOne({
_id: _id
});
// If we can't find it, mark the subscription as ready and quit
if (!page) {
this.ready();
return;
}
// If the page we want to display is not claimed, display it
if(true) {
return Meteor.pages.find(this._id);
} else {
// Redirect to the page
}
});
The Schema of the Page Collection:
_id: ,
createdAt: ,
CreatedBy: ,
claimedAt: ,
claimedBy: ,
Update:
I've scoped it down to this problem, I get the following error in the console server-side:
I20160202-11:16:24.644(2)? Exception from sub qrPage id 2kY6RKCTuCpBDbuzm TypeError: Cannot call method 'findOne' of undefined
I20160202-11:16:24.645(2)? at [object Object].process.env.MAIL_URL [as _handler] (server/ecclesia.life_server.js:40:33)
I20160202-11:16:24.645(2)? at maybeAuditArgumentChecks (livedata_server.js:1698:12)
I20160202-11:16:24.645(2)? at [object Object]._.extend._runHandler (livedata_server.js:1023:17)
I20160202-11:16:24.645(2)? at [object Object]._.extend._startSubscription (livedata_server.js:842:9)
I20160202-11:16:24.646(2)? at [object Object]._.extend.protocol_handlers.sub (livedata_server.js:614:12)
I20160202-11:16:24.646(2)? at livedata_server.js:548:43
This error occurs whenever I try to direct to mysite.com/&<_id>
Based on this website: https://perishablepress.com/stop-using-unsafe-characters-in-urls/
It looks like # is considered an unsafe character to use in a URL string. On the web page above, it looks like there are several symbols you could use instead as safe characters.
I just tried this on my own machine, and I don't think Meteor plays nicely when the # is introduced in the URL.
This got it working...
Publications:
Meteor.publish('qrpages', function() {
return QRPages.find({});
});
Meteor.publish('qrPage', function(id) {
// Try find the qrpage by _id
var qrpage = QRPages.find({_id: id});
// If we can't find it, mark the subscription as ready and quit
if (!qrpage) {
this.ready();
return;
}
return qrpage;
});
Routes:
var qrpageRoute = '/$:_id';
Router.route(qrpageRoute, {
name: 'qrpage',
controller: 'QRController'
});
QRController = RouteController.extend({
template: 'qrpage',
waitOn: function() {
var id = this.params._id;
return Meteor.subscribe('qrPage', id);
},
data: function() {
var id = this.params._id;
return QRPages.findOne({
_id: id
});
}
});

Weird undefined error on server

I have the following meteor method
hasNoPendingPayments: function() {
var userId = Meteor.userId();
console.log(userId); <---------------------- correctly logs userId
var user = Users.findOne({_id: userId }, { fields: { services: 0 } });
console.log(user); <-------------------------- logs 'undefined'
return hasNoPendingPayments(user);
},
This private helper I call from the above
hasNoPendingPayments = function(user) {
// console.log('hasNoPendingPayments ');
// console.log(user);
var payments = Payments.find({ userId: user._id, status: {
$in: [Payments.States.PENDING, Payments.States.PROCESSING]}
});
return payments.count() === 0;
};
And I call it from the client here
Template.payments.created = function() {
this.hasNoPendingPayments = new ReactiveVar(false);v
};
Template.payments.rendered = function () {
Session.set('showPaymentRequestForm', false);
var self = this;
Meteor.call('hasNoPendingPayments', function(error, result) {
if (result === true) { self.hasNoPendingPayments.set(true); }
});
...
However, I get an undefined error on the server when I load the template initially (I marked where in code). Although, when I try call the same query on the client with the same userId, i correctly gets the user record
Any idea as to why this is?
Try with this.
Template.payments.rendered = function () {
Session.set('showPaymentRequestForm', false);
var self = this;
if(Meteor.userId()){
Meteor.call('hasNoPendingPayments', function(error, result) {
if (result === true) { self.hasNoPendingPayments.set(true); }
});
}else{
console.log("Seems like user its not logged in at the moment")
}
Maybe when you make the Meteor.call, the data its not ready
Also just to be sure, when you run Users.findOne({_id: userId }, { fields: { services: 0 } }); on console.log what you get?
Maybe the find is wrong or have some typo
update
Router.map(function()
{
this.route('payments',
{
action: function()
{
if (Meteor.userId())
this.render();
} else{
this.render('login') // we send the user to login Template
}
}
}
or waitOn
Router.map(function () {
this.route('payments', {
path: '/payments',
waitOn: function(){
return Meteor.subscribe("userData"); //here we render template until the subscribe its ready
}
});
});
Meteor stores all the user records in Meteor.users collection
so try Meteor.users.findOne({_id: userId }....)
Instead of Users.findOne({_id: userId }, { fields: { services: 0 } });
in your server method

Need help updating Router.js for upgrade to Meteor 1.0 and iron:router package

I'm in the midst of updating from Meteor 8.2 to Meteor 1.0. I've removed all my old meteorite packages and installed the relevant meteor package system packages. I had to install the new iron-router package and I'm getting the following error in my console on meteor run:
Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?
The migration notes for the package say: "onBeforeAction hooks now require you to call this.next(), and no longer take a pause() argument."
I tried following the example by remove pause from the function and adding this.next(); after the else statement, but to no avail.
How to edit my router so it uses the new onBeforeAction hook? Also, anything else you can call out from the migration that might be problematic would be much appreciated. Thanks!
Here's my router file:
/*****************************************************************************/
/* Client and Server Routes */
/*****************************************************************************/
// TODO: use these as per the Event Mind CLI tool.
//Router.configure({
// templateNameConverter: 'upperCamelCase',
// routeControllerNameConverter: 'upperCamelCase'
//});
Router.configure({
layoutTemplate: 'devLayout',
notFoundTemplate: 'devMain',
loadingTemplate: 'loading'
});
Router.onRun(function () {Session.set("waiting-on", null); });
Router.onBeforeAction(function() { Alerts.clearSeen(); });
var filters = {
nProgressHook: function (pause) {
// we're done waiting on all subs
if (this.ready()) {
NProgress.done();
} else {
NProgress.start();
pause(); // stop downstream funcs from running
}
}
};
Router.onBeforeAction(filters.nProgressHook);
Meteor.startup(function () {
Router.map(function () {
this.route('loading');
// reset password urls use hash fragments instead of url paths/query
// strings so that the reset password token is not sent over the wire
// on the http request
this.route('reset-password', {
template: 'devMain',
layoutTemplate: 'devLayout',
onRun: function () {
var token = this.params.hash;
Meteor.logout(function () {
Session.set("viewing-settings", true);
Session.set("set-password-token", token);
Session.set("settings-set-password", true);
// Session.set("enrolling", true) // do something special?
});
}
});
this.route('verify-email', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Accounts.verifyEmail(token, function (err) {
if (!err) {
Alerts.throw({
message: "Your email address is now verified!",
type: "success", where: "main",
autoremove: 3000
});
Router.go('home');
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason +
"\". Try again?",
type: "danger", where: "main"
});
Session.set("viewing-settings", true);
Router.go('home');
}
});
}
});
this.route('leave-game', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("leaveGameViaToken", token, function (err, res) {
if (!err) {
// Idempotently verify user's email,
// since they got the token via email.
Accounts.verifyEmail(token);
if (res.error) {
// e.g. "Leave-game link is for unknown game"
Alerts.throw({
message: res.error.reason, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "OK, you are no longer in this game.",
type: "success", where: res.gameId
});
Router.go("devDetail", {_id: res.gameId});
}
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason + "\".",
type: "danger", where: "main"
});
Router.go("home");
}
});
}
});
this.route('game-on', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("gameOnViaToken", token, function (err, res) {
if (err || (res && res.error)) {
errorMessage = err ? "Hmm, something went wrong: \"" + err.reason + "\"." : res.error.reason;
Alerts.throw({
message: errorMessage, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "Woohoo! Players will be notified.",
type: "success", where: res.gameId
});
Router.go("devDetail", {_id: res.gameId, token: token });
}
});
}
});
this.route('cancel-game', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("cancelGameViaToken", token, function (err, res) {
if (!err) {
Accounts.verifyEmail(token);
if (res.error) {
Alerts.throw({
message: res.error.reason, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "OK, your game is now cancelled, and players "
+ "will be notified.",
type: "success", where: "main"
});
Router.go("home");
}
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason + "\".",
type: "danger", where: "main"
});
Router.go("home");
}
});
}
});
// quite similar to 'leave-game' route
this.route('unsubscribe-all', {
template: 'devMain',
layoutTemplate: 'devLayout',
action: function () {
var self = this;
var token = self.params.hash;
Meteor.call("unsubscribeAllViaToken", token, function (err, res) {
if (!err) {
// Idempotently verify user's email,
// since they got the token via email.
Accounts.verifyEmail(token);
if (res.error) {
// e.g. "Token provided in link is not an unsubscribe-all token"
Alerts.throw({
message: res.error.reason, type: "danger", where: "main"
});
Router.go("home");
} else {
Alerts.throw({
message: "OK, you will no longer receive emails "
+ "from Push Pickup.",
type: "success", where: "main"
});
Router.go("home");
}
} else {
Alerts.throw({
message: "Hmm, something went wrong: \""+err.reason + "\".",
type: "danger", where: "main"
});
Router.go("home");
}
});
}
});
this.route('enroll-account', {
template: 'devMain',
layoutTemplate: 'devLayout',
onRun: function () {
var token = this.params.hash;
Meteor.logout(function () {
Session.set("viewing-settings", true);
Session.set("set-password-token", token);
Session.set("settings-set-password", true);
// Session.set("enrolling", true) // do something special?
});
}
});
// the home page. listing and searching for games
this.route('home', {
path: '/',
template: 'devMain',
layoutTemplate: 'devLayout'
});
// typical user interaction with a single game
this.route('devDetail', {
path: '/g/:_id/:token?',
layoutTemplate: 'devLayout',
onRun: function () {
Session.set("joined-game", null);
},
waitOn: function () {
return Meteor.subscribe('game', this.params._id);
},
onBeforeAction: function (pause) {
Session.set("soloGame", this.params._id);
},
data: function () {
var game = Games.findOne(this.params._id);
if (game) {
Session.set("gameExists", true);
}
return game;
},
action: function () {
var token = this.params.token;
if (Session.get("gameExists")) {
this.render();
} else {
Router.go('home');
Alerts.throw({
message: "Game not found",
type: "warning", where: "top"
});
}
if (token) {
Meteor.call("sendReminderEmailsViaToken", token, function (err, res) {
var errorMessage;
Accounts.verifyEmail(token);
if (err || (res && res.error)) {
errorMessage = err ? "Hmm, something went wrong: \"" + err.reason + "\"." : res.error.reason;
Alerts.throw({
message: errorMessage, type: "danger", where: "main"
});
Router.go("home");
}
});
}
},
onStop: function () {
Session.set("soloGame", null);
Session.set("gameExists", null);
}
});
this.route('devAddGame', {
path: '/addGame',
template: 'devEditableGame',
layoutTemplate: 'devLayout',
onRun: function () {
Session.set("selectedLocationPoint", null);
Session.set("newGameDay", null);
Session.set("newGameTime", null);
InviteList.remove({});
},
waitOn: function() {
Meteor.subscribe('recently-played');
},
data: function () {
return {
action: 'add',
title: 'Add game',
submit: 'Add game'
};
}
});
this.route('invitePreviousPlayers', {
path: 'invitePlayers',
template: 'invitePreviousPlayers',
layoutTemplate: 'devLayout'
});
this.route('devEditGame', {
path: '/editGame/:_id',
template: 'devEditableGame',
layoutTemplate: 'devLayout',
onRun: function () {
Session.set("selectedLocationPoint", null);
},
waitOn: function () {
return Meteor.subscribe('game', this.params._id);
},
onBeforeAction: function (pause) {
Session.set("soloGame", this.params._id);
},
data: function () {
return _.extend({
action: 'edit',
title: 'Edit game',
submit: 'Update game'
}, Games.findOne(this.params._id));
},
action: function () {
var self = this;
var user = Meteor.user();
var game = self.data();
if (user && user._id === game.creator.userId ||
user && user.admin) {
self.render();
} else {
Router.go('home');
}
}
});
this.route('adminView', {
path: '/admin',
onBeforeAction: function () {
var user = Meteor.user();
if (!user || !user.admin) {
this.render('home');
}
}
});
});
});
New onBeforeAction hook must call this.next(); You must call it in every onBeforeAction. For example your admin route in new Iron Router would look like this:
Router.route('/admin', {
name: 'adminView',
onBeforeAction: function () {
var user = Meteor.user();
if (!user || !user.admin) {
this.render('home');
}
this.next();
}
});
Replace all this.route(...) in Router.map with Router.route('/path', options) and remove Router.map()
Your global onBeforeAction will look like this:
Router.onBeforeAction(function() {
Alerts.clearSeen();
this.next();
});
Also, you don't need to wrap your routes in Meteor.startup(...). You can remove it.
And there is no pause parameter anymore, instead of pause call this.next() outside condition:
var filters = {
nProgressHook: function () {
// we're done waiting on all subs
if (this.ready()) {
NProgress.done();
} else {
NProgress.start();
}
this.next();
}
};
Router.onBeforeAction(filters.nProgressHook);

Meteor JS Iron Router subscribe to data

I am using Meteor 0.8 and iron-router.
I have a single route that I use to subscribe to a number of collections. What I would like to do is create a document when you arrive on the route and then subscribe to it so I can access it.
This is my route:
this.route('product_collection', {
path: '/collection/:collection',
template: 'product_collection',
layoutTemplate: 'layout-new',
waitOn: function() { return [ Meteor.subscribe('specific_product_collection', this.params.collection), Meteor.subscribe('product_types')] },
data: function () { return ProductCollections.findOne({ url: this.params.collection }) },
onBeforeAction: function(pause){
//Prevent double rendering
if (this.ready()) {
var productType = ProductTypes.findOne({url: this.params.collection })
console.log(productType)
if(productType){
var product = {
productType: productType._id
};
Session.set("productTypeId", productType._id);
Products.insert(product, function(error, id) {
if (error) {
console.error(error);
set_notification('Oops, something went wrong.', 'Please try again later.');
} else {
Session.set("productId", id);
Session.set("productName", productType.name );
//Not working
Meteor.subscribe('specific-product', id);
console.log('should be subscribed')
}
});
}
} else {
console.log('loading')
this.render('loading');
pause();
}
},
});
Ok,
I found out the ID was different on the server vs client side location. So ended up writing a meteor method call on the server.
See reference: Retrieve _id after insert in a Meteor.method call

Resources