summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-01-28 09:23:45 +0100
committerYorhel <git@yorhel.nl>2020-01-28 16:42:44 +0100
commit915ea70075d6ffced641ac52cf567acc747aa7eb (patch)
tree1f0955db45b2f6048b35165eddf7a1e3e3f1e781 /lib
parent495e03c42b378bfd5ee9bb0c431bac45c5e2b330 (diff)
v2rw: Convert the VN tagmod interface
This also changes the voting interface a little bit: - Spoiler options are a bit more concise - Mouse-over a button indicates what it does - The -1 and -2 options are not available anymore - Downvoted tags are hidden by default - Moderators can now vote-and-overrule in a single go
Diffstat (limited to 'lib')
-rw-r--r--lib/VNDB/DB/Tags.pm34
-rw-r--r--lib/VNDB/Handler/Tags.pm161
-rw-r--r--lib/VNWeb/DB.pm2
-rw-r--r--lib/VNWeb/Elm.pm16
-rw-r--r--lib/VNWeb/Prelude.pm1
-rw-r--r--lib/VNWeb/Tags/Elm.pm24
-rw-r--r--lib/VNWeb/VN/Tagmod.pm90
7 files changed, 129 insertions, 199 deletions
diff --git a/lib/VNDB/DB/Tags.pm b/lib/VNDB/DB/Tags.pm
index 875ff6e9..ed3ea9fe 100644
--- a/lib/VNDB/DB/Tags.pm
+++ b/lib/VNDB/DB/Tags.pm
@@ -5,7 +5,7 @@ use strict;
use warnings;
use Exporter 'import';
-our @EXPORT = qw|dbTagGet dbTTTree dbTagEdit dbTagAdd dbTagMerge dbTagLinks dbTagLinkEdit dbTagStats dbTagWipeVotes|;
+our @EXPORT = qw|dbTagGet dbTTTree dbTagEdit dbTagAdd dbTagMerge dbTagLinks dbTagStats dbTagWipeVotes|;
# %options->{ id noid name search state searchable applicable page results what sort reverse }
@@ -211,38 +211,6 @@ sub dbTagLinks {
}
-# Change a user's tags for a VN entry
-sub dbTagLinkEdit {
- my($self, $uid, $vid, $insert, $update, $delete, $overrule) = @_;
-
- # overrule
- # 1. set ignore flag for everyone except $uid
- $self->dbExec('UPDATE tags_vn SET ignore = ? WHERE tag = ? AND vid = ? AND uid <> ?',
- $overrule->{$_}?1:0, $_, $vid, $uid) for(keys %$overrule);
- # 2. make sure $uid isn't ignored when others are set to ignore
- # (this happens when a mod takes over an other mods' overrule)
- $self->dbExec('UPDATE tags_vn SET ignore = false WHERE tag = ? AND vid = ? AND uid = ?',
- $_, $vid, $uid) for(grep $overrule->{$_}, keys %$overrule);
-
- # delete
- $self->dbExec('DELETE FROM tags_vn WHERE vid = ? AND uid = ? AND tag IN(!l)',
- $vid, $uid, [ keys %$delete ]) if keys %$delete;
-
- # insert
- my $val = join ',', map '(?,?,?,?,?,?)', keys %$insert;
- $self->dbExec("INSERT INTO tags_vn (tag, vid, uid, vote, spoiler, ignore) VALUES $val", map
- +($_, $vid, $uid, $insert->{$_}[0], $insert->{$_}[1]<0?undef:$insert->{$_}[1], $insert->{$_}[2]?1:0),
- keys %$insert) if keys %$insert;
-
- # update
- $self->dbExec('UPDATE tags_vn SET vote = ?, spoiler = ?, date = NOW() WHERE tag = ? AND vid = ? AND uid = ?',
- $update->{$_}[0], $update->{$_}[1]<0?undef:$update->{$_}[1], $_, $vid, $uid) for (keys %$update);
-
- # Update cache
- $self->dbExec('SELECT tag_vn_calc(?)', $vid);
-}
-
-
# Fetch all tags related to a VN
# Argument: %options->{ vid minrating state results what page sort reverse }
# sort: name, rating
diff --git a/lib/VNDB/Handler/Tags.pm b/lib/VNDB/Handler/Tags.pm
index 5acc948f..c44529cf 100644
--- a/lib/VNDB/Handler/Tags.pm
+++ b/lib/VNDB/Handler/Tags.pm
@@ -15,7 +15,6 @@ TUWF::register(
qr{g([1-9]\d*)/(add)}, \&tagedit,
qr{g/new}, \&tagedit,
qr{g/list}, \&taglist,
- qr{v([1-9]\d*)/tagmod}, \&vntagmod,
qr{u([1-9]\d*)/tags}, \&usertags,
qr{g}, \&tagindex,
qr{g/debug}, \&fulltree,
@@ -379,166 +378,6 @@ sub taglist {
}
-sub vntagmod {
- my($self, $vid) = @_;
-
- my $v = $self->dbVNGet(id => $vid)->[0];
- return $self->resNotFound if !$v || $v->{hidden};
-
- return $self->htmlDenied if !$self->authCan('tag');
-
- my $tags = $self->dbTagStats(vid => $vid, results => 9999);
- my $my = $self->dbTagLinks(vid => $vid, uid => $self->authInfo->{id});
-
- if($self->reqMethod eq 'POST') {
- return if !$self->authCheckCode;
- my $frm = $self->formValidate(
- { post => 'taglinks', required => 0, default => '', maxlength => 10240, regex => [ qr/^[1-9][0-9]*,-?[1-3],-?[0-2]( [1-9][0-9]*,-?[1-3],-?[0-2])*$/, 'meh' ] },
- { post => 'overrule', required => 0, multi => 1, template => 'id' },
- );
- return $self->resNotFound if $frm->{_err};
-
- # convert some data in a more convenient structure for faster lookup
- my %tags = map +($_->{id} => $_), @$tags;
- my %old = map +($_->{tag} => $_), @$my;
- my %new = map { my($tag, $vote, $spoiler) = split /,/; ($tag => [ $vote, $spoiler ]) } split / /, $frm->{taglinks};
- my %over = !$self->authCan('tagmod') || !$frm->{overrule}[0] ? () : (map $new{$_} ? ($_ => 1) : (), @{$frm->{overrule}});
-
- # hashes which need to be filled, indicating what should be changed to the DB
- my %delete; # tag => 1
- my %update; # tag => [ vote, spoiler ] (ignore flag is untouched)
- my %insert; # tag => [ vote, spoiler, ignore ]
- my %overrule; # tag => 0/1
-
- # remove tags in the deleted state
- delete $new{$_->{id}} for(keys %new ? @{$self->dbTagGet(id => [ keys %new ], state => 1)} : ());
- # and not-applicable tags
- delete $new{$_->{id}} for(keys %new ? @{$self->dbTagGet(id => [ keys %new ], applicable => 0)} : ());
-
- for my $t (keys %old, keys %new) {
- my $prev_over = $old{$t} && !$old{$t}{ignore} && $tags{$t}{overruled};
-
- # overrule checkbox has changed? make sure to (de-)overrule the tag votes
- $overrule{$t} = $over{$t}?1:0 if (!$prev_over && $over{$t}) || ($prev_over && !$over{$t});
-
- # tag deleted?
- if($old{$t} && !$new{$t}) {
- $delete{$t} = 1;
- next;
- }
-
- # and insert or update the vote
- if(!$old{$t} && $new{$t}) {
- # determine whether this vote is going to be ignored or not
- my $ign = $tags{$t}{overruled} && !$prev_over && !$over{$t};
- $insert{$t} = [ $new{$t}[0], $new{$t}[1], $ign ];
- } elsif($old{$t}{vote} != $new{$t}[0] || (defined $old{$t}{spoiler} ? $old{$t}{spoiler} : -1) != $new{$t}[1]) {
- $update{$t} = [ $new{$t}[0], $new{$t}[1] ];
- }
- }
-
- $self->dbTagLinkEdit($self->authInfo->{id}, $vid, \%insert, \%update, \%delete, \%overrule);
-
- # need to re-fetch the tags and tag links, as these have been modified
- $tags = $self->dbTagStats(vid => $vid, results => 9999);
- $my = $self->dbTagLinks(vid => $vid, uid => $self->authInfo->{id});
- }
-
-
- my $title = "Add/remove tags for $v->{title}";
- $self->htmlHeader(title => $title, noindex => 1);
- $self->htmlMainTabs('v', $v, 'tagmod');
- div class => 'mainbox';
- h1 $title;
- div class => 'notice';
- h2 'Tagging';
- ul;
- li; txt 'Make sure you have read the '; a href => '/d10', 'guidelines'; txt '!'; end;
- li 'Don\'t forget to hit the submit button on the bottom of the page to make your changes permanent.';
- end;
- end;
- end 'div';
- $self->htmlForm({ action => "/v$vid/tagmod", nosubmit => 1 }, tagmod => [ 'Tags',
- [ hidden => short => 'taglinks', value => '' ],
- [ static => nolabel => 1, content => sub {
- table class => 'tgl stripe';
- thead;
- Tr;
- td '';
- td colspan => $self->authCan('tagmod') ? 3 : 2, class => 'tc_you', 'You';
- td colspan => 3, class => 'tc_others', 'Others';
- end;
- Tr;
- td class => 'tc_tagname', 'Tag';
- td class => 'tc_myvote', 'Rating';
- td class => 'tc_myover', 'O' if $self->authCan('tagmod');
- td class => 'tc_myspoil', 'Spoiler';
- td class => 'tc_allvote', 'Rating';
- td class => 'tc_allspoil', 'Spoiler';
- td class => 'tc_allwho', '';
- end;
- end 'thead';
- tfoot; Tr;
- td colspan => 6;
- input type => 'submit', class => 'submit', value => 'Save changes', style => 'float: right';
- input id => 'tagmod_tag', type => 'text', class => 'text', value => '';
- input id => 'tagmod_add', type => 'button', class => 'submit', value => 'Add tag';
- br;
- p;
- txt 'Check the '; a href => '/g', 'tag list'; txt ' to browse all available tags.';
- br;
- txt 'Can\'t find what you\'re looking for? '; a href => '/g/new', 'Request a new tag'; txt '.';
- end;
- end;
- end; end 'tfoot';
- tbody id => 'tagtable';
- _tagmod_list($self, $vid, $tags, $my);
- end 'tbody';
- end 'table';
- } ],
- ]);
- $self->htmlFooter;
-}
-
-sub _tagmod_list {
- my($self, $vid, $tags, $my) = @_;
-
- my %my = map +($_->{tag} => $_), @$my;
-
- for my $cat (keys %TAG_CATEGORY) {
- my @tags = grep $_->{cat} eq $cat, @$tags;
- next if !@tags;
- Tr class => 'tagmod_cat';
- td colspan => 7, $TAG_CATEGORY{$cat};
- end;
- for my $t (@tags) {
- my $m = $my{$t->{id}};
- Tr id => "tgl_$t->{id}";
- td class => 'tc_tagname'; a href => "/g$t->{id}", $t->{name}; end;
- td class => 'tc_myvote', $m->{vote}||0;
- if($self->authCan('tagmod')) {
- td class => 'tc_myover';
- input type => 'checkbox', name => 'overrule', value => $t->{id},
- $m->{vote} && !$m->{ignore} && $t->{overruled} ? (checked => 'checked') : ()
- if $t->{cnt} > 1;
- end;
- }
- td class => 'tc_myspoil', defined $m->{spoiler} ? $m->{spoiler} : -1;
- td class => 'tc_allvote';
- VNWeb::Tags::Lib::tagscore_($t->{rating});
- i $t->{overruled} ? (class => 'grayedout') : (), " ($t->{cnt})";
- b class => 'standout', style => 'font-weight: bold', title => 'Tag overruled. All votes other than that of the moderator who overruled it will be ignored.', ' !' if $t->{overruled};
- end;
- td class => 'tc_allspoil', sprintf '%.2f', $t->{spoiler};
- td class => 'tc_allwho';
- a href => "/g/links?v=$vid;t=$t->{id}", 'Who?';
- end;
- end;
- }
- }
-}
-
-
sub tagindex {
my $self = shift;
diff --git a/lib/VNWeb/DB.pm b/lib/VNWeb/DB.pm
index 2e7cf7f6..30018c96 100644
--- a/lib/VNWeb/DB.pm
+++ b/lib/VNWeb/DB.pm
@@ -185,7 +185,7 @@ sub enrich_merge {
_enrich sub {
my($data, $array) = @_;
my %ids = map +(delete($_->{$key}), $_), @$data;
- %$_ = (%$_, $ids{ $_->{$key} }->%*) for @$array;
+ %$_ = (%$_, ($ids{ $_->{$key} }||{})->%*) for @$array;
}, $key, $sql, @array;
}
diff --git a/lib/VNWeb/Elm.pm b/lib/VNWeb/Elm.pm
index 0c8b42eb..d98d1967 100644
--- a/lib/VNWeb/Elm.pm
+++ b/lib/VNWeb/Elm.pm
@@ -1,7 +1,8 @@
# This module is responsible for generating elm/Gen/*.
#
-# It exports an `elm_form` function to generate type definitions, a JSON
-# encoder and HTML5 validation attributes to simplify and synchronize forms.
+# It exports an `elm_api` function to create an API endpoint, type definitions,
+# a JSON encoder and HTML5 validation attributes to simplify and synchronize
+# forms.
#
# It also exports an `elm_Response` function for each possible API response
# (see %apis below).
@@ -47,7 +48,7 @@ my %apis = (
DoubleIP => [], # Account with same IP already exists
BadCurPass => [], # Current password is incorrect when changing password
MailChange => [], # A confirmation mail has been sent to change a user's email address
- Releases => [ { aoh => { # Response to /r/get.json
+ Releases => [ { aoh => { # Response to 'Release'
id => { id => 1 },
title => {},
original => { required => 0, default => '' },
@@ -56,11 +57,18 @@ my %apis = (
lang => { type => 'array', values => {} },
platforms=> { type => 'array', values => {} },
} } ],
- BoardResult => [ { aoh => { # Response to /t/boards.json
+ BoardResult => [ { aoh => { # Response to 'Boards'
btype => {},
iid => { required => 0, default => 0, id => 1 },
title => { required => 0 },
} } ],
+ TagResult => [ { aoh => { # Response to 'Tags'
+ id => { id => 1 },
+ name => {},
+ searchable => { anybool => 1 },
+ applicable => { anybool => 1 },
+ state => { int => 1 },
+ } } ],
);
diff --git a/lib/VNWeb/Prelude.pm b/lib/VNWeb/Prelude.pm
index e44703a6..aa6d9291 100644
--- a/lib/VNWeb/Prelude.pm
+++ b/lib/VNWeb/Prelude.pm
@@ -89,6 +89,7 @@ our %RE = (
iid => qr{i$id},
did => qr{d$id},
tid => qr{t$id},
+ gid => qr{g$id},
vrev => qr{v$id$rev?},
rrev => qr{r$id$rev?},
prev => qr{p$id$rev?},
diff --git a/lib/VNWeb/Tags/Elm.pm b/lib/VNWeb/Tags/Elm.pm
new file mode 100644
index 00000000..0f816bad
--- /dev/null
+++ b/lib/VNWeb/Tags/Elm.pm
@@ -0,0 +1,24 @@
+package VNWeb::Tags::Elm;
+
+use VNWeb::Prelude;
+
+elm_api Tags => undef, { search => {} }, sub {
+ my $q = shift->{search};
+ my $qs = $q =~ s/[%_]//gr;
+
+ elm_TagResult tuwf->dbPagei({ results => 15, page => 1 },
+ 'SELECT t.id, t.name, t.searchable, t.applicable, t.state
+ FROM (',
+ sql_join('UNION ALL',
+ $q =~ /^$RE{gid}$/ ? sql('SELECT 1, id FROM tags WHERE id =', \"$+{id}") : (),
+ sql('SELECT 1+substr_score(lower(name),', \$qs, '), id FROM tags WHERE name ILIKE', \"%$qs%"),
+ sql('SELECT 10+substr_score(lower(alias),', \$qs, '), tag FROM tags_aliases WHERE alias ILIKE', \"%$qs%"),
+ ), ') x (prio, id)
+ JOIN tags t ON t.id = x.id
+ WHERE t.state <> 1
+ GROUP BY t.id, t.name, t.searchable, t.applicable, t.state
+ ORDER BY MIN(x.prio), t.name
+ ')
+};
+
+1;
diff --git a/lib/VNWeb/VN/Tagmod.pm b/lib/VNWeb/VN/Tagmod.pm
new file mode 100644
index 00000000..8a2e2509
--- /dev/null
+++ b/lib/VNWeb/VN/Tagmod.pm
@@ -0,0 +1,90 @@
+package VNWeb::VN::Tagmod;
+
+use VNWeb::Prelude;
+use VNWeb::Tags::Lib;
+
+
+my $FORM = {
+ id => { id => 1 },
+ title => { _when => 'out' },
+ tags => { aoh => {
+ id => { id => 1 },
+ vote => { int => 1, enum => [ -3..3 ] },
+ spoil => { required => 0, uint => 1, enum => [ 0..2 ] },
+ overrule => { anybool => 1 },
+ cat => { _when => 'out' },
+ name => { _when => 'out' },
+ rating => { _when => 'out', num => 1 },
+ count => { _when => 'out', uint => 1 },
+ spoiler => { _when => 'out', num => 1 },
+ overruled => { _when => 'out', anybool => 1 },
+ } },
+ mod => { _when => 'out', anybool => 1 },
+};
+
+my $FORM_IN = form_compile in => $FORM;
+my $FORM_OUT = form_compile out => $FORM;
+
+elm_api Tagmod => $FORM_OUT, $FORM_IN, sub {
+ my($id, $tags) = $_[0]->@{'id', 'tags'};
+ return elm_Unauth if !auth->permTag;
+
+ $tags = [ grep $_->{vote}, @$tags ];
+ $_->{overrule} = 0 for auth->permTagmod ? () : @$tags;
+
+ # Weed out invalid/deleted/non-applicable tags
+ enrich_merge id => 'SELECT id, 1 as exists FROM tags WHERE state <> 1 AND applicable AND id IN', $tags;
+ $tags = [ grep $_->{exists}, @$tags ];
+
+ # Find out if any of these tags are being overruled
+ enrich_merge id => sub { sql 'SELECT tag AS id, bool_or(ignore) as overruled FROM tags_vn WHERE vid =', \$id, 'AND tag IN', $_, 'GROUP BY tag' }, $tags;
+
+ # Delete tag votes not in $tags
+ tuwf->dbExeci('DELETE FROM tags_vn WHERE uid =', \auth->uid, 'AND vid =', \$id, @$tags ? ('AND tag NOT IN', [ map $_->{id}, @$tags ]) : ());
+
+ # Add & update tags
+ for(@$tags) {
+ my $row = { uid => auth->uid, vid => $id, tag => $_->{id}, vote => $_->{vote}, spoiler => $_->{spoil}, ignore => ($_->{overruled} && !$_->{overrule})?1:0 };
+ tuwf->dbExeci('INSERT INTO tags_vn', $row, 'ON CONFLICT (uid, vid, tag) DO UPDATE SET', $row);
+ tuwf->dbExeci('UPDATE tags_vn SET ignore = TRUE WHERE uid <>', \auth->uid, 'AND vid =', \$id, 'AND tag =', \$_->{id}) if $_->{overrule};
+ }
+
+ # Make sure to reset the ignore flag when a moderator removes an overruled vote.
+ # (i.e. look for tags where *all* votes are on ignore)
+ tuwf->dbExeci('UPDATE tags_vn tv SET ignore = FALSE WHERE NOT EXISTS(SELECT 1 FROM tags_vn tvi WHERE tvi.tag = tv.tag AND tvi.vid = tv.vid AND NOT tvi.ignore) AND vid =', \$id) if auth->permTagmod;
+
+ tuwf->dbExeci(select => sql_func tag_vn_calc => \$id);
+ elm_Success
+};
+
+
+TUWF::get qr{/$RE{vid}/tagmod}, sub {
+ my $v = tuwf->dbRowi('SELECT id, title, hidden AS entry_hidden, locked AS entry_locked FROM vn WHERE id =', \tuwf->capture('id'));
+ return tuwf->resNotFound if !$v->{id} || (!auth->permDbmod && $v->{entry_hidden});
+ return tuwf->resDenied if !auth->permTag;
+
+ my $tags = tuwf->dbAlli('
+ SELECT t.id, t.name, t.cat, count(*) as count
+ , avg(CASE WHEN tv.ignore THEN NULL ELSE tv.vote END) as rating
+ , coalesce(avg(CASE WHEN tv.ignore THEN NULL ELSE tv.spoiler END), t.defaultspoil) as spoiler
+ , bool_or(tv.ignore) as overruled
+ FROM tags t
+ JOIN tags_vn tv ON tv.tag = t.id
+ WHERE tv.vid =', \$v->{id}, '
+ GROUP BY t.id, t.name, t.cat
+ ORDER BY t.name'
+ );
+ enrich_merge id => sub { sql 'SELECT tag AS id, vote, spoiler AS spoil, ignore FROM tags_vn WHERE', { uid => auth->uid, vid => $v->{id} } }, $tags;
+
+ for(@$tags) {
+ $_->{vote} //= 0;
+ $_->{spoil} //= undef;
+ $_->{overrule} = $_->{vote} && !$_->{ignore} && $_->{overruled};
+ }
+
+ framework_ title => "Edit tags for $v->{title}", type => 'v', dbobj => $v, tab => 'tagmod', sub {
+ elm_ 'Tagmod' => $FORM_OUT, { id => $v->{id}, title => $v->{title}, tags => $tags, mod => auth->permTagmod };
+ };
+};
+
+1;