upload with meteor and dropbox - meteor

I'm trying to do an upload using dropbox, but something is going wrong and I don't know what it is. I've done some searches on the Internet and I couldn't find anything.
Here it's my code:
if (Meteor.isClient) {
Template.hello.helpers({
uploads:function(){
return Avatars.find();
},
images:function(){
return Images.find();
}
});
var avatarStoreLarge = new FS.Store.Dropbox("avatarsLarge");
var avatarStoreSmall = new FS.Store.Dropbox("avatarsSmall");
Avatars = new FS.Collection("avatars", {
stores: [avatarStoreSmall, avatarStoreLarge],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Template.hello.events({
'change .fileInput':function(event,template){
FS.Utility.eachFile(event,function(file){
var fileObj = new FS.File(file);
Avatars.insert(fileObj,function(err){
console.log(err);
})
})
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
var avatarStoreLarge = new FS.Store.Dropbox("avatarsLarge", {
key: "xxxxxxxxxxxxxxx",
secret: "xxxxxxxxxxxxxxx",
token: "xxxxxxxxxxxxxxx",
transformWrite: function(fileObj, readStream, writeStream) {
gm(readStream, fileObj.name()).resize('250', '250').stream().pipe(writeStream)
}
})
var avatarStoreSmall = new FS.Store.Dropbox("avatarsSmall", {
key: "xxxxxxxxxxxxxxx",
secret: "xxxxxxxxxxxxxxx",
token: "xxxxxxxxxxxxxxx",
beforeWrite: function(fileObj) {
fileObj.size(20, {store: "avatarStoreSmall", save: false});
},
transformWrite: function(fileObj, readStream, writeStream) {
gm(readStream, fileObj.name()).resize('20', '20').stream().pipe(writeStream)
}
})
Avatars = new FS.Collection("avatars", {
stores: [avatarStoreSmall, avatarStoreLarge],
filter: {
allow: {
contentTypes: ['image/*']
}
}
})
});
}
I did this example following this documentation.

First, check installed meteor packages: meteor list, to get code to work you have to install:
cfs:standard-packages
cfs:dropbox
Second, simplify your code to reduce error precursors (Example in docs required Image Manipulation packages), for example:
CLIENT-SIDE
var dataStorage = new FS.Store.Dropbox("imageData");
Images = new FS.Collection("imageData", {
stores: [dataStorage],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Template.helpers block
'imageToShow': function(){
return Images.find()
},
Template.events block
'click #imageToUpload': function(event, template) {
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {});
});
},
SERVER-SIDE
var dataStorage = new FS.Store.Dropbox("imageData", {
key: "...",
secret: "...",
token: "..."
});
Images = new FS.Collection("imageData", {
stores: [dataStorage],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Third, handle errors to figure out what's going wrong.
Fourth, it takes time to upload and download back images from dropbox, to test your app use light weighted files. Use console.log in client and server sides to see apps' flow.

First check if you have installed the following packages:
cfs:standard-packages
cfs:dropbox
cfs:gridfs
Install them:
meteor add cfs:standard-packages
meteor add cfs:dropbox
meteor add cfs:gridfs
Make sure your file "client/collections_client/avatar.js" looks like this:
Template.hello.helpers({
'imageToShow': function(){
return Images.find()
},
});
var dataStorage = new FS.Store.Dropbox("imageData");
Images = new FS.Collection("imageData", {
stores: [dataStorage],
filter: {
allow: {
contentTypes: ['image/*']
}
}
});
Template.hello.events({
'click #imageToUpload': function(event, template) {
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {});
});
},
});
"client/collections_client/hello.html"
<template name="hello">
<input type="file" name="teste" id="imageToUpload">
</template>
<body>
{{> hello}}
</body>
Assuming you have created your app:
"server/collections_server/avatar.js"
var dataStorage = new FS.Store.Dropbox("imageData", {
key: "yourAppKey",
secret: "yourAppSecret",
token: "youroAuth2Token"
});
Images = new FS.Collection("imageData", {
stores: [dataStorage]
});

Related

Meteor JS: "Mail not sent; to enable sending, set the MAIL_URL environment variable"

I am getting the following mail error:
"Mail not sent; to enable sending, set the MAIL_URL environment variable."
Not sure why. Please help. I double checked and the mail servers are fine. Tried as well everthing from meteor site:
https://docs.meteor.com/environment-variables.html#MAIL-URL
But still getting the error.
My code is in server folder and this is the config.js file:
AWS.config.update({
accessKeyId: Meteor.settings.private.s3Settings.awsAccessKeyId,
secretAccessKey: Meteor.settings.private.s3Settings.awsSecretKey,
});
STS = new AWS.STS();
S3 = new AWS.S3();
Mailer.config({
from: 'export#INSIDEDOMAIN.com',
replyTo: 'export#INSIDEDOMAIN.com',
addRoutes: false
});
Meteor.startup(() => {
Mailer.init({
templates: Templates
});
if (Meteor.isProduction) {
process.env.MAIL_URL = 'smtp://export%40INSIDEDOMAIN.com:INSIDEPASS#mail.INSIDEDOMAIN.com:465/';
}
Meteor.call('check_users_trial', function(err) {
if (err) {
console.log(err);
}
});
});
SyncedCron.start();
Meteor.methods({
check_users_trial: function() {
function checkSubscriptions() {
const fromDate = new Date(new Date().setDate(new Date().getDate() - 15)),
toDate = new Date(new Date().setDate(new Date().getDate() - 13));
const users = Accounts.users.find({
'profile.isSubscribed': false,
'profile.warningEmailSent': false,
createdAt: {
$gt: fromDate,
$lt: toDate
},
roles: {
$ne: 'admin'
}
});
users.forEach(function(user) {
if (!user.profile.isSubscribed) {
let isSubscriptionValid = false;
const today = new Date();
const registerDate = new Date(user.createdAt);
const checkDate = registerDate.setDate(registerDate.getDate() + 14);
if (checkDate > today.getTime()) {
isSubscriptionValid = true;
}
if (!isSubscriptionValid) {
Meteor.call('send_warining_email_trial', user.emails[0].address, function(error) {
if (error) {
console.log(error);
} else {
Accounts.users.update({
_id: user._id
}, {
$set: {
'profile.warningEmailSent': true
}
});
}
});
}
}
});
}
SyncedCron.add({
name: 'Send emails to users, whose trial period ended.',
schedule: function(parser) {
return parser.text('every 1 hours');
},
job: function() {
checkSubscriptions();
}
});
}
});
process.env.MAIL_URL is set only after startup() finishes, which is too late.
Move the following lines
if (Meteor.isProduction) {
process.env.MAIL_URL = 'smtp://export%40INSIDEDOMAIN.com:INSIDEPASS#mail.INSIDEDOMAIN.com:465/';
}
above your Meteor.startup call. And make sure isProduction is, in fact, true, otherwise that code will not execute.

Unable to display data from database

I am building a forum with Meteor and would like to display comments to answers. And the answers are on the same page as an individual question. I am able to save the comments once submitted (I can check the mongo database and I see that I am successfully submitting comments), but I'm not able to display them using templates. I would think the problem has something to do with publications and subscriptions, but I can't locate the error. Below are the relevant snippets of code.
answer_item.js
Template.answerItem.helpers({
submittedText: function() {
return this.submitted.toString();
},
comments: function() {
return Comments.find({answerId: this._id});
}
});
answer_item.html
<template name="answerItem">
...
<ul class="comments">
{{#each comments}}
{{> commentItem}}
{{/each}}
</ul>
...
</template>
comment_item.html
<template name="commentItem">
<li>
<h4>
<span class="author">{{author}}</span>
<span class="date">on {{submitted}}</span>
</h4>
<p>{{body}}</p>
</li>
</template>
comment_item.js
Template.commentItem.helpers({
submittedText: function() {
return this.submitted.toString();
}
});
lib/collections/comment.js
Comments = new Mongo.Collection('comments');
Meteor.methods({
commentInsert: function(commentAttributes) {
check(this.userId, String);
check(commentAttributes, {
answerId: String,
body: String
});
var user = Meteor.user();
var answer = Answers.findOne(commentAttributes.answerId);
if (!answer)
throw new Meteor.Error('invalid-comment', 'You must comment on an answer');
comment = _.extend(commentAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
Answers.update(comment.answerId, {$inc: {commentsCount: 1}});
comment._id = Comments.insert(comment);
return comment._id
}
});
router.js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() {
return [Meteor.subscribe('notifications')]
}
});
QuestionsListController = RouteController.extend({
template: 'questionsList',
increment: 5,
questionsLimit: function() {
return parseInt(this.params.questionsLimit) || this.increment;
},
findOptions: function() {
return {sort: this.sort, limit: this.questionsLimit()};
},
subscriptions: function() {
this.questionsSub = Meteor.subscribe('questions', this.findOptions());
},
questions: function() {
return Questions.find({}, this.findOptions());
},
data: function() {
var self = this;
return {
questions: self.questions(),
ready: self.questionsSub.ready,
nextPath: function() {
if (self.questions().count() === self.questionsLimit())
return self.nextPath();
}
};
}
});
NewQuestionsController = QuestionsListController.extend({
sort: {submitted: -1, _id: -1},
nextPath: function() {
return Router.routes.newQuestions.path({questionsLimit: this.questionsLimit() + this.increment})
}
});
FollowedQuestionsController = QuestionsListController.extend({
sort: {follows: -1, submitted: -1, _id: -1},
nextPath: function() {
return Router.routes.followedQuestions.path({questionsLimit: this.questionsLimit() + this.increment})
}
});
Router.route('/', {
name: 'home',
controller: NewQuestionsController
});
Router.route('/new/:questionsLimit?', {name: 'newQuestions'});
Router.route('/followed/:questionsLimit?', {name: 'followedQuestions'});
Router.route('/questions/:_id', {
name: 'questionPage',
waitOn: function() {
return [
Meteor.subscribe('singleQuestion', this.params._id),
Meteor.subscribe('answers', this.params._id),
Meteor.subscribe('comments', this.params._id)
];
},
data: function() { return Questions.findOne(this.params._id); }
});
Router.route('/questions/:_id/edit', {
name: 'questionEdit',
waitOn: function() {
return Meteor.subscribe('singleQuestion', this.params._id);
},
data: function() { return Questions.findOne(this.params._id); }
});
Router.route('/submit', {name: 'questionSubmit'});
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.render('accessDenied');
}
} else {
this.next();
}
}
Router.onBeforeAction('dataNotFound', {only: 'questionPage'});
Router.onBeforeAction(requireLogin, {only: 'questionSubmit'});
server/publications.js
Meteor.publish('comments', function(answerId) {
check(answerId, String);
return Comments.find({answerId: answerId});
});
It looks like you need to run a separate query in your comment publication, to get a list of all of the answers for the given question, then use the results of that query to get a list of all the comments for all of the answers.
Meteor.publish('comments', function(questionId) {
check(questionId, String);
var answerIds = _.pluck(Answers.find({'questionId': questionId}, {fields: {_id: 1}}).fetch(), '_id');
return Comments.find({answerId: {$in: answerIds});
});
EDIT
I have a similar feature within an app that I'm working on now, with the same issue you were running into. I spent a few hours on it yesterday and came to the conclusion that the issue has to do with the fact that the _.pluck statement converts the results from the Answers cursor to an array, which prevents the publish function from being reactive.
After looking into several solutions the best one I found was the publish composite package. The syntax is a little verbose, but it gets the job done. To make it all work properly you need to merge all of the publish functions for the question, answers, and comments all into one publish function. Under the covers it creates observeChanges watchers on each of the answers under the question, so it can be reactive.
Meteor.publishComposite('question', function(questionId) {
return {
find: function() {
return Questions.find({_id: questionId});
},
children: [
{
find: function(question) {
return Answers.find({questionId: question._id});
},
children: [
{
find: function(answer, question) {
return Comments.find({answerId: answer._id});
}
}
]
}
]
}
});

How to save to /public in Meteor with cfs:filesystem and collectionfs?

/lib/collections/images.js
var imageStore = new FS.Store.FileSystem("images", {
// what should the path be if I want to save to /public/assets?
// this does not work
path: "/assets/images/",
maxTries: 1
});
Images = new FS.Collection("images", {
stores: [imageStore]
});
Images.deny({
insert: function() {
return false;
},
update: function() {
return false;
},
remove: function() {
return false;
},
download: function() {
return false;
}
});
Images.allow({
insert: function() {
return true;
},
update: function() {
return true;
},
remove: function() {
return true;
},
download: function() {
return true;
}
});
/client/test.html
<template name="test">
<input type="file" name="myFileInput" class="myFileInput">
</template>
/client/test.js
Template.test.events({
'change .myFileInput': function(event, template) {
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {
if (err){
// handle error
} else {
// handle success
}
});
});
},
});
For the path, if I use:
path: "/public/assets/images",
Error: EACCES, permission denied '/public'
path: "/assets/images",
Error: EACCES, permission denied '/assets'
path: "~/assets/images",
This works, but it saves the image to /home/assets/images on my Linux machine. The path isn't relative to the Meteor project at all.
I have resolved this issue using meteor-root package:
https://atmospherejs.com/ostrio/meteor-root
Files = new FS.Collection("files", {
stores: [new FS.Store.FileSystem("images", {path: Meteor.absolutePath + '/public/uploads'})]
});
So now it stores files in app/public/uploads/
Hope this helps!
/ doesn't stand for root of your site. / stands for root of your system. The meteor app runs as your user.
What you'll want do, is use a relative path. It's possible that the collectionFS fs.write operation isn't done from the apps root. So alternatively you could use path: process.env.PWD + '/public/assets/images'
You can use
var homeDir = process.env.HOMEPATH;
tmpDir: homeDir + '/uploads/tmp'

