diff options
author | Yorhel <git@yorhel.nl> | 2020-04-25 13:53:32 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2020-04-25 13:53:34 +0200 |
commit | 0f88e511bd1b3fdcc290ee1cc5da69690e191137 (patch) | |
tree | fe1704d23be1f182191e4a7a4bfc5af9bcae2dd3 | |
parent | 00fbcdf96432efad85abdbc7392d493409a4c253 (diff) |
imgflag: Add moderator vote overruling
Works exactly like tag vote overruling.
-rw-r--r-- | data/style.css | 3 | ||||
-rw-r--r-- | elm/ImageFlagging.elm | 53 | ||||
-rw-r--r-- | lib/VNWeb/Elm.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/Images/Vote.pm | 48 | ||||
-rw-r--r-- | sql/func.sql | 4 | ||||
-rw-r--r-- | sql/schema.sql | 3 | ||||
-rw-r--r-- | sql/triggers.sql | 2 | ||||
-rw-r--r-- | util/updates/2020-04-25-imgflag-overrule.sql | 4 |
8 files changed, 79 insertions, 40 deletions
diff --git a/data/style.css b/data/style.css index 2b4974d2..2bacbb1e 100644 --- a/data/style.css +++ b/data/style.css @@ -1115,8 +1115,11 @@ p.filselect i { font-style: normal } .imageflag > div:nth-child(4) ul li label { display: inline-block; border: 1px solid $secborder$; padding: 7px; width: 100px; white-space: nowrap; margin: 2px 0; cursor: pointer } .imageflag > div:nth-child(4) ul li.sel label, .imageflag > div:nth-child(4) ul li label:hover { background-color: $secbg$ } +.imageflag > div:nth-child(4) ul li.overrule label { position: relative; left: 80px; border: none; padding: 0 } .imageflag > div:nth-child(6) { min-height: 200px; padding: 15px 0 } .imageflag > div:nth-child(6) table { margin: 0 auto } +.imageflag > div:nth-child(6) table tr.ignored td:nth-child(2), +.imageflag > div:nth-child(6) table tr.ignored td:nth-child(3) { text-decoration: line-through; color: $grayedout$ } .imageflag > div:nth-child(6) table td { min-width: 50px } .imageflag .fullscreen { position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-repeat: no-repeat; background-position: center; background-size: contain; background-color: #000 } diff --git a/elm/ImageFlagging.elm b/elm/ImageFlagging.elm index 2ea7eb36..e864b2c4 100644 --- a/elm/ImageFlagging.elm +++ b/elm/ImageFlagging.elm @@ -39,6 +39,7 @@ type alias Model = , showVotes : Bool , myVotes : Int , nsfwToken : String + , mod : Bool , images : Array.Array GApi.ApiImageResult , index : Int , desc : (Maybe Int, Maybe Int) @@ -59,6 +60,7 @@ init d = , showVotes = d.single , myVotes = d.my_votes , nsfwToken = d.nsfw_token + , mod = d.mod , images = Array.fromList d.images , index = if d.single then 0 else List.length d.images , desc = Maybe.withDefault (Nothing,Nothing) <| Maybe.map (\i -> (i.my_sexual, i.my_violence)) <| if d.single then List.head d.images else Nothing @@ -72,29 +74,29 @@ init d = } -keyToVote : Model -> String -> Maybe (Maybe Int, Maybe Int) +keyToVote : Model -> String -> Maybe (Maybe Int, Maybe Int, Bool) keyToVote model k = - let (s,v) = Maybe.withDefault (Nothing,Nothing) <| Maybe.map (\i -> (i.my_sexual, i.my_violence)) <| Array.get model.index model.images + let (s,v,o) = Maybe.withDefault (Nothing,Nothing,False) <| Maybe.map (\i -> (i.my_sexual, i.my_violence, i.my_overrule)) <| Array.get model.index model.images in case k of - "1" -> Just (Just 0, Just 0) - "2" -> Just (Just 1, Just 0) - "3" -> Just (Just 2, Just 0) - "4" -> Just (Just 0, Just 1) - "5" -> Just (Just 1, Just 1) - "6" -> Just (Just 2, Just 1) - "7" -> Just (Just 0, Just 2) - "8" -> Just (Just 1, Just 2) - "9" -> Just (Just 2, Just 2) - "s" -> Just (Just 0, v) - "d" -> Just (Just 1, v) - "f" -> Just (Just 2, v) - "j" -> Just (s, Just 0) - "k" -> Just (s, Just 1) - "l" -> Just (s, Just 2) + "1" -> Just (Just 0, Just 0, o) + "2" -> Just (Just 1, Just 0, o) + "3" -> Just (Just 2, Just 0, o) + "4" -> Just (Just 0, Just 1, o) + "5" -> Just (Just 1, Just 1, o) + "6" -> Just (Just 2, Just 1, o) + "7" -> Just (Just 0, Just 2, o) + "8" -> Just (Just 1, Just 2, o) + "9" -> Just (Just 2, Just 2, o) + "s" -> Just (Just 0, v, o) + "d" -> Just (Just 1, v, o) + "f" -> Just (Just 2, v, o) + "j" -> Just (s, Just 0, o) + "k" -> Just (s, Just 1, o) + "l" -> Just (s, Just 2, o) _ -> Nothing keydown : Model -> JD.Decoder Msg -keydown model = JD.andThen (\k -> keyToVote model k |> Maybe.map (\(s,v) -> JD.succeed (Desc s v)) |> Maybe.withDefault (JD.fail "")) (JD.field "key" JD.string) +keydown model = JD.andThen (\k -> keyToVote model k |> Maybe.map (\(s,v,_) -> JD.succeed (Desc s v)) |> Maybe.withDefault (JD.fail "")) (JD.field "key" JD.string) keyup : Model -> JD.Decoder Msg keyup model = @@ -104,7 +106,7 @@ keyup model = "ArrowRight" -> JD.succeed Next "v" -> JD.succeed (Fullscreen (not model.fullscreen)) "Escape" -> JD.succeed (Fullscreen False) - _ -> keyToVote model k |> Maybe.map (\(s,v) -> JD.succeed (Vote s v True)) |> Maybe.withDefault (JD.fail "") + _ -> keyToVote model k |> Maybe.map (\(s,v,o) -> JD.succeed (Vote s v o True)) |> Maybe.withDefault (JD.fail "") ) (JD.field "key" JD.string) @@ -114,7 +116,7 @@ type Msg | Fullscreen Bool | Desc (Maybe Int) (Maybe Int) | Load GApi.Response - | Vote (Maybe Int) (Maybe Int) Bool + | Vote (Maybe Int) (Maybe Int) Bool Bool | Save | Saved GApi.Response | Prev @@ -158,18 +160,18 @@ update msg model = in pre (nc, Cmd.none) Load e -> ({ model | loadState = Api.Error e }, Cmd.none) - Vote s v _ -> + Vote s v o _ -> case Array.get model.index model.images of Nothing -> (model, Cmd.none) Just i -> - let m = { model | saved = False, images = Array.set model.index { i | my_sexual = s, my_violence = v } model.images } + let m = { model | saved = False, images = Array.set model.index { i | my_sexual = s, my_violence = v, my_overrule = o } model.images } adv = if not m.single && (i.my_sexual == Nothing || i.my_violence == Nothing) then 1 else 0 in case (i.token,s,v) of -- Complete vote, mark it as a change and go to next image (Just token, Just xs, Just xv) -> desc <| pre <| save <| load ({ m | index = m.index + adv , myVotes = m.myVotes + adv - , changes = Dict.insert i.id { id = i.id, token = token, sexual = xs, violence = xv } m.changes + , changes = Dict.insert i.id { id = i.id, token = token, sexual = xs, violence = xv, overrule = o } m.changes }, Cmd.none) -- Otherwise just save it internally _ -> (m, Cmd.none) @@ -202,7 +204,7 @@ view model = let sel = i.my_sexual == s && i.my_violence == v in li [ classList [("sel", sel || (s /= i.my_sexual && Tuple.first model.desc == s) || (v /= i.my_violence && Tuple.second model.desc == v))] ] [ label [ onMouseOver (Desc s v), onMouseOut (Desc i.my_sexual i.my_violence) ] - [ input [ type_ "radio", onCheck (Vote s v), checked sel, onFocus (Focus lid), id lid ] [], text lbl ] + [ input [ type_ "radio", onCheck (Vote s v i.my_overrule), checked sel, onFocus (Focus lid), id lid ] [], text lbl ] ] votestats i = @@ -220,7 +222,7 @@ view model = ] , table [] <| List.map (\v -> - tr [] + tr [ classList [("ignored", v.ignore)]] [ td [ Ffi.innerHtml v.user ] [] , td [] [ text <| if v.sexual == 0 then "Safe" else if v.sexual == 1 then "Suggestive" else "Explicit" ] , td [] [ text <| if v.violence == 0 then "Tame" else if v.violence == 1 then "Violent" else "Brutal" ] @@ -286,6 +288,7 @@ view model = , but i (Just 0) i.my_violence "vio0" " Safe" , but i (Just 1) i.my_violence "vio1" " Suggestive" , but i (Just 2) i.my_violence "vio2" " Explicit" + , if model.mod then li [ class "overrule" ] [ label [ title "Overrule" ] [ inputCheck "" i.my_overrule (\b -> Vote i.my_sexual i.my_violence b True), text " Overrule" ] ] else text "" ] , ul [] [ li [] [ b [] [ text "Violence" ] ] diff --git a/lib/VNWeb/Elm.pm b/lib/VNWeb/Elm.pm index 7cce9109..f945989c 100644 --- a/lib/VNWeb/Elm.pm +++ b/lib/VNWeb/Elm.pm @@ -93,6 +93,7 @@ our %apis = ( violence_stddev => { num => 1, required => 0 }, my_sexual => { uint => 1, required => 0 }, my_violence => { uint => 1, required => 0 }, + my_overrule => { anybool => 1 }, entry => { required => 0, type => 'hash', keys => { id => {}, title => {}, @@ -102,6 +103,7 @@ our %apis = ( uid => { uint => 1, required => 0 }, sexual => { uint => 1 }, violence => { uint => 1 }, + ignore => { anybool => 1 }, } }, } } ], ); diff --git a/lib/VNWeb/Images/Vote.pm b/lib/VNWeb/Images/Vote.pm index 507590c8..60cb0787 100644 --- a/lib/VNWeb/Images/Vote.pm +++ b/lib/VNWeb/Images/Vote.pm @@ -30,6 +30,7 @@ sub enrich_image { , i.c_sexual_avg AS sexual_avg, i.c_sexual_stddev AS sexual_stddev , i.c_violence_avg AS violence_avg, i.c_violence_stddev AS violence_stddev , iv.sexual AS my_sexual, iv.violence AS my_violence + , COALESCE(EXISTS(SELECT 1 FROM image_votes iv0 WHERE iv0.id = i.id AND iv0.ignore) AND NOT iv.ignore, FALSE) AS my_overrule , COALESCE('v'||v.id, 'c'||c.id, 'v'||vsv.id) AS entry_id , COALESCE(v.title, c.name, vsv.title) AS entry_title FROM images i @@ -42,7 +43,7 @@ sub enrich_image { }, $l; enrich votes => id => id => sub { sql ' - SELECT iv.id, iv.uid, iv.sexual, iv.violence, ', sql_user(), ' + SELECT iv.id, iv.uid, iv.sexual, iv.violence, iv.ignore OR (u.id IS NOT NULL AND NOT u.perm_imgvote) AS ignore, ', sql_user(), ' FROM image_votes iv LEFT JOIN users u ON u.id = iv.uid WHERE iv.id IN', $_, @@ -63,15 +64,11 @@ sub enrich_image { } -sub my_votes { - auth ? tuwf->dbVali('SELECT c_imgvotes FROM users WHERE id =', \auth->uid) : 0 -} - - my $SEND = form_compile any => { images => $VNWeb::Elm::apis{ImageResult}[0], single => { anybool => 1 }, warn => { anybool => 1 }, + mod => { anybool => 1 }, my_votes => { uint => 1 }, pWidth => { uint => 1 }, # Set by JS pHeight => { uint => 1 }, # ^ @@ -127,20 +124,49 @@ elm_api ImageVote => undef, { token => {}, sexual => { uint => 1, range => [0,2] }, violence => { uint => 1, range => [0,2] }, + overrule => { anybool => 1 }, } }, }, sub { my($data) = @_; return elm_Unauth if !auth->permImgvote; return elm_CSRF if !validate_token $data->{votes}; + + # Find out if any of these images are being overruled + enrich_merge id => sub { sql 'SELECT id, bool_or(ignore) AS overruled FROM image_votes WHERE id IN', $_, 'GROUP BY id' }, $data->{votes}; + enrich_merge id => sql('SELECT id, NOT ignore AS my_overrule FROM image_votes WHERE uid =', \auth->uid, 'AND id IN'), + grep $_->{overruled}, $data->{votes}->@* if auth->permDbmod; + for($data->{votes}->@*) { - $_->{uid} = auth->uid ; - delete $_->{token}; - tuwf->dbExeci('INSERT INTO image_votes', $_, 'ON CONFLICT (id, uid) DO UPDATE SET', $_, ', date = now()'); + $_->{overrule} = 0 if !auth->permDbmod; + my $d = { + id => $_->{id}, + uid => auth->uid(), + sexual => $_->{sexual}, + violence => $_->{violence}, + ignore => !$_->{overrule} && !$_->{my_overrule} && $_->{overruled} ? 1 : 0, + }; + tuwf->dbExeci('INSERT INTO image_votes', $d, 'ON CONFLICT (id, uid) DO UPDATE SET', $d, ', date = now()'); + tuwf->dbExeci('UPDATE image_votes SET ignore =', \($_->{overrule}?1:0), 'WHERE uid IS DISTINCT FROM', \auth->uid, 'AND id =', \$_->{id}) + if !$_->{overrule} != !$_->{my_overrule}; } elm_Success }; +sub my_votes { + auth ? tuwf->dbVali('SELECT c_imgvotes FROM users WHERE id =', \auth->uid) : 0 +} + + +sub imgflag_ { + elm_ 'ImageFlagging', $SEND, { + my_votes => my_votes(), + nsfw_token => viewset(show_nsfw => 1), + mod => auth->permDbmod()||0, + @_ + }; +} + TUWF::get qr{/img/vote}, sub { return tuwf->resDenied if !auth->permImgvote; @@ -150,7 +176,7 @@ TUWF::get qr{/img/vote}, sub { enrich_token 1, $recent; framework_ title => 'Image flagging', sub { - elm_ 'ImageFlagging', $SEND, { images => [ reverse @$recent ], single => 0, warn => 1, my_votes => my_votes(), nsfw_token => viewset(show_nsfw => 1) }; + imgflag_ images => [ reverse @$recent ], single => 0, warn => 1; }; }; @@ -165,7 +191,7 @@ TUWF::get qr{/img/$RE{imgid}}, sub { enrich_token defined($l->[0]{my_sexual}) || auth->permDbmod(), $l; # XXX: permImgmod? framework_ title => "Image flagging for $id", sub { - elm_ 'ImageFlagging', $SEND, { images => $l, single => 1, warn => !tuwf->samesite(), my_votes => my_votes(), nsfw_token => viewset(show_nsfw => 1) }; + imgflag_ images => $l, single => 1, warn => !tuwf->samesite(); }; }; diff --git a/sql/func.sql b/sql/func.sql index 6f0401f2..f2e836ec 100644 --- a/sql/func.sql +++ b/sql/func.sql @@ -166,8 +166,8 @@ BEGIN ELSE 0 END AS weight FROM ( SELECT i.id, count(iv.id) AS votecount - , avg(sexual) AS sexual_avg, stddev_pop(sexual) AS sexual_stddev - , avg(violence) AS violence_avg, stddev_pop(violence) AS violence_stddev + , avg(sexual) FILTER(WHERE NOT iv.ignore) AS sexual_avg, stddev_pop(sexual) FILTER(WHERE NOT iv.ignore) AS sexual_stddev + , avg(violence) FILTER(WHERE NOT iv.ignore) AS violence_avg, stddev_pop(violence) FILTER(WHERE NOT iv.ignore) AS violence_stddev FROM images i LEFT JOIN image_votes iv ON iv.id = i.id LEFT JOIN users u ON u.id = iv.uid diff --git a/sql/schema.sql b/sql/schema.sql index a8add72e..03c9e218 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -220,7 +220,8 @@ CREATE TABLE image_votes ( uid integer, -- [pub] sexual smallint NOT NULL CHECK(sexual >= 0 AND sexual <= 2), -- [pub] violence smallint NOT NULL CHECK(violence >= 0 AND violence <= 2), -- [pub] - date timestamptz NOT NULL DEFAULT NOW() -- [pub] + date timestamptz NOT NULL DEFAULT NOW(),-- [pub] + ignore boolean NOT NULL DEFAULT false -- [pub] ); -- login_throttle diff --git a/sql/triggers.sql b/sql/triggers.sql index d06faf65..b7ade0c4 100644 --- a/sql/triggers.sql +++ b/sql/triggers.sql @@ -285,4 +285,4 @@ END $$ LANGUAGE plpgsql; CREATE TRIGGER image_votes_cache1 AFTER INSERT OR DELETE ON image_votes FOR EACH ROW EXECUTE PROCEDURE update_images_cache(); -CREATE TRIGGER image_votes_cache2 AFTER UPDATE ON image_votes FOR EACH ROW WHEN ((OLD.id, OLD.sexual, OLD.violence) IS DISTINCT FROM (NEW.id, NEW.sexual, NEW.violence)) EXECUTE PROCEDURE update_images_cache(); +CREATE TRIGGER image_votes_cache2 AFTER UPDATE ON image_votes FOR EACH ROW WHEN ((OLD.id, OLD.sexual, OLD.violence, OLD.ignore) IS DISTINCT FROM (NEW.id, NEW.sexual, NEW.violence, NEW.ignore)) EXECUTE PROCEDURE update_images_cache(); diff --git a/util/updates/2020-04-25-imgflag-overrule.sql b/util/updates/2020-04-25-imgflag-overrule.sql new file mode 100644 index 00000000..19f8d8a5 --- /dev/null +++ b/util/updates/2020-04-25-imgflag-overrule.sql @@ -0,0 +1,4 @@ +ALTER TABLE image_votes ADD COLUMN ignore boolean NOT NULL DEFAULT false; +DROP TRIGGER image_votes_cache2 ON image_votes; +CREATE TRIGGER image_votes_cache2 AFTER UPDATE ON image_votes FOR EACH ROW WHEN ((OLD.id, OLD.sexual, OLD.violence, OLD.ignore) IS DISTINCT FROM (NEW.id, NEW.sexual, NEW.violence, NEW.ignore)) EXECUTE PROCEDURE update_images_cache(); +\i sql/func.sql |