2013-07-19 21 views
9

Próbuję wprowadzić system głosowania podobny do stackoverflow lub reddit, gdzie użytkownik może głosować tylko raz na dany wpis.Rozgłaszanie i notowanie z kręgosłupem, ekspresowym i mangowym

Po wykonaniu zaleceń podanych tutaj

storing upvotes/downvotes in mongodb

Stworzyłem dwa schematy do przechowywania upvotes i downvotes. Dla każdego użytkownika śledzę posty, na które głosował użytkownik.

post schematu: Schemat

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

użytkownika:

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

zależności od aktualnego użytkownika każdy post będzie mieć inny pogląd, jeśli użytkownik głosował na stanowisku przed przycisk upvote lub przycisk downvote będzie pomarańczowy (podobny do stackoverflow), więc mam następujący (uproszczony) model szkieletu dla postu:

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

     }); 

    }, 


}); 

Tak więc typ głosowy zaczyna się od "0", jeśli użytkownik nie głosował wcześniej na ten wpis, a jego "1" lub "-1" w zależności od głosowania. W funkcji upvote, jak aktualizować i zapisać votetype tego posta, ja również wysłać ajax żądania dodać, że stanowisko do głosowania posty tablicy użytkownika w sterowniku postu tak:

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

mogę mieć niektóre złe decyzje projektowe, ale jak na razie wygląda na to, że to działa dobrze. Proszę, proszę powiedz mi, czy mogę ulepszyć mój kod lub cokolwiek innego na moim projekcie.

Teraz jest trudna część. Jakoś muszę patrzeć przez oba te schematy i zmienić „votetype” każdego postu przed wykonaniem collection.fetch() .. I wymyślił rozwiązanie brzydki jak ten:

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

(í umieścić go w gitarze, więc być może jest on bardziej czytelny, przepraszam za brzydki kod ...)

i raz zaktualizuję typ głosu każdego posta w zależności od użytkownika, przekazuję go do mojego widoku kręgosłupa, aw moim szablonie Zrób coś bardzo prostego jak:

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

To rozwiązanie działa, ale nie sądzę, żeby naprawdę skutecznie sprawdzał wszystkie posty i wszystkie posty, na które użytkownik głosował za każdym razem, gdy użytkownik otwiera stronę, aby wyświetlić niestandardowy widok wpisów. Czy każdy może pomyśleć o lepszy sposób na zrobienie tego? Jestem otwarty na wszelkie rady i krytyki na temat mojego kodu .. z góry dzięki

Odpowiedz

7

Są tam wiele rzeczy, które można poprawić:

pierwszy, kod po stronie klienta jest nisko wiszące owoce dla atakującego - zrobić operację atomową (upvote/downvote) z dwóch wniosków, a pierwsze żądanie nie tylko wysyła rodzaj głosu, ale także wysyła całkowitej liczbie głosów:

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

ale, jak aplikacja będzie reagować, jeśli atakujący wyśle 100, a nawet 1000 głosów? Ta operacja powinna być atomowa i powinieneś zwiększyć liczbę głosów na serwerze, gdy wysyłasz żądanie POST do punktu końcowego /upvote.

Po drugie, tak naprawdę nie musisz zapisywać typu na samym poście - za każdym razem, gdy użytkownik głosuje, zmieniasz typ, który jest widoczny dla wszystkich użytkowników, ale później ukrywasz go za pomocą pętli i po prostu dziwnie jest przechowywać typ głosu ostatniego głosowania na stanowisku, w którym wyraźnie potrzebujesz mieć typ głosu konkretnego użytkownika, dlatego nie potrzebujesz go w schemacie i możesz go zdalnie. Możesz usunąć typ z postów i usunąć pętlę, przechowując historię głosów na samym wpisie, więc gdy chcesz wyświetlić post lub listę postów, możesz łatwo filtrować tablicę, aby zawierała tylko głos dany użytkownik, więc schemat będzie wyglądał następująco:

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

a potem można dostać poście i filtrów głosów z czymś takim:

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

Jeśli chcesz wyświetlić najnowszy głosowali postów użytkownika profil można filtrować posty głosowane przez użytkownika:

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

Kod szablonu po stronie klienta powinien pozostać prawie taki sam.

+0

wielkie dzięki za odpowiedź, to było naprawdę pomocne. Próbuję się tylko nauczyć ... czy możesz wyjaśnić tę część jeszcze raz "Ale, jak zareaguje twoja aplikacja, jeśli atakujący wyśle ​​100, a nawet 1000 głosów? Ta operacja powinna być atomowa i powinieneś zwiększać głosy na serwerze, kiedy robisz Żądanie POST do punktu końcowego/upvote. " –

+1

Tak, z tego, co napisałeś, doszedłem do wniosku, że robisz dwie prośby: jedną, gdy zapisujesz 'PostModel': this.save(); i jeden zaraz po tym, gdy tworzysz 'POST/upvote'. Zgłaszasz prośbę o przypisanie głosu użytkownika i, podobnie jak w innych miejscach, musisz upewnić się, że użytkownicy nie mogą go wykorzystać. Co się stanie, jeśli jakiś użytkownik wyśle ​​tylko pierwszą prośbę - tę, która zwiększa głosowanie, ale nie wysyłając drugiej - w zasadzie robi to anonimowo. Właściwym sposobem byłoby po prostu przypisywanie nowych wartości w modelu bez wywoływania 'this.save' i zapisywanie do databe z * twojego * kodu po stronie serwera'/upvote' –

+0

Jak dodać lub zmienić głosowanie? Z mojego obecnego rozumienia MongoDB musiałbym najpierw sprawdzić, czy jest głosowanie na użytkownika, a następnie dodać lub zmienić głos. Ale co się stanie, jeśli zostanie mu przyznany kolejny głos po sprawdzeniu, czy jest głosowanie? –