2013-07-19 22 views
9

Sto tentando di implementare un sistema di voto simile a StackOverflow o reddit in cui un utente può votare solo una volta su un determinato post.Voto superiore e inferiore con Backbone, Express e Mongoose

Dopo aver seguito il consiglio dato qui

storing upvotes/downvotes in mongodb

ho creato due schemi per memorizzare i upvotes ei downvotes. Per ogni utente tengo traccia dei post che l'utente ha votato.

Messaggio Schema: Schema

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

utente:

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

A seconda del l'utente corrente ogni post sta per avere una visione diversa, se l'utente ha votato il post prima della il pulsante upvote o downvote sarà arancione (simile allo stackoverflow), quindi ho il seguente modello (semplificato) backbone per un post:

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); 
      } 

     }); 

    }, 


}); 

Quindi il votetype inizia con uno "0" se l'utente non ha votato sul post prima e il suo "1" o "-1" dipende dal voto. Nella funzione upvote, come ho aggiornare e salvare la votetype di quel post, ho anche inviare una richiesta Ajax di aggiungere che post per voto messaggi di matrice dell'utente nel controller post come il seguente:

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(); 
}; 

potrei avere alcune cattive decisioni di progettazione ma finora sembra che questo funzioni bene. Per favore, dimmi se posso apportare alcuni miglioramenti sul mio codice o su qualsiasi altra cosa sul mio progetto.

Ora arriva la parte difficile. In qualche modo devo guardare attraverso entrambi questi schemi e cambiare il "votetype" di ogni post prima di fare un collection.fetch() .. mi si avvicinò con una soluzione brutto come questo:

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

(i inseriscilo in un gitano quindi forse è più leggibile, mi dispiace per il brutto codice ..)

e una volta che aggiorno il tipo di voto di ogni post a seconda dell'utente lo passo alla mia vista backbone, e nel mio modello I fare qualcosa di molto semplice come:

<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> 

Questa soluzione funziona, ma non penso che sia davvero efficiente cercare tutti i post e tutti i post che l'utente ha votato ogni volta che un utente apre la pagina per visualizzare la visualizzazione personalizzata dei post. Qualcuno può pensare a un modo migliore per farlo? Sono aperto a qualsiasi consiglio o critica circa il mio codice .. grazie in anticipo

risposta

7

Ci sono molte cose che possono essere migliorate:

In primo luogo, è il codice lato client è un frutto a basso appeso per un attaccante - si fa un'operazione atomica (upvote/downvote) con due richieste, e la prima richiesta non solo invia tipo voto, ma invia anche il numero totale dei voti:

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}); 

ma, come l'applicazione risponderà se l'attaccante invierà 100 o anche 1000 voti? Questa operazione deve essere atomica e si dovrebbero aumentare i voti sul server quando si effettua la richiesta POST sull'endpoint /upvote.

In secondo luogo, non è necessario archiviare votetype sul post stesso: ogni volta che l'utente vota, si modifica il votetype che è visibile per tutti gli utenti, ma in seguito lo si nasconde con il ciclo ed è strano memorizzare un votetype dell'ultimo voto sul post in cui è chiaramente necessario avere un tipo di votazione di un particolare utente, quindi, non è necessario nello schema e può essere remoto. Puoi rimuovere il votetype dai post ed è possibile rimuovere il loop memorizzando la storia dei voti sul post stesso, quindi ogni volta che vuoi visualizzare un post o un elenco di post, puoi facilmente filtrare l'array per contenere solo il voto del determinato utente, in modo da schema sarà simile a questa:

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

e allora si può ottenere un post e filtro voti con qualcosa di simile:

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; 
} 

Se avete bisogno di visualizzare più votato messaggi l'utente profilo è possibile filtrare i post votati dall'utente:

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) 
    } 
) 

Il codice di modello sul lato client deve rimanere quasi lo stesso.

+0

grazie mille per la risposta, è stato davvero utile. Sto solo cercando di imparare .. puoi chiarire di nuovo questa parte "Ma come risponderà la tua applicazione se l'aggressore invierà 100 o anche 1000 voti? Questa operazione dovrebbe essere atomica, e dovresti incrementare i voti sul server quando fai Richiesta POST all'endpoint/upvote. " –

+1

Sì, da quello che hai scritto, ho concluso che fai due richieste: una quando salvi 'PostModel': this.save(); e uno subito dopo quando si esegue 'POST/upvote'. Fai queste richieste per assegnare un voto dell'utente e, come in qualsiasi altro luogo, devi assicurarti che gli utenti non possano sfruttarlo. Cosa succede se qualche utente invierà solo la prima richiesta - quella che incrementa il voto, senza inviare il secondo - fondamentalmente rendendo i propri voti anonimi. Il modo corretto sarebbe solo assegnando nuovi valori nel modello senza chiamare 'this.save' e scrivendo nel databe * il * codice lato server su'/upvote' –

+0

Come aggiungere o modificare I vote? Dalla mia attuale comprensione di MongoDB dovrei prima vedere se c'è un voto per l'utente e poi aggiungere o modificare il voto. Ma cosa succede se un altro voto per lui viene aggiunto dopo aver controllato se c'è un voto? –

Problemi correlati