Meteor.js - How to observeChanges() from client side

I'm a total newbie to Meteor. I'm trying to create a proof of concept using Meteor where updates to the Mongo oplog get displayed to the browser screen.
For now, I'm trying to adapt the simple-todos to this purpose. I got the server logging updates to the terminal but no idea how to transfer this to the client browser screen?
if(Meteor.isClient) {
// counter starts at 0
Session.setDefault('counter', 0);
Template.hello.helpers({
counter: function () {
return Session.get('counter');
}
});
Template.hello.events({
'click button': function () {
// increment the counter when button is clicked
Session.set('counter', Session.get('counter') + 1);
}
});
}
if(Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
var Test = new Mongo.Collection('test');
var query = Test.find({});
var init = true;
query.observeChanges({
added: function(id, fields) {
if(!init)
console.log('doc inserted');
},
changed: function(id, fields) {
console.log('doc updated');
},
removed: function() {
console.log('doc removed');
}
});
init = false;
});
}
Define collection for both - server and client:
//collection Test for client and server
var Test = new Mongo.Collection('test');
if (Meteor.isClient) {
//subscribe for collection test
Meteor.subscribe('test');
Template.hello.helpers({
test: function() {
var query = Test.find();
query.observeChanges({
added: function(id, fields) {
console.log('doc inserted');
},
changed: function(id, fields) {
console.log('doc updated');
},
removed: function() {
console.log('doc removed');
}
});
return query;
}
});
}
if (Meteor.isServer) {
Meteor.publish('test', function() {
return Test.find();
});
}
For more complex app you should structure your app into multiple directories and files. Read about it in Meteor docs.

