使用Backbone,Express和Mongoose进行Upvote和Downvote

时间:2013-07-19 21:50:59

标签: node.js mongodb backbone.js mongoose database-schema

我正在尝试实现类似于stackoverflow或reddit的投票系统,其中只允许用户在给定帖子上投票一次。

按照这里给出的建议后

storing upvotes/downvotes in mongodb

我创建了两个模式来存储upvotes和downvotes。对于每个用户,我都会跟踪用户投票的帖子。

发布架构:

var postSchema = new Schema({
    name: String,
    votes: Number,
    votetype: Number,
    postedBy: { type: String, ref: 'User' },
});

用户架构:

var userSchema = new Schema({
    twittername: String,
    twitterID: Number,
    votedPosts : [{ _id : mongoose.Schema.Types.ObjectId , votetype: Number }]
});

根据当前用户的不同,每个帖子都会有不同的视图,如果用户在upvote按钮或者downvote按钮之前投票在该帖子上将是橙色(类似于stackoverflow),那么我有以下内容(简化)帖子的骨干模型:

var PostModel = Backbone.Model.extend({
    urlRoot : '/tweet',
    idAttribute: '_id',
    defaults:{
        name: '',
        votes: 0,
        votetype: 0,
        postedBy : '',
    },

    upvote: function(){
        this.set ({votetype : 1 }, {votes : this.get('votes') + 1});
        this.save();
        $.ajax({
            type: "POST",
            url:"/upvote",
            data : {postID : this.id , userID : window.userID , vote: 1},
            success : function(result){
                console.log(result);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log(textStatus, errorThrown);
            }

        });

    },


}); 

因此,如果用户之前未对该帖子进行投票,则其姓名为“0”,并且根据投票结果为“1”或“-1”。在upvote函数中,当我更新并保存该帖子的votetype时,我还发送一个ajax请求,将该帖子添加到帖子控制器中的用户投票帖子数组中,如下所示:

exports.upvote = function(req,res){
  var postID = req.body.postID;
  var newvotetype = req.body.vote;

  User.findOne({twitterID : req.body.userID}, {votedPosts : { $elemMatch: { "_id":  postID  }}},
         function(err, post) { 
              if (post.votedPosts.length == 0) { 
                //append to the array
                User.update({twitterID : req.body.userID} , { $push : {votedPosts : {_id : postID , votetype: newvotetype}}} ,function (err, user, raw) {
                    if (err){console.log(err);}
                });

                console.log(post);
                console.log("no one has voted on this before");

              } 
              else { 
                //update in the existing array
                User.update({twitterID : req.body.userID, 'votedPosts._id':  postID  }, { $set : {'votedPosts.$.votetype' : newvotetype}} ,function (err, user, raw) {
                    if (err){console.log(err);}
                });
              }
          }
  );
  res.send("success");
  res.end();
}; 

我可能会有一些糟糕的设计决定,但到目前为止似乎这样做很好。如果我可以对我的代码或我的设计中的任何其他内容进行一些改进,请告诉我。

现在是棘手的部分。不知何故,我必须查看这两个模式并在进行collection.fetch()之前更改每个帖子的“votetype”。我想出了一个像这样的丑陋解决方案:

https://gist.github.com/gorkemyurt/6042558

(我把它放在一个gits所以也许它更具可读性,对于丑陋的代码感到抱歉..)

一旦我根据用户更新每个帖子的投票类型,我将其传递给我的骨干视图,在我的模板中,我做了一些非常基本的事情,如:

<div class="post-container">
      <div id="arrow-container">
            <% if (votetype == 1 ) { %>
                  <p><img id="arrowup" src="/images/arrow-up-orange.jpg"></p>
                  <p><img id="arrowdown" src="/images/arrow-down.jpg"></p>
            <% } %>
            <% if ( votetype  == 0 ) { %>
                  <p><img id="arrowup" src="/images/arrow-up.jpg"></p>
                  <p><img id="arrowdown" src="/images/arrow-down.jpg"></p>
            <% } %>
            <% if ( votetype  == -1 ) { %>
                  <p><img id="arrowup" src="/images/arrow-up.jpg"></p>
                  <p><img id="arrowdown" src="/images/arrow-down-orange.jpg"></p>
            <% } %>
      </div>

      <div id="text-container">
            <p><h2><%- name %></h2></p>
            <p><%- dateCreated %></p>
            <p>Posted by: <%- postedBy %></p>
      </div>
</div>

这个解决方案有效,但我不认为查找所有帖子以及用户每次打开页面以呈现帖子的自定义视图时所投票的所有帖子都非常有效..任何人都可以认为更好的方法来做到这一点?我愿意接受有关我的代码的任何建议或批评..提前感谢

1 个答案:

答案 0 :(得分:7)

有很多事情可以改进:

首先,您的客户端代码对于攻击者来说是一个悬而未决的成果 - 您使用两个请求执行原子操作(upvote / downvote),并且第一个请求不仅发送投票类型而且还发送总投票数:

this.set ({votetype : 1 }, {votes : this.get('votes') + 1});
this.save();
// btw this looks broken, the second argument for `set` is options, so 
// if you want to set votes, you should move them to the first argument:
this.set ({votetype : 1, votes : this.get('votes') + 1});

但是,如果攻击者发送100票甚至1000票,您的应用程序将如何响应? 此操作应该是原子操作,当您向/upvote端点发出POST请求时,应该增加服务器上的投票。

其次,你真的不需要在帖子本身上存储选票类型 - 每当用户投票时,你改变所有用户都可以看到的投票类型,但是你稍后用循环隐藏它,并且存储一个类型的对帖子的最后一次投票,你明确需要特定用户的选票类型,因此,你不需要它在架构中并可以远程它。 你可以从帖子中删除votetype,你可以通过在帖子本身存储投票历史来删除循环,所以每当你想显示帖子或帖子列表时,你可以轻松过滤数组以包含给定的投票用户,因此您的架构将如下所示:

var postSchema = new Schema({
   name: String,
   votesCount: Number,
   votes: [{ user_id : mongoose.Schema.Types.ObjectId , type: Number }],
   postedBy: { type: String, ref: 'User' },
});

然后你可以获得一个帖子并过滤投票,例如:

Post.findOne({_id:<post-id>)}, function(err, post){
     post.vote = post.votes.filter(function(vote){
         return vote.user_id === req.body.userID;
     })[0].type;
     res.send(post)
  }
)
// or list of posts
Post.find(function(err, posts){
     posts.forEach(function(post){
         post.vote = post.votes.filter(function(vote){
             return vote.user_id === req.body.userID;
         })[0].type;
     });
     res.send(posts)
  }
)
// you can move vote finding logic in a function: 
function findVote(post) {
    var vote = post.votes.filter(function(vote){
        return vote.user_id === req.body.userID;
    })[0]
    if(vote) return vote.type;
}

如果您需要在用户个人资料中显示最新投票的帖子,您可以过滤用户投票的帖子:

Post.find({'votes.user_id': req.body.userID}, function(err, posts){
     posts.forEach(function(post){
         // using findVote defined above
         post.vote = findVote(post);
     });
     res.send(posts)
  }
)

客户端的模板代码应该保持不变。