Server methods outside of Meteor.methods - meteor

On the client I need a helper method that returns true or false depending on whether the user is eligible for a payment request.
However, I can't really use a Meteor.method for this, because they don't return a value on the client.
Instead, I have done this and would like to know if this poses any security holes or if there is a preferable approach
Server:
...
// Constants
//
_.extend(Payments, {
MINIMUM_REQUIRED_FOR_REQUEST: 100
});
// Public
//
Meteor.methods({
});
canRequestPayment = function(userId) {
var user = Meteor.users.findOne(userId, { fields: { earnings: 1 } });
if (_.isUndefined(user)) { throw new Meteor.Error('user-not-found', 'User not found'); }
return hasEnoughCreditForRequest(user) && hasNoPendingPayments(user);
};
// Private
//
var hasNoPendingPayments = function(user) {
return Payments.find({ userId: user._id, state: 'pending' }).count() === 0;
};
var hasEnoughCreditForRequest = function(user) {
var period = user.earnings.period;
return period >= Payments.MINIMUM_REQUIRED_FOR_REQUEST;
};
As can be seen, I have created two helper methods with var, to mimic private behavior, and then I have the canRequestPayment method which is accessable outside of the file, and that I call on the client instead of a Meteor.method
Client:
Template.payments.helpers({
eligibleForPaymentRequest: function() {
return canRequestPayment(Meteor.userId());
},

Related

How do I dynamically publish collections via a Meteor method?

I dynamically create collections with this method:
createPlist: function(jid) {
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Plist[jid].insert({
...,
...,
public:true,
uid:this.userId
});
}
Then I am wanting to publish these selectively, and I am attempting to do it via a method:
getPlist: function(jid,pid) {
// var future = new Future();
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Meteor.publish(pid, function() {
console.log(Plist[jid].find({}));
// future["return"](Plist[jid].find({}));
return Plist[jid].find();
});
// return future.wait();
},
This returns 'undefined' to my Template helper, and returns nothing (i.e. waits forever) using Future.
Any user can log in and create a Plist collection, which can be either public or not. A user can also subscribe to any collection where public is true. The variable jid is passed to the method 'getPlist' from the template. It is stored in the user's Session.
Thanks! I hope I have explained it well enough!
And of course the template:
Template.plist.helpers({
getPlist: function() {
Pl = []
jid = Session.get('jid');
//alert(jid);
pid = "pl_"+jid;
// console.log(pid);
Meteor.call('getPlist', jid, pid, function(err,res) {
console.log(res); //returns undefined
try {
Pl[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log(e);
}
Meteor.subscribe(pid);
// return Pl[jid].find({}).fetch();
});
}

How to show documents from multiple remote publication in the template?

I wish to use Meteor to subscribe a few remote publication via DDP. Then show the documents in one template. Here is what I did:
Posts = {};
var lists = [
{server: "localhost:4000"},
{server: "localhost:5000"}
];
var startup = function () {
_.each(lists, function (list) {
var connection = DDP.connect(`http://${list.server}`);
Posts[`${list.server}`] = new Mongo.Collection('posts', {connection: connection});
connection.subscribe("allPosts");
});
}
startup();
This file is at client folder. Every startup, in this example, at browser I have two client collections Posts["localhost:4000"] and Posts["localhost:5000"], both are same schema. I know this format (Collection[server]) is ugly, please tell me if there is a better way.
Is there a way to show these client collections in the same template with reactive. Like this:
Template.registerHelper("posts", function () {
return Posts.find({}, {sort: {createdAt: -1}});
});
I think Connected Client is a big part of the Meteor. There should be a best practice to solve this problem, right?
Solved.
Connect to multiple servers via DDP, then observe their collections reactive via cursor.observeChanges.
Posts = {};
PostsHandle = {};
// LocalPosts is a local collection lived at browser.
LocalPosts = new Mongo.Collection(null); // null means local
// userId is generated by another Meteor app.
var lists = [
{server: "localhost:4000", userId: [
"hocm8Cd3SjztwtiBr",
"492WZqeqCxrDqfG5u"
]},
{server: "localhost:5000", userId: [
"X3oicwXho45xzmyc6",
"iZY4CdELFN9eQv5sa"
]}
];
var connect = function () {
_.each(lists, function (list) {
console.log("connect:", list.server, list.userId);
var connection = DDP.connect(`http://${list.server}`);
Posts[`${list.server}`] = new Mongo.Collection('posts', {connection: connection}); // 'posts' should be same with remote collection name.
PostsHandle[`${list.server}`] = connection.subscribe("posts", list.userId);
});
};
var observe = function () {
_.each(PostsHandle, function (handle, server) {
Tracker.autorun(function () {
if (handle.ready()) {
console.log(server, handle.ready());
// learn from http://docs.meteor.com/#/full/observe_changes
// thank you cursor.observeChanges
var cursor = Posts[server].find();
var cursorHandle = cursor.observeChanges({
added: function (id, post) {
console.log("added:", id, post);
piece._id = id; // sync post's _id
LocalPosts.insert(post);
},
removed: function (id) {
console.log("removed:", id);
LocalPosts.remove(id);
}
});
}
})
});
}
Template.posts.onCreated(function () {
connect(); // template level subscriptions
});
Template.posts.helpers({
posts: function () {
observe();
return LocalPosts.find({}, {sort: {createdAt: -1}}); // sort reactive
}
});

How to make a Meteor template helper reactive without changing collection data

I'm writing a small application that shows live hits on a website. I'm displaying the hits as a table and passing each one to a template helper to determine the row's class class. The idea is that over time hits will change colour to indicate their age.
Everything renders correctly but I need to refresh the page in order to see the helper's returned class change over time. How can I make the helper work reactively?
I suspect that because the collection object's data isn't changing that this is why and I think I need to use a Session object.
Router:
Router.route('/tracked-data', {
name: 'tracked.data'
});
Controller:
TrackedDataController = RouteController.extend({
data: function () {
return {
hits: Hits.find({}, {sort: {createdAt: -1}})
};
}
});
Template:
{{#each hits}}
<tr class="{{ getClass this }}">{{> hit}}</tr>
{{/each}}
Helper:
Template.trackedData.helpers({
getClass: function(hit) {
var oneMinuteAgo = Date.now() - 1*60*1000;
if (hit.createdAt.getTime() > oneMinuteAgo) {
return 'success';
} else {
return 'error';
}
}
});
I've managed to get this working though I'm not sure it's the 'right' way to do it. I created a function that is called every second to update Session key containing the current time. Then, in my helper, I can create a new Session key for each of the objects that I want to add a class to. This session key is based upon the value in Session.get('currentTime') and thus updates every second. Session is reactive and so the template updates once the time comparison condition changes value.
var updateTime = function () {
var time = Date.now();
Session.set('currentTime', time);
setTimeout(updateTime, 1 * 1000); // 1 second
};
updateTime();
Template.trackedData.helpers({
getClass: function(hit) {
var tenMinutesAgo = Session.get('currentTime') - 10*1000,
sessionName = "class_" + hit._id,
className;
className = (hit.createdAt.getTime() > tenMinutesAgo) ? 'success' : 'error';
Session.set(sessionName, className);
return Session.get(sessionName);
}
});
Update
Thanks for the comments. The solution I ended up with was this:
client/utils.js
// Note that `Session` is only available on the client so this is a client only utility.
Utils = (function(exports) {
return {
updateTime: function () {
// Date.getTime() returns milliseconds
Session.set('currentTime', Date.now());
setTimeout(Utils.updateTime, 1 * 1000); // 1 second
},
secondsAgo: function(seconds) {
var milliseconds = seconds * 1000; // ms => s
return Session.get('currentTime') - milliseconds;
},
};
})(this);
Utils.updateTime();
client/templates/hits/hit_list.js
Template.trackedData.helpers({
getClass: function() {
if (this.createdAt.getTime() > Utils.secondsAgo(2)) {
return 'success';
} else if (this.createdAt.getTime() > Utils.secondsAgo(4)) {
return 'warning';
} else {
return 'error';
}
}
});

Mongoose pre.save() async middleware not working on record creation

I am using keystone#0.2.32. I would like to change the post category to a tree structure. The below code is running well except when I create a category, it goes into a deadlock:
var keystone = require('keystone'),
Types = keystone.Field.Types;
/**
* PostCategory Model
* ==================
*/
var PostCategory = new keystone.List('PostCategory', {
autokey: { from: 'name', path: 'key', unique: true }
});
PostCategory.add({
name: { type: String, required: true },
parent: { type: Types.Relationship, ref: 'PostCategory' },
parentTree: { type: Types.Relationship, ref: 'PostCategory', many: true }
});
PostCategory.relationship({ ref: 'Post', path: 'categories' });
PostCategory.scanTree = function(item, obj, done) {
if(item.parent){
PostCategory.model.find().where('_id', item.parent).exec(function(err, cats) {
if(cats.length){
obj.parentTree.push(cats[0]);
PostCategory.scanTree(cats[0], obj, done);
}
});
}else{
done();
}
}
PostCategory.schema.pre('save', true, function (next, done) { //Parallel middleware, waiting done to be call
if (this.isModified('parent')) {
this.parentTree = [];
if(this.parent != null){
this.parentTree.push(this.parent);
PostCategory.scanTree(this, this, done);
}else
process.nextTick(done);
}else
process.nextTick(done); //here is deadlock.
next();
});
PostCategory.defaultColumns = 'name, parentTree';
PostCategory.register();
Thanks so much.
As I explained on the issue you logged on Keystone here: https://github.com/keystonejs/keystone/issues/759
This appears to be a reproducible bug in mongoose that prevents middleware from resolving when:
Parallel middleware runs that executes a query, followed by
Serial middleware runs that executes a query
Changing Keystone's autokey middleware to run in parallel mode may cause bugs in other use cases, so cannot be done. The answer is to implement your parentTree middleware in serial mode instead of parallel mode.
Also, some other things I noticed:
There is a bug in your middleware, where the first parent is added to the array twice.
The scanTree method would be better implemented as a method on the schama
You can use the findById method for a simpler parent query
The schema method looks like this:
PostCategory.schema.methods.addParents = function(target, done) {
if (this.parent) {
PostCategory.model.findById(this.parent, function(err, parent) {
if (parent) {
target.parentTree.push(parent.id);
parent.addParents(target, done);
}
});
} else {
done();
}
}
And the fixed middleware looks like this:
PostCategory.schema.pre('save', function(done) {
if (this.isModified('parent')) {
this.parentTree = [];
if (this.parent != null) {
PostCategory.scanTree(this, this, done);
} else {
process.nextTick(done);
}
} else {
process.nextTick(done);
}
});
I think it's a bug of keystone.js. I have changed schemaPlugins.js 104 line
from
this.schema.pre('save', function(next) {
to
this.schema.pre('save', true, function(next, done) {
and change from line 124 to the following,
// if has a value and is unmodified or fixed, don't update it
if ((!modified || autokey.fixed) && this.get(autokey.path)) {
process.nextTick(done);
return next();
}
var newKey = utils.slug(values.join(' ')) || this.id;
if (autokey.unique) {
r = getUniqueKey(this, newKey, done);
next();
return r;
} else {
this.set(autokey.path, newKey);
process.nextTick(done);
return next();
}
It works.

angular service and http request

creating service
myApp.factory('serviceHttp', ['$http', function(http) {
http.get($scope.url).then(function(result){
serviceVariable = result;
}
return serviceVariable;
}
Controller
function appController($scope, serviceHttp){
$scope.varX = serviceHttp;
if(serviceHttp){
// decision X;
} else {
// decision Y;
}
}
view:
input(ng-if='varX') serviceHttp Exist
input(ng-if='!varX') serviceHttp noExist
The above code always shows varX not exist because app installs during http call of service. I want to use angular service to inject variables from server to make decision at time of booting the application.
Try to rewrite factory by this way that returns promise:
myApp.factory('serviceHttp', ['$http', function(http) {
var factory = {
query: function () {
var data = http.get($scope.url).then(function(result){
return result;
},
function (result) {
alert("Error: No data returned");
});
return data;
}
}
return factory;
}]);
From controller:
serviceHttp.query().then(function (result) {
$scope.varX = = result;
}
Here is Demo Fiddle
in demo we used other URL source
If i correct understand you, you should doing it like this:
var app = angular.module('YourModule', []);
app.factory("serviceHttp", function($http) {
var serviceHttp={};
serviceHttp.yourGetRequest = function(yourUrl) {
return $http.get(yourUrl);
};
return serviceHttp;
});
And for example, controller:
var Controller = function($scope,serviceHttp) {
$scope.varX='';
$scope.loading = true;
var returnArr = serviceHttp.yourGetRequest($scope.url).success(function(dataFromServer) {
$scope.loading = false;
$scope.varX = dataFromServer;
})
};
in view you can use ng-show, like this:
<div ng-show="loading" class="loading"><img src="../styles/ajax-loader-large.gif"></div>
When your application start loading, $scope.loading = true and this div shown, and when you get response from server $scope.loading became false and div doesn't show.

Resources