Meteor.js collection aggregate returning undefined is not a function

I am trying to make collection aggregate in my Meteor.js app as shown below, yet each time I call my server logSummary method I get the following error. Can someone please tell me what I am doing wrong / how to resolve this error? Thanks.
Note: I am using Meteor-aggregate package
TypeError: undefined is not a function
at Object.Template.detailedreport.helpers.myCollection (http://localhost:3000/client/views/report.js?
Code:
Template.detailedreport.rendered = function() {
Session.set("dreport_customer", "");
Session.set("dreport_project", "");
Session.set("dreport_startDate", new Date());
Session.set("dreport_endDate", new Date());
$('.set-start-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-end-date').datetimepicker({
pickTime: false,
defaultDate: new Date()
});
$('.set-start-date').on("dp.change",function (e) {
Session.set("dreport_startDate", $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
});
$('.set-end-date').on("dp.change",function (e) {
Session.set("dreport_endDate", $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
});
};
Template.detailedreport.helpers({
customerslist: function() {
return Customers.find({}, {sort:{name: -1}});
},
projectslist: function() {
return Projects.find({customerid: Session.get("dreport_customer")}, {sort:{title: -1}});
},
myCollection: function () {
var now = Session.get("dreport_startDate");
var then = Session.get("dreport_endDate");
var custID = Session.get("dreport_customer");
var projID = Session.get("dreport_project");
Meteor.call('logSummary', now, then, projID, custID, function(error, data){
if(error)
return alert(error.reason);
return data;
});
},
settings: function () {
return {
rowsPerPage: 10,
showFilter: true,
showColumnToggles: false,
fields: [
{ key: '0._id.day', label: 'Day' },
{ key: '0.totalhours', label: 'Hours Spent'}
]
};
}
});
Template.detailedreport.events({
'submit form': function(e) {
e.preventDefault();
Session.set('dreport_endDate', $('.set-end-date').data('DateTimePicker').getDate().toLocaleString());
Session.set('dreport_startDate', $('.set-start-date').data('DateTimePicker').getDate().toLocaleString());
Session.set('dreport_project', $(e.target).find('[name=project]').val());
Session.set('dreport_customer', $(e.target).find('[name=customer]').val());
},
'change #customer': function(e){
Session.set("dreport_project", "");
Session.set("dreport_customer", e.currentTarget.value);
},
'change #project': function(e){
Session.set("dreport_project", e.currentTarget.value);
}
});
Template:
<div>
{{> reactiveTable class="table table-bordered table-hover" collection=myCollection settings=settings}}
</div>
Server:
Meteor.methods({
logSummary: function(startDate, endDate, projid, custid){
var pipeline = [
{ $match: { date: { $gte: new Date(startDate), $lte: new Date(endDate) },
projectid: projid,
customerid: custid
}
},
{ $group: {
_id: {
"projectid": "$projectid",
"day": { "$dayOfMonth": "$date" },
"month": { "$month": "$date" },
"year": { "$year": "$date" }
},
totalhours: {"$sum": "$hours"}
}}
];
return ProjectLog.aggregate(pipeline);;
}
});
Looking at the ReactiveTable documentation, it looks like you need to do something like:
Template.myTemplate.helpers({
myCollection: function () {
return myCollection;
}
});
Where myCollection is the name of a Mongo/Meteor collection (e.g. BlogPosts) which you defined with e.g. BlogPosts = new Mongo.Collection('blogPosts');
The reason you're getting undefined is not a function is that you are calling a Meteor method inside a template helper. The call is asynchronous so the return value is undefined. Now you are passing undefined to ReactiveTable. ReactiveTable will be trying to call something like myCollection.find() which is essentially undefined.find() and will therefore throw the error you're seeing.
Later on the Meteor call will finish and the data value will be lost because the function has already returned.
You can call Meteor.call inside the onCreated function like so:
Template.myTemplate.onCreated(function () {
Meteor.call('myFunction', 'my', 'params', function (err, result) {
if (err) {
// do something about the error
} else {
Session.set('myData',result);
}
});
});
Template.myTemplate.helpers({
myData: function () {
Session.get('myData')
}
});
This won't fix the issue with ReactiveTable, however.
If the collection you're trying to display is only used for this single page, you could put the aggregation inside the publish function so that minimongo contains only the documents that match the aggregation and therefore the correct documents will appear in your ReactiveTable.

Resources