Upvoting and downvoting posts - Meteor - meteor

Up and downvotes are functional yet I'd like to do a check like "If the user is a downvoter or an upvoter" and do the right thing which is explained below
upvote: function(postId) {
check(this.userId, String);
check(postId, String);
var affected = Posts.update({
_id: postId,
upvoters: {$ne: this.userId}
},{
$addToSet: {
upvoters: this.userId
},
$inc: {
upvotes: 1
}
});
if (! affected)
throw new Meteor.Error('invalid', "You already up-voted this post");
},
downvote: function(postId) {
check(this.userId, String);
check(postId, String);
var affected = Posts.update({
_id: postId,
downvoters: {$ne: this.userId},
}, {
$addToSet: {
downvoters: this.userId
},
$inc: {
downvotes: 1
}
});
if (! affected)
throw new Meteor.Error('invalid', "You already down-voted this post");
},
With my code above, users can upvote and downvote once, but they can do both...
I wrote the code for what happens if a user is a downvoter and clicks upvote but I couldn't figure out how to check if the user is a downvoter or an upvoter.
$pull: {
downvoters: this.userId
},
$addToSet: {
upvoters: this.userId
},
$inc: {
downvotes: -1
},
$inc: {
upvotes: 1
});
EDIT: Even though the accepted answer works fine, I found an issue with it. When you click fast, it might increment the vote count 2-3 times. Instead of incrementing vote count, I only insert userId and simply count how many IDs there are inside the upvoters/downvoters array which gives the same result & it never inserts the same userId twice.
Inside the helpers for the count:
return this.upvoters.length
Also, inArray is a useful tool for checking if the value you have is inside an array.
if($.inArray(Meteor.userId(), this.upvoters)) //gives true if the current user's ID is inside the array

You will have to fetch the post and see if it contains the user's id in its downvoters array:
var post = Posts.findOne(postId);
if (post.downvoters && _.contains(post.downvoters, this.userId)) {
Posts.update({
_id: postId
},
{
$pull: {
downvoters: this.userId
},
$addToSet: {
upvoters: this.userId
},
$inc: {
downvotes: -1,
upvotes: 1
}
}
});
}

Related

How to Publish joined Data from Array of IDs in Meteor

I just want to Publish the relational Data for a Publication to client, but the issue is my Relational Data field is array of ID's of a Different Collection, I tried Different Packages but all works with single Relational ID but not working with Array of relational ID's, let assume I have two Collection Companies and Meteor.users below is my Company Document Looks like
{
_id : "dYo4tqpZms9j8aG4C"
owner : "yjzakAgYWejmJcuHz"
name : "Labbaik Waters"
peoples : ["yjzakAgYWejmJcuHz", "yjzakAgYWejmJcuHz"],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
here you can see peoples field contains the user ID's as Array, so How I publish this userId's as user Documents, as for example I tried the most popular meteor package named publishComposit, when I tried Loop in Children's find, I got undefined in children i.e below
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
find(company) {
let cursors = company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
and if I implement async loop in children's find I got error like below code
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
async find(company) {
let cursors = await company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
the error occured in above code is Exception in callback of async function: TypeError: this.cursor._getCollectionName is not a function
I don't know what I am exactly doing wrong here, or implementing package function not as intended any help will be greatly appropriated
EDIT: my desired result should be full user documents instead of ID no matter it mapped in same peoples array or as another fields I just want as below
{
_id: "dYo4tqpZms9j8aG4C",
owner: "yjzakAgYWejmJcuHz",
name: "Labbaik Waters",
peoples: [
{
profile: {firstName: "Abdul", lastName: "Hameed"},
_id: "yjzakAgYWejmJcuHz"
}
],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
I ran into a similar problem couple of days ago. There are two problems with the provided code. First, using async; it's not needed and rather complicates things. Second, publishComposite relies on receiving one cursor not multiple within its children to work properly.
Below is a snippet of the code used to solve the problem I had, hopefully you can replicate it.
Meteor.publishComposite("table.conversations", function(table, ids, fields) {
if (!this.userId) {
return this.ready();
}
check(table, String);
check(ids, Array);
check(fields, Match.Optional(Object));
return {
find() {
return Conversation.find(
{
_id: {
$in: ids
}
},
{ fields }
);
},
children: [
{
find(conversation) {
// constructing one big cursor that entails all of the documents in one single go
// as publish composite cannot work with multiple cursors at once
return User.find(
{ _id: { $in: conversation.participants } },
{ fields: { profile: 1, roles: 1, emails: 1 } }
);
}
}
]
};
});

How to run a `$set` and `$pull` within a Meteor method?

I want to update a collection called SMUProfiles, through a method called classroom.delete. I want to pull out the classroom_id from 2 places inside SMUProfiles i.e. one inside classrooms.owner which has an array of codes, and the other inside array classrooms.students.
I have successfully one the $set part, and now trying to add the $pull, but $pull doesn't seem to work.
Can we do the $set and $pull in such way?
/* Method for deleting Classroom */
'classroom.delete'(classroom_id) {
if (!this.userId) {
throw new Meteor.Error('not-authorised');
}
Classrooms.remove(classroom_id)
let classids = Classrooms.find({ owner: this.userId }).fetch().map(function(classrooms){
return classrooms._id })
//console.log(classids);
SMUProfiles.update({
owner: this.userId,
}, {
$set: {
'classrooms.owner': classids
},
$pull: {
'classrooms.students': classroom_id
}
}
)
}
You're trying to $set and $pull on the same field in the same update - the two operations conflict; so no, you can't use these operators in this way.
You could easily split this into two:
SMUProfiles.update(
{ owner: this.userId },
{ $set: { 'classrooms.owner': classids },
);
SMUProfiles.update(
{ owner: this.userId },
{ $pull: { 'classrooms.students': classroom_id },
);
See e.g. this answer

How to access server aggregate on the client side within meteor

Bit of a noob question. I'm using meteor-native-mongo on the server to access the aggregate function in MongoDB, however, I'm not sure how I return and access the results on the client side. In the past subscribing and then accessing the collections on the client was pretty straightforward using the collection.find({}) function, however, I don't understand how to do it with the aggregate function. Can someone please explain.
Meteor.publish('companies', function(limit) {
db.collection('companies').aggregate([{ $group: { _id: { location: "$google_maps.geometry_location" }, companies: { $addToSet: { name: "$company_name" } }, count: { $sum: 1} } }, { $match: { count: { $gt: 1 } } }]).toArray((err, result) => {
console.log(result);
return result;
});
});
Use this.added, this.changed, this.removed from https://docs.meteor.com/api/pubsub.html#Subscription-added ...
Meteor.publish('companies', function(limit) {
var subscription = this;
db.collection('companies').aggregate([{ $group: { _id: { location: "$google_maps.geometry_location" }, companies: { $addToSet: { name: "$company_name" } }, count: { $sum: 1} } }, { $match: { count: { $gt: 1 } } }]).toArray((err, result) => {
subscription.added('companies-aggregate', 'geometry-grouping', {result: result});
});
});
// On the client:
var CompaniesAggregate = new Mongo.Collection('companies-aggregate');
// Inside a reactive context, like a helper
var result = CompaniesAggregate.findOne('geometry-grouping').result;
Naturally, to make it reactive, you'd have to know when the results of the aggregations would change. There is no automatic way to do that--you would have to resolve that logically, with your own code.
The best way to do that is to save the subscription variable in an array somewhere in a higher scope, and called changed on all the subscriptions for 'companies' for the geometry-grouping document, computing an updated result.
The commenter's solution won't be realtime; in other words, if one user makes a change to the e.g. company name or location, another user won't see those changes.

Publish users using id from a different collection

I'm trying to access the userIds stored in a collection and then use them to publish the details of all of the meteor.users. My publish function doesn't isn't return anything?
Meteor.publish('allUsersWithOffers', function () {
var user = Offers.find({}, {fields: {"UserId": 1}});
return Meteor.users.find({_id: user});
});
Give this a try:
Meteor.publish('allUsersWithOffers', function () {
var offers = Offers.find({}, { fields: { UserId: 1 } }).fetch();
var ids = _.pluck(offers, 'UserId');
// This is critical - you must limit the fields returned from
// the users collection! Update this as needed.
options = { fields: { username: 1, emails: 1 } };
return Meteor.users.find({ _id: { $in: ids } }, options);
});
find returns a cursor - you need to call fetch to actually get the documents.

Overlapping subscription does not publish all fields

When I have two subscriptions that limit the fields to publish it does not do a union between the two
This should publish only the avatar.
Meteor.publish('userStatisticsByYear', function(year) {
check(year, Number);
this.unblock();
var userStats = UserStatistics.find({year: year},{sort: {count: -1}, limit: 5});
var userIds = userStats.map(function(u) {
return u.userId;
});
return [
Meteor.users.find({_id: {$in: userIds}},{fields: {username: 1, "profile.avatar": 1}}),
ProfileImages.find({owner: {$in: userIds}}),
userStats
];
});
And this subscription publishes more details of the profile. Which is fine under some routes.
Meteor.publish('getUserProfile', function(userId) {
this.unblock();
if (!this.userId) {
this.ready();
return;
}
return [
Meteor.users.find({_id: userId}, {fields: {profile: 1}}),
ProfileImages.find({owner: userId})
]
});
The problem is that if I subscribe to both only "profile.avatar" is being published. But not the extra fields from "getUserProfile"
The output of the console:
Meteor.subscribe('userStatisticsByYear')
Object {subscriptionId: "aQFv4HkGDKx54gJLq"}
Meteor.users.findOne("KABEf7SzNCmQapXND").profile
Object {avatar: "NQokwm9bHgfrMtKLY"}
Meteor.subscribe('getUserProfile','KABEf7SzNCmQapXND')
Object {subscriptionId: "hemH2NF88vwd3AkHv"}
Meteor.users.findOne("KABEf7SzNCmQapXND").profile
Object {avatar: "NQokwm9bHgfrMtKLY"}
I have seen this before.
if you use Meteor.users.find({"_id":"KABEf7SzNCmQapXND"}).profile instead of findOne, I believe it should work.

Resources