From dda6647d207ea26800c5dbf3b326c88eccaa2c66 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Mon, 24 Aug 2020 15:16:34 +0200 Subject: reviews: Disallow voting on competing reviews + only use Elm when necessary i.e. no need to spawn Elm when the user can't vote anyway. --- elm/Reviews/Vote.elm | 9 ++------- lib/VNWeb/Reviews/Elm.pm | 1 - lib/VNWeb/Reviews/Lib.pm | 20 ++++++++++++++++++++ lib/VNWeb/Reviews/List.pm | 3 ++- lib/VNWeb/Reviews/Page.pm | 12 +++++------- lib/VNWeb/Reviews/VNTab.pm | 8 ++++---- sql/func.sql | 3 ++- 7 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 lib/VNWeb/Reviews/Lib.pm diff --git a/elm/Reviews/Vote.elm b/elm/Reviews/Vote.elm index cf63fe71..717539c1 100644 --- a/elm/Reviews/Vote.elm +++ b/elm/Reviews/Vote.elm @@ -23,7 +23,6 @@ type alias Model = { state : Api.State , id : String , my : Maybe Bool - , can : Bool , up : Int , down : Int } @@ -33,7 +32,6 @@ init d = { state = Api.Normal , id = d.id , my = d.my - , can = d.can , up = d.up , down = d.down } @@ -62,16 +60,13 @@ update msg model = view : Model -> Html Msg view model = - let but opt lbl = - if not model.can - then span [] [ text lbl ] - else a [ href "#", onClickD (Vote opt), classList [("votebut", True), ("myvote", model.my == Just opt)] ] [ text lbl ] + let but opt lbl = a [ href "#", onClickD (Vote opt), classList [("votebut", True), ("myvote", model.my == Just opt)] ] [ text lbl ] in span [] [ case model.state of Api.Loading -> span [ class "spinner" ] [] Api.Error e -> b [ class "standout" ] [ text (Api.showResponse e) ] - Api.Normal -> if model.can && model.my == Nothing then text "Was this review helpful? " else text "" + Api.Normal -> if model.my == Nothing then text "Was this review helpful? " else text "" , but True ("๐Ÿ‘ " ++ String.fromInt model.up) , text " " , but False ("๐Ÿ‘Ž " ++ String.fromInt model.down) diff --git a/lib/VNWeb/Reviews/Elm.pm b/lib/VNWeb/Reviews/Elm.pm index 527da239..428da4dc 100644 --- a/lib/VNWeb/Reviews/Elm.pm +++ b/lib/VNWeb/Reviews/Elm.pm @@ -5,7 +5,6 @@ use VNWeb::Prelude; my $VOTE = { id => { vndbid => 'w' }, my => { required => 0, jsonbool => 1 }, - can => { _when => 'out', anybool => 1 }, up => { _when => 'out', uint => 1 }, down => { _when => 'out', uint => 1 }, }; diff --git a/lib/VNWeb/Reviews/Lib.pm b/lib/VNWeb/Reviews/Lib.pm new file mode 100644 index 00000000..3564464a --- /dev/null +++ b/lib/VNWeb/Reviews/Lib.pm @@ -0,0 +1,20 @@ +package VNWeb::Reviews::Lib; + +use VNWeb::Prelude; +use Exporter 'import'; + +our @EXPORT = qw/review_vote_/; + + +# Display the up/down vote counts for a review, optionally with the option for the user to vote. +# Takes an object with the following fields: id, c_up, c_down, my, can +sub review_vote_ { + my($w) = @_; + my sub plain_ { + span_ sprintf '๐Ÿ‘ %d ๐Ÿ‘Ž %d', $w->{c_up}, $w->{c_down}; + }; + return plain_ if !auth || !$w->{can}; + elm_ 'Reviews.Vote' => $VNWeb::Reviews::Elm::VOTE_OUT, { id => $w->{id}, up => $w->{c_up}, down => $w->{c_down}, my => $w->{my} }, \&plain_; +} + +1; diff --git a/lib/VNWeb/Reviews/List.pm b/lib/VNWeb/Reviews/List.pm index fcd0ed03..04f932bd 100644 --- a/lib/VNWeb/Reviews/List.pm +++ b/lib/VNWeb/Reviews/List.pm @@ -1,6 +1,7 @@ package VNWeb::Reviews::List; use VNWeb::Prelude; +use VNWeb::Reviews::Lib; sub tablebox_ { @@ -25,7 +26,7 @@ sub tablebox_ { td_ class => 'tc2', sub { user_ $_ }; td_ class => 'tc3', sub { a_ href => "/$_->{id}", $_->{title} }; td_ class => 'tc4', fmtvote $_->{vote}; - td_ class => 'tc5', sprintf '๐Ÿ‘ %d ๐Ÿ‘Ž %d', $_->{c_up}, $_->{c_down}; + td_ class => 'tc5', sub { review_vote_ $_ }; td_ class => 'tc6', $_->{c_count}; td_ class => 'tc7', $_->{c_lastnum} ? sub { user_ $_, 'lu_'; diff --git a/lib/VNWeb/Reviews/Page.pm b/lib/VNWeb/Reviews/Page.pm index 5f2671ac..23c62cf1 100644 --- a/lib/VNWeb/Reviews/Page.pm +++ b/lib/VNWeb/Reviews/Page.pm @@ -2,6 +2,7 @@ package VNWeb::Reviews::Page; use VNWeb::Prelude; use VNWeb::Releases::Lib; +use VNWeb::Reviews::Lib; my $COMMENT = form_compile any => { @@ -64,11 +65,7 @@ sub review_ { } if length $w->{text}; tr_ sub { td_ ''; - td_ style => 'text-align: right', sub { - elm_ 'Reviews.Vote' => $VNWeb::Reviews::Elm::VOTE_OUT, { %$w, can => auth && $w->{user_id} != auth->uid }, sub { - span_ sprintf '๐Ÿ‘ %d ๐Ÿ‘Ž %d', $w->{c_up}, $w->{c_down}; - }; - } + td_ style => 'text-align: right', sub { review_vote_ $w }; }; } } @@ -78,7 +75,7 @@ TUWF::get qr{/$RE{wid}(?:(?[\./])$RE{num})?}, sub { return tuwf->resNotFound if !auth->permReview; #XXX:While in beta my($id, $sep, $num) = (tuwf->capture('id'), tuwf->capture('sep')||'', tuwf->capture('num')); my $w = tuwf->dbRowi( - 'SELECT r.id, r.vid, r.rid, r.summary, r.text, r.spoiler, COALESCE(c.count,0) AS count, r.c_up, r.c_down, uv.vote + 'SELECT r.id, r.vid, r.rid, r.summary, r.text, r.spoiler, COALESCE(c.count,0) AS count, r.c_up, r.c_down, uv.vote, r2.id IS NULL AS can , rel.title AS rtitle, rel.original AS roriginal, rel.type AS rtype, rv.vote AS my , ', sql_user(), ',', sql_totime('r.date'), 'AS date,', sql_totime('r.lastmod'), 'AS lastmod FROM reviews r @@ -87,6 +84,7 @@ TUWF::get qr{/$RE{wid}(?:(?[\./])$RE{num})?}, sub { LEFT JOIN ulist_vns uv ON uv.uid = r.uid AND uv.vid = r.vid LEFT JOIN (SELECT id, COUNT(*) FROM reviews_posts GROUP BY id) AS c(id,count) ON c.id = r.id LEFT JOIN reviews_votes rv ON rv.id = r.id AND rv.uid =', \auth->uid, ' + LEFT JOIN reviews r2 ON r2.vid = r.vid AND r2.uid =', \auth->uid, ' WHERE r.id =', \$id ); return tuwf->resNotFound if !$w->{id}; @@ -113,7 +111,7 @@ TUWF::get qr{/$RE{wid}(?:(?[\./])$RE{num})?}, sub { VNWeb::VN::Page::enrich_vn($v); framework_ title => "Review of $v->{title}", index => 1, type => 'v', dbobj => $v, hiddenmsg => 1, - js => 1, pagevars => {sethash=>$num?$num:$page>1?'threadstart':'review'}, + pagevars => {sethash=>$num?$num:$page>1?'threadstart':'review'}, sub { VNWeb::VN::Page::infobox_($v); VNWeb::VN::Page::tabs_($v, 'reviews'); diff --git a/lib/VNWeb/Reviews/VNTab.pm b/lib/VNWeb/Reviews/VNTab.pm index 95fc18aa..c2a6dee6 100644 --- a/lib/VNWeb/Reviews/VNTab.pm +++ b/lib/VNWeb/Reviews/VNTab.pm @@ -1,6 +1,7 @@ package VNWeb::Reviews::VNTab; use VNWeb::Prelude; +use VNWeb::Reviews::Lib; sub reviews_ { @@ -10,12 +11,13 @@ sub reviews_ { # TODO: Order my $lst = tuwf->dbAlli( - 'SELECT r.id, r.rid, r.summary, r.text <> \'\' AS isfull, r.spoiler, r.c_up, r.c_down, r.c_count, uv.vote, rv.vote AS my + 'SELECT r.id, r.rid, r.summary, r.text <> \'\' AS isfull, r.spoiler, r.c_up, r.c_down, r.c_count, uv.vote, rv.vote AS my, r2.id IS NULL AS can , ', sql_totime('r.date'), 'AS date, ', sql_user(), ' FROM reviews r LEFT JOIN users u ON r.uid = u.id LEFT JOIN ulist_vns uv ON uv.uid = r.uid AND uv.vid = r.vid LEFT JOIN reviews_votes rv ON rv.uid =', \auth->uid, ' AND rv.id = r.id + LEFT JOIN reviews r2 ON r2.vid = r.vid AND r2.uid =', \auth->uid, ' WhERE r.vid =', \$v->{id} ); @@ -53,9 +55,7 @@ sub reviews_ { div_ sub { a_ href => "/$r->{id}#review", 'Full review ยป' if $r->{isfull}; a_ href => "/$r->{id}#threadstart", $r->{c_count} == 1 ? '1 comment' : "$r->{c_count} comments"; - elm_ 'Reviews.Vote' => $VNWeb::Reviews::Elm::VOTE_OUT, { %$r, can => auth && $r->{user_id} != auth->uid }, sub { - span_ sprintf '๐Ÿ‘ %d ๐Ÿ‘Ž %d', $r->{c_up}, $r->{c_down}; - }; + review_vote_ $r; }; } for @$lst; } diff --git a/sql/func.sql b/sql/func.sql index da06450f..215f87c1 100644 --- a/sql/func.sql +++ b/sql/func.sql @@ -183,10 +183,11 @@ END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION update_reviews_votes_cache(vndbid) RETURNS void AS $$ BEGIN WITH stats(id,up,down) AS ( - SELECT r.id, COUNT(*) FILTER(WHERE rv.vote AND NOT u.ign_votes), COUNT(*) FILTER(WHERE NOT rv.vote AND NOT u.ign_votes) + SELECT r.id, COUNT(*) FILTER(WHERE rv.vote AND NOT u.ign_votes AND r2.id IS NULL), COUNT(*) FILTER(WHERE NOT rv.vote AND NOT u.ign_votes AND r2.id IS NULL) FROM reviews r LEFT JOIN reviews_votes rv ON rv.id = r.id LEFT JOIN users u ON u.id = rv.uid + LEFT JOIN reviews r2 ON r2.vid = r.vid AND r2.uid = rv.uid WHERE $1 IS NULL OR r.id = $1 GROUP BY r.id ) -- cgit v1.2.3