Meteor: How can I restrict user on a chat? - meteor

Im working on a project and my last task is to implement publish and subscribe to prevent users seeing conversations they were not involved in. We were given an example code and I notice a filter on the router.
Router.route('/chat/:_id', function () {
// the user they want to chat to has id equal to
// the id sent in after /chat/...
var otherUserId = this.params._id;
// find a chat that has two users that match current user id
// and the requested user id
var filter = {$or:[
{user1Id:Meteor.userId(), user2Id:otherUserId},
{user2Id:Meteor.userId(), user1Id:otherUserId}
]};
var chat = Chats.findOne(filter);
if (!chat){// no chat matching the filter - need to insert a new one
chatId = Chats.insert({user1Id:Meteor.userId(), user2Id:otherUserId});
}
else {// there is a chat going already - use that.
chatId = chat._id;
}
if (chatId){// looking good, save the id to the session
Session.set("chatId",chatId);
}
this.render("navbar", {to:"header"});
this.render("chat_page", {to:"main"});
});
I thought the filter should be on publish and then subscribe. Here is what I have now.
My HTML
<template name="chat_page">
<h2>Type in the box below to send a message!</h2>
<div class="row">
<div class="col-md-12">
<div class="well well-lg">
{{#each recentMessages}}
{{> message}}
{{/each}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
{{#if currentUser}}
<form class="new-message">
<input class="input" type="text" name="text" placeholder="type a message here...">
<button class="btn btn-default">Send</button>
</form>
{{/if}}
</div>
</div>
</template>
<!-- simple template that displays a message -->
<template name="message">
<div class = "container">
<div class = "row">
<div class = "username">
<img src="/{{avatar}}" class="avatar_img" >
{{username}}
said: {{messageText}}
</div>
</div>
</div>
</template>
Server Side
Meteor.startup(function () {
if (!Meteor.users.findOne()){
for (var i=1;i<9;i++){
var email = "user"+i+"#test.com";
var username = "user"+i;
var avatar = "ava"+i+".png"
console.log("creating a user with password 'test123' and username/ email: "
+ email);
Meteor.users.insert({
profile:{username:username, avatar:avatar},
emails: [{address:email}],
services:{
password:{"bcrypt" : "PASSWORD REMOVED"}}});
}
}
});
Meteor.publish("messages", function (userId) {
return Messages.find();
});
Meteor.publish("userStatus", function() {
return Meteor.users.find({ "status.online": true });
});
Meteor.publish("userData", function(){
if(this.userId) {
return Meteor.users.find({_id: this.userId},{
fields: {'other':1, 'things': 1}});
} else {
this.ready();
}
return Meteor.users.find({ "status.online": true })
});
Client Side
Meteor.subscribe("messages");
Meteor.subscribe("userStatus");
Meteor.subscribe("userData");
// set up the main template the the router will use to build pages
Router.configure({
layoutTemplate: 'ApplicationLayout'
});
// specify the top level route, the page users see when they arrive at the site
Router.route('/', function () {
console.log("rendering root /");
this.render("navbar", {to:"header"});
this.render("lobby_page", {to:"main"});
});
Router.route('/chat/:_id', function () {
this.render("navbar", {to:"header"});
this.render("chat_page", {to:"main"});
});
///
// helper functions
///
Template.message.helpers({
userName: function() {
var userId = this.userId;
var user = Meteor.users.findOne(userId);
var username = user && user.profile && user.profile.username;
var avatar = user && user.profile && user.profile.avatar;
return {
username: username,
avatar: avatar
}
}
})
Template.available_user_list.helpers({
users:function(){
return Meteor.users.find();
}
})
Template.available_user.helpers({
getUsername:function(userId){
user = Meteor.users.findOne({_id:userId});
return user.profile.username;
},
isMyUser:function(userId){
if (userId == Meteor.userId()){
return true;
}
else {
return false;
}
}
})
Template.chat_page.helpers({
recentMessages: function () {
if (Session.get("hideCompleted")) {
return Messages.find({checked: {$ne: true}}, {sort: {createdAt: -1}});
} else {
return Messages.find({}, {sort: {createdAt: 1}});
}
},
hideCompleted: function () {
return Session.get("hideCompleted");
},
incompleteCount: function () {
return Tasks.find({checked: {$ne: true}}).count();
}
});
Template.chat_page.events({
// this event fires when the user sends a message on the chat page
'submit .new-message':function(event){
console.log(event);
event.preventDefault();
var text = event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
// see if we can find a chat object in the database
// to which we'll add the message
Meteor.call("sendMessage", text);
},
});
Collections
Messages = new Mongo.Collection("messages");
Shared-Methods
Meteor.methods({
sendMessage: function (messageText) {
if (! Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Messages.insert({
messageText: messageText,
createdAt: new Date(),
username: Meteor.user().profile.username,
avatar: Meteor.user().profile.avatar,
});
}
});

Related

UniqueId for autoform hook

This is probably are a really simple answer, however, I'm stumped. I have a lot of forms on my site. I would like to notify the user that an update form has been saved.
In this instance I'm creating a uniqueId, it's a Q&A form so there can be many question forms on one page. I'm not sure how to use the uniqueId with AutoForm.addHooks(); everything I've tried to date creates global autoform hooks which is not ideal because it affects all the other forms on the site.
Template.answers.helpers({
makeUniqueID: function () {
return "update-each-" + this._id;
},
});
var hooksObject = {
onSuccess: function(formType, result) {
var collectionId = this.docId;
Meteor.call('sendEmail',
collectionId
);
},
};
var answerForm = "update-each-" + this._id;
AutoForm.addHooks('answerForm', hooksObject);
UPDATE
Path: answer.js
var hooksObject = {
onSuccess: function(formType, result) {
var collectionId = this.docId;
Meteor.call('sendEmail',
collectionId
);
},
};
Template.answers.onCreated(function() {
var self = this;
self.autorun(function() {
var id = FlowRouter.getParam('id');
self.subscribe('answers', id);
});
this.formId = "update-each-" + this._id;
console.log("oncreated: ", this.formId);
AutoForm.addHooks(this.formId, hooksObject, true);
});
Template.answers.helpers({
answer: function() {
return Answers.find({"userId": Meteor.userId()});
},
formId() {
/**
* expose the form id we set above to the template
**/
var test = Template.instance().formId;
console.log("test: ", test);
return Template.instance().formId;
},
});
Path: answer.html
{{#each answer}}
{{#autoForm id=formId type="update" collection="Answers" doc=this}}
{{> afQuickField name='answer'}}
<button type="submit">Save changes</button>
{{/autoForm}}
{{/each}}
Updated based on new information from updated post and comment feedback.
The key to this solution is calling AutoForm.addHooks inside your Template's onCreated event. Otherwise, you won't have access to the unique id:
answers.html
<template name="answers">
{{#each answer}}
{{> answerForm this }}
{{/each}}
</template>
answers.js
Template.answers.onCreated(function() {
this.autorun(() => {
var id = FlowRouter.getParam('id');
this.subscribe('answers', id);
});
});
Template.answers.helpers({
answer: function() {
return Answers.find({"userId": Meteor.userId()});
}
});
answer.html
<template name="answerForm">
{{#autoForm id=formId type="update" collection="Answers" doc=this}}
{{> afQuickField name='answer'}}
<button type="submit">Save changes</button>
{{/autoForm}}
</template>
answer.js
Template.answerForm.onCreated(function onCreated() {
/**
* The data._id field is passed in to your template as data
**/
this.formId = "update-each-" + this.data._id;
/**
* Call addHooks with the third option, true, to replace any existing hooks for this form.
* This is because addHooks will run every time an instance of this template is created.
**/
AutoForm.addHooks(this.formId, hooksObject, true);
});
Template.answerForm.helpers({
formId() {
/**
* expose the form id we set above to the template
**/
return Template.instance().formId;
}
});

SimpleSchema.clean message in console

I'm getting this console message when displaying a group of update forms together. As far as I can tell I've followed the Autoform example correctly. Can anyone tell me what I'm doing wrong?
SimpleSchema.clean: filtered out value that would have affected key "_id", which is not allowed by the schema
Path: form.html
{{#each student}}
{{#autoForm id=makeUniqueID type="update" collection="StudentHistory" doc=this}}
<div class="panel panel-default edit-profile-margin-pannel">
<div class="panel-body">
{{> afQuickField name='class'}}
</div>
</div>
{{/autoForm}}
{{/each}}
Path: form.js
Template.form.helpers({
student: function() {
return StudentHistory.find({});
},
makeUniqueID: function () {
return "update-each-" + this._id;
}
});
Path: Schema.js
StudentHistory = new Mongo.Collection("studentHistory");
StudentHistory.allow({
insert: function(userId, doc) {
return !!userId;
},
update: function(userId, doc) {
return !!userId;
},
remove: function(userId, doc) {
return !!userId;
}
});
var Schemas = {};
Schemas.StudentHistory = new SimpleSchema({
studentUserId: {
type: String,
autoValue: function() {
return this.userId;
},
autoform: {
type: "hidden"
}
},
class: {
type: String,
optional: false
}
});
StudentHistory.attachSchema(Schemas.StudentHistory);
My error was in the template helper. The message disappears when I add the code below.
Template.form.helpers({
student: function() {
return StudentHistory.find({"studentUserId": Meteor.userId()});
}
});

sendEmail after form submitted

I've been trying to send an email after a autoform has been successfully submitted. I've tried using the template.events 'submit' which didn't work and I've tried to use metermethod="sendEmail". Nothing I do seems to work. Can someone please tell me what I'm doing wrong.
Path: form.html
{{#autoForm collection="JobOffers" id="jobOfferForm" type="insert" meteormethod="sendEmail"}}
<fieldset>
{{> afQuickField name='firstName'}}
<button type="submit" data-meteor-method="sendEmail" class="btn btn-primary">Submit</button>
</fieldset>
{{/autoForm}}
Path: server/email.js
sendEmail: function (from, subject, userId) {
check([from, subject, userId], [String]);
// Let other method calls from the same client start running,
// without waiting for the email sending to complete.
this.unblock();
SSR.compileTemplate( 'htmlEmail', Assets.getText( 'html-email.html' ) );
// to find the users info for the logged in users
// var user = Meteor.user();
var user = Meteor.users.findOne({ _id: userId });
var email = (user && user.emails[0].address);
var emailData = {
// name: (candidate && candidate.profile && candidate.profile.firstName),
name: (user && user.profile && user.profile.firstName),
// favoriteRestaurant: "Honker Burger",
// bestFriend: "Skeeter Valentine"
};
Email.send({
to: email,
from: from,
subject: subject,
html: SSR.render( 'htmlEmail', emailData )
});
console.log('sendEmail sent');
}
});
UPDATE
Path: form.js
AutoForm.hooks({
jobOfferForm: hooksObject
});
var hooksObject = {
after: {
insert: function(error, result){
Email.send({
var otheruserId = FlowRouter.getParam('id');
Meteor.call('sendEmail',
'test#email.com',
'Hello from Meteor!',
otheruserId);
};
}
}
};
You can use callbacks/hooks of autoform. If you want to send email after an insert following would be a solution:
var hooksObject ={
after: {
insert: function(error, result){
//Send email here
}
}
}
UPDATE:
var hooksObject = {
after: {
insert: function(error, result){
var otheruserId = FlowRouter.getParam('id');
Meteor.call('sendEmail',
'test#email.com',
'Hello from Meteor!',
otheruserId);
}
}
};
AutoForm.addHooks('jobOfferForm', hooksObject);
Please refer to autoform documentation for more info.

Tracker afterFlush error : Cannot read value of a property from data context in template rendered callback

I'm making a simple Meteor app that can redirect to a page when user click a link.
On 'redirect' template, I try get the value of property 'url' from the template instance. But I only get right value at the first time I click the link. When I press F5 to refresh 'redirect' page, I keep getting this error message:
Exception from Tracker afterFlush function: Cannot read property 'url' of null
TypeError: Cannot read property 'url' of null
at Template.redirect.rendered (http://localhost:3000/client/redirect.js?abbe5acdbab2c487f7aa42f0d68cf612f472683b:2:17)
at null.
This is where debug.js points to: (line 2)
if (allArgumentsOfTypeString)
console.log.apply(console, [Array.prototype.join.call(arguments, " ")]);
else
console.log.apply(console, arguments);
} else if (typeof Function.prototype.bind === "function") {
// IE9
var log = Function.prototype.bind.call(console.log, console);
log.apply(console, arguments);
} else {
// IE8
Function.prototype.call.call(console.log, console, Array.prototype.slice.call(arguments));
}
Can you tell me why I can't read the value of 'url' property from template data context in template rendered callback?
This is my code (for more details, you can visit my repo):
HTML:
<template name="layout">
{{>yield}}
</template>
<template name="home">
<div id="input">
<input type="text" id="url">
<input type="text" id="description">
<button id="add">Add</button>
</div>
<div id="output">
{{#each urls}}
{{>urlItem}}
{{/each}}
</div>
</template>
<template name="redirect">
<h3>Redirecting to new...{{url}}</h3>
</template>
<template name="urlItem">
<p><a href="{{pathFor 'redirect'}}">
<strong>{{url}}: </strong>
</a>{{des}}</p>
</template>
home.js
Template.home.helpers({
urls: function(){
return UrlCollection.find();
}
});
Template.home.events({
'click #add': function() {
var urlItem = {
url: $('#url').val(),
des: $('#description').val()
};
Meteor.call('urlInsert', urlItem);
}
});
redirect.js
Template.redirect.rendered = function() {
if ( this.data.url ) {
console.log('New location: '+ this.data.url);
} else {
console.log('No where');
}
}
Template.redirect.helpers({
url: function() {
return this.url;
}
});
router.js
Router.configure({
layoutTemplate: 'layout'
})
Router.route('/', {
name: 'home',
waitOn: function() {
Meteor.subscribe('getUrl');
}
});
Router.route('/redirect/:_id', {
name: 'redirect',
waitOn: function() {
Meteor.subscribe('getUrl', this.params._id);
},
data: function() {
return UrlCollection.findOne({_id: this.params._id});
}
});
publication.js
Meteor.publish('getUrl', function(_id) {
if ( _id ) {
return UrlCollection.find({_id: _id});
} else {
return UrlCollection.find();
}
});
Add this
Router.route('/redirect/:_id', {
name: 'redirect',
waitOn: function() {
Meteor.subscribe('getUrl', this.params._id);
},
data: function() {
if(this.ready()){
return UrlCollection.findOne({_id: this.params._id});
}else{
console.log("Not yet");
}
}
});
Tell me if works.
With help from my colleague, I can solve the problem.
My problem comes from wrong Meteor.subscribe syntax. In my code, I forget "return" in waitOn function. This will make Meteor do not know when the data is fully loaded.
Here is the right syntax:
waitOn: function() {
return Meteor.subscribe('getUrl', this.params._id);
}

Iterating through an array in a Meteor.js collection

Background: writing a web application using Meteor.js where users can add board games to their profile. There is a Users collection and a Games collection.
I have the list of Games and each game has a button next to it so the user can add the game to their profile. The button will find the _id of each game and add it to a user.games array. The _id is a really long hash.
On the user's dashboard, I want to be able to display each game the user has "subscribed" to but I'm not sure how to access the user.games field.
Here is my code:
/server/publications.js
Meteor.publish('userGames', function () {
return Meteor.users.find({_id: this.userId},
{fields: {'games': 1}});
});
/client/main.js
Meteor.subscribe('userGames');
/client/views/dashboard/dashboard.html
<template name="dashboard">
<div class="dashboard">
{{#if hasGames}}
<ul class="games-list">
{{#each userGames}}
{{> game}}
{{/each}}
</ul>
{{else}}
<h3>You have not added any games to your chest</h3>
{{/if}}
</div>
</template>
/client/views/dashboard/dashboard.js
Template.dashboard.helpers({
hasGames: function(){
},
userGames: function(){
}
});
As you can see I'm not sure what goes into the dashboard.js helper function in order to be able to access the user.games field.
EDIT:
So I ran a test to see if the below answer works - I've updated the following:
dashboard.html
<template name="dashboard">
<div class="dashboard">
{{test}}
</div>
</template>
dashboard.js
Template.dashboard.helpers({
test: function(){
var user = Meteor.user();
return Games.find({_id: { $in: user.games }});
}
});
The console says "Uncaught TypeError: Cannot read property 'games' of undefined"
Finding all games for currently logged user
Games = new Meteor.Collection("userGames");
if (Meteor.isClient) {
Meteor.subscribe('userGames');
Template.hello.greeting = function () {
var user = Meteor.user();
if(user && Array.isArray(user.games)){
return Games.find({_id: { $in: user.games }}).fetch();
}else{
return [];
}
};
}
if (Meteor.isServer) {
Meteor.publish('userGames', function () {
return Meteor.users.find({_id: this.userId},
{fields: {'games': 1}});
});
}

Resources