Meteor collection updated on server, not reflecting on client - meteor

I am trying to create a Meteor app that stores content in a Meteor collection to be passed between the server and the client to display a success message after an asynchronous api call through the twit package.
However, I am running into an issue where when I update the collection on the server and the updates are not reflected on the client. My code is as follows:
/lib
Alerts = new Meteor.Collection("alerts");
/client
Template.suggestionForm.events({
"submit form": function (e) {
return Meteor.call('submitMessage', message);
}
});
Meteor.subscribe('alerts');
Meteor.startup(function() {
Tracker.autorun(function() {
console.log(Alerts.find());
})
});
/server
Fiber = Npm.require('fibers')
Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
Meteor.publish("alerts", function(){
Alerts.find();
});
Meteor.methods({
submitMessage: function(message) {
this.unblock();
Twit.post('statuses/update', { 'status': message }, function(err, data, response) {
Fiber(
Alerts.remove({});
Alerts.insert({response: err});
).run();
}));
}
});
When I submit the form the function calls just fine and updates the collection, however the Tracker.autorun() does not run. Any ideas why this is happening or how I can make the client listen for changes in collections would be super helpful. Thank you!

Remember to return the resulting cursor in the publish():
Meteor.publish("alerts", function(){
return Alerts.find();
});
Reference: http://docs.meteor.com/#/full/meteor_publish
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor's documents to each subscribed client. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
and
Alternatively, a publish function can directly control its published record set by calling the functions added (to add a new document to the published record set), changed (to change or clear some fields on a document already in the published record set), and removed (to remove documents from the published record set). These methods are provided by this in your publish function.
If a publish function does not return a cursor or array of cursors, it is assumed to be using the low-level added/changed/removed interface, and it must also call ready once the initial record set is complete.

Related

Insert new collection after function runs on server

When I return the geocode from googles API I'm trying to save it into my database. I've been trying to use the code below, to just insert a Test document with no luck. I think it has something to do with meteor being asynchronous. If I run the insert function before the googleMapsClient.geocode function it works fine. Can someone show me what I'm doing wrong.
Meteor.methods({
'myTestFunction'() {
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Test.insert({test: 'test name'});
}
});
}
});
I see now where you got the idea to run the NPM library on the client side, but this is not what you really want here. You should be getting some errors on the server side of your meteor instance when you run the initial piece of code you gave us here. The problem is that the google npm library runs in it's own thread, this prevents us from using Meteor's methods. The easiest thing you could do is wrap the function with Meteor.wrapAsync so it would look something like this.
try {
var wrappedGeocode = Meteor.wrapAsync(googleMapsClient.geocode);
var results = wrappedGeocode({ address : "testAddress" });
console.log("results ", results);
Test.insert({ test : results });
} catch (err) {
throw new Meteor.Error('error code', 'error message');
}
You can find more info by looking at this thread, there are others dealing with the same issue as well
You should run the googleMapsClient.geocode() function on the client side, and the Test.insert() function on the server side (via a method). Try this:
Server side
Meteor.methods({
'insertIntoTest'(json) {
Test.insert({results: json.results});
}
});
Client side
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Meteor.call('insertIntoTest', response.json);
}
});
Meteor Methods should be available on the both the server and client sides. Therefore make sure that your method is accessible by server; via proper importing on /server/main.js or proper folder structuring.
(If a method contains a secret logic run on the server, it should be isolated from the method runs on both server & client, though)

How do I reliably pull data from Meteor server collections to client collections when using an existing mongodb as MONGO_URL?

I know that there are several methods to share collections on both the client and server -- namely either in top level lib folder or publish/subscribe model -- but when I try either of these things when using mongodb running at localhost:27017 as my MONGO_URL, I am not reliably getting data on the client. Occasionally console.log(myCollection.findOne({})) will return expected data in the browser but most of the time it returns undefined.
//Client side code
Template.controls.onCreated(function controlsOnCreated() {
Meteor.subscribe("myEvents");
Events = new Mongo.Collection("events");
});
//Server side code
Meteor.startup(() => {
Events = new Mongo.Collection("events");
}
Meteor.publish('myEvents', function() {
console.log(Events.find());
return Events.find();
});
UPDATED CODE -- returns Events on server but not client:
//Client
Template.controls.onCreated(function controlsOnCreated() {
this.subscribe("myEvents");
});
//Server
if (Meteor.isServer) {
Meteor.publish("myEvents", function() {
return Events.find();
});
}
// /collections/events.js
Events = new Mongo.Collection("events");
UPDATE 2:
I am attempting to verify the publication in the browser after the page has rendered, calling Events.findOne({}) in the Chrome dev tools console.
on your client:
Template.controls.onCreated(function controlsOnCreated() {
Meteor.subscribe("myEvents");
Events = new Mongo.Collection("events");
});
that is an odd place to define the Events variable. typically, you would put that line of code in a JS file common to both platform. e.g.
collections/events.js:
Events = new Mongo.Collection("events");
when that line runs on the server, it defines the mongo collection and creates a server-side reference to it. when it runs on the client, it creates a collection by that name in mini-mongo and creates a client-side reference to it.
you can write your onCreated like this (note "this" instead of "Meteor"):
Template.controls.onCreated(function() {
this.subscribe("myEvents");
});
you don't say where on the client you ran your console.log with the find(). if you did it in the onCreated(), that's too early. you're seeing the effects of a race condition. typically, you might use it in a helper:
Template.controls.helpers({
events() {
return Events.find({});
}
});
and display the data in the view:
{{#each event in events}}
{{event.name}}
{{/each}}
that helper will run reactively once the data from the publish shows up.

Meteor.userId() not available in ConnectHandlers

I am trying to create a file upload feature in Meteor where a logged in user is able to upload a file to the server under a directory named after their username. I have the basics working but when I take it a step further by checking the logged in user ID, things start breaking. Specifically:
WebApp.connectHandlers.use('/upload/', function(req, res) {
if (this.userId) {
// Do cool stuff.
} else {
res.writeHead(500, {"content-type":"text/html"});
res.end("this.userId = " + this.userId); // End the response.
}
});
Result:
this.userId = undefined
And...
WebApp.connectHandlers.use('/upload/', function(req, res) {
if (Meteor.userId()) {
// Do cool stuff.
} else {
res.writeHead(500, {"content-type":"text/html"});
res.end("Meteor.userId() = " + Meteor.userId()); // End the response.
}
});
Result:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
at Object.Meteor.userId (packages/accounts-base/accounts_server.js:19:1)
at Object.Package [as handle] (packages/cool_package/upload.js:34:1)
at next (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:190:15)
at Function.app.handle (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:198:3)
at Object.fn [as handle] (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:74:14)
at next (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:190:15)
at Object.WebAppInternals.staticFilesMiddleware (packages/webapp/webapp_server.js:331:1)
at packages/webapp/webapp_server.js:625:1
The code above is included in a Meteor package I'm developing. The package.js file specifies that the code should run on the server:
api.add_files("upload.js", "server");
So my questions are:
What is the correct way to check the logged in user ID and username?
Can this code be moved to an Iron Router route instead?
It looks like the line
WebApp.connectHandlers.use('/upload/', function(req, res) {
Is Express.js or similar code -- if so, you have broken out of the Meteor frameowrk providing your own REST services etc. If that is the case you also have to provide your own user management and authentication scheme for incoming REST calls, just as you would in any other bare-bones REST applications

How to protect a file directory and only allow authenticated users to access the files?

how do I restrict a folder, so only those who logged in into my Meteor app can download files?
I looked into multiple ways of doing this, but the main problem is that I can't access ( I get null.) with:
Meteor.user() or this.userId()
I tried:
__meteor_bootstrap__.app
.use(connect.query())
.use(function(req, res, next) {
Fiber(function () {
// USER HERE?
}).run();
});
or
__meteor_bootstrap__.app.stack.unshift({
route: "/protected/secret_document.doc", // only users can download this
handle: function(req, res) { Fiber(function() {
// CHECK USER HERE ?
// IF NOT LOGGED IN:
res.writeHead(403, {'Content-Type': 'text/html'});
var content = '<html><body>403 Forbidden</body></html>';
res.end(content, 'utf-8');
}).run() }
});
You could try storing the files in mongodb, which would mean that they would then be hooked into your collection system and be queryable on the client and server. Then, just publish the relevant data to the client for specific users, or use Meteor.methods to expose information that way.
Example:
Assuming files are stored in MongoDB, let's first publish them to the client:
Meteor.publish("files", function(folder) {
if (!this.userId) return;
// the userHasAccessToFolder method checks whether
// this user is allowed to see files in this folder
if (userHasAccessToFolder(this.userId, folder))
// if so, return the files for that folder
// (filter the results however you need to)
return Files.find({folder: folder});
});
Then on the client, we autosubscribe to the published channel so that whenever it changes, it gets refreshed:
Meteor.startup(function() {
Meteor.autosubscribe(function() {
// send the current folder to the server,
// which will return the files in the folder
// only if the current user is allowed to see it
Meteor.subscribe("files", Session.get("currentFolder"));
});
});
NB. I haven't tested above code so consider it pseudocode, but it should point you in the general direction for solving this problem. The hard part is storing the files in mongodb!
i'd be more concerned as to why Meteor.user() isn't working.
a few questions:
are you on meteor 0.5.0?
have you added accounts-base to your meteor project?
have you used one of meteor's login systems (accounts-password, accounts-facebook, etc)? (optional - accounts-ui for ease of use?)
have you still got autopublish on? or have you set up publishing / subscription properly?
Meteor.user() should be the current user, and Meteor.users should be a Meteor Collection of all previous logged in users.

Meteor - Using collection on client startup

Why this code shows "0"? Shouldn't it return "1"?
Messages = new Meteor.Collection("messages");
if (Meteor.is_client) {
Meteor.startup(function () {
alert(Messages.find().count());
});
}
if (Meteor.is_server) {
Meteor.startup(function () {
Messages.insert({text: "server says hello"});
});
}
If I do the "Messages.find().count()" later, it returns 1.
By default, when a Meteor client starts up, it connects to the server and subscribes to documents in any Meteor.Collection you defined. That takes some time to complete, since there's always some amount of delay in establishing the server connection and receiving documents.
Meteor.startup() on the client is a lot like $() in jQuery -- it runs its argument once the client DOM is ready. It does not wait for your client's collections to receive all their documents from the server. So the way you wrote the code, the call to find() will always run too early and return 0.
If you want to wait to run code until after a collection is first downloaded from the server, you need to use Meteor.subscribe() to explicitly subscribe to a collection. subscribe() takes a callback that will run when the initial set of documents are on the client.
See:
meteor-publish
and meteor-subscribe
Just to follow up with a code example of how to know when a collection is ready to use on the client.
As #debergalis described, you should use the Meteor.subscribe approach - it accepts a couple of callbacks, notably onReady
For example:
if(Meteor.isClient){
Meteor.subscribe("myCollection", {
onReady: function(){
// do stuff with my collection
}
});
}

Resources