summaryrefslogtreecommitdiff
path: root/lib/VN3/User
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VN3/User')
-rw-r--r--lib/VN3/User/Lib.pm31
-rw-r--r--lib/VN3/User/Login.pm50
-rw-r--r--lib/VN3/User/Page.pm207
-rw-r--r--lib/VN3/User/RegReset.pm137
-rw-r--r--lib/VN3/User/Settings.pm98
-rw-r--r--lib/VN3/User/VNList.pm325
6 files changed, 0 insertions, 848 deletions
diff --git a/lib/VN3/User/Lib.pm b/lib/VN3/User/Lib.pm
deleted file mode 100644
index c63e4286..00000000
--- a/lib/VN3/User/Lib.pm
+++ /dev/null
@@ -1,31 +0,0 @@
-package VN3::User::Lib;
-
-use VN3::Prelude;
-
-our @EXPORT = qw/show_list TopNav/;
-
-
-# Whether we can see the user's list
-sub show_list {
- my $u = shift;
- die "Can't determine show_list() when hide_list preference is not known" if !exists $u->{hide_list};
- auth->permUsermod || !$u->{hide_list} || $u->{id} == (auth->uid||0);
-}
-
-
-sub TopNav {
- my($page, $u) = @_;
-
- Div class => 'nav raised-top-nav', sub {
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'details'), sub { A href => "/u$u->{id}", class => 'nav__link', 'Details'; };
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'list'), sub { A href => "/u$u->{id}/list", class => 'nav__link', 'List'; } if show_list $u;
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'wish'), sub { A href => "/u$u->{id}/wish", class => 'nav__link', 'Wishlist'; } if show_list $u;
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'posts'), sub { A href => "/u$u->{id}/posts", class => 'nav__link', 'Posts'; };
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'discussions'),sub { A href => "/t/u$u->{id}", class => 'nav__link', 'Discussions'; };
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'tags'), sub { A href => "/g/links?uid=$u->{id}", class => 'nav__link', 'Tags'; };
- Div mkclass('nav__item' => 1, 'nav__item--active' => $page eq 'hist'), sub { A href => "/u$u->{id}/hist", class => 'nav__link', 'Contributions'; };
- };
-}
-
-1;
-
diff --git a/lib/VN3/User/Login.pm b/lib/VN3/User/Login.pm
deleted file mode 100644
index 7660762a..00000000
--- a/lib/VN3/User/Login.pm
+++ /dev/null
@@ -1,50 +0,0 @@
-package VN3::User::Login;
-
-use VN3::Prelude;
-
-# TODO: Redirect to a password change form when a user logs in with an insecure password.
-
-TUWF::get '/u/login' => sub {
- return tuwf->resRedirect('/', 'temp') if auth;
- Framework title => 'Login', center => 1, sub {
- Div 'data-elm-module' => 'User.Login', '';
- };
-};
-
-
-my $elm_Throttled = elm_api 'Throttled';
-my $elm_BadLogin = elm_api 'BadLogin';
-
-json_api '/u/login', {
- username => { username => 1 },
- password => { password => 1 }
-}, sub {
- my $data = shift;
-
- my $conf = tuwf->conf->{login_throttle} || [ 24*3600/10, 24*3600 ];
- my $ip = norm_ip tuwf->reqIP;
-
- my $tm = tuwf->dbVali(
- 'SELECT', sql_totime('greatest(timeout, now())'), 'FROM login_throttle WHERE ip =', \$ip
- ) || time;
-
- return $elm_Throttled->() if $tm-time() > $conf->[1];
- return $elm_Success->() if auth->login($data->{username}, $data->{password});
-
- # Failed login, update throttle.
- my $upd = {
- ip => \$ip,
- timeout => sql_fromtime $tm+$conf->[0]
- };
- tuwf->dbExeci('INSERT INTO login_throttle', $upd, 'ON CONFLICT (ip) DO UPDATE SET', $upd);
- $elm_BadLogin->()
-};
-
-
-TUWF::get qr{/$UID_RE/logout}, sub {
- return tuwf->resNotFound if !auth || auth->uid != tuwf->capture('id');
- auth->logout;
- tuwf->resRedirect('/', 'temp');
-};
-
-1;
diff --git a/lib/VN3/User/Page.pm b/lib/VN3/User/Page.pm
deleted file mode 100644
index 886ad39a..00000000
--- a/lib/VN3/User/Page.pm
+++ /dev/null
@@ -1,207 +0,0 @@
-package VN3::User::Page;
-
-use VN3::Prelude;
-use VN3::User::Lib;
-
-
-sub StatsLeft {
- my $u = shift;
- my $vns = show_list($u) && tuwf->dbVali('SELECT COUNT(*) FROM vnlists WHERE uid =', \$u->{id});
- my $rel = show_list($u) && tuwf->dbVali('SELECT COUNT(*) FROM rlists WHERE uid =', \$u->{id});
- my $posts = tuwf->dbVali('SELECT COUNT(*) FROM threads_posts WHERE uid =', \$u->{id});
- my $threads = tuwf->dbVali('SELECT COUNT(*) FROM threads_posts WHERE num = 1 AND uid =', \$u->{id});
-
- Div class => 'card__title mb-4', 'Stats';
- Div class => 'big-stats mb-5', sub {
- A href => "/u$u->{id}/list", class => 'big-stats__stat', sub {
- Txt 'Votes';
- Div class => 'big-stats__value', show_list($u) ? $u->{c_votes} : '-';
- };
- A href => "/u$u->{id}/hist", class => 'big-stats__stat', sub {
- Txt 'Edits';
- Div class => 'big-stats__value', $u->{c_changes};
- };
- A href => "/g/links?u=$u->{id}", class => 'big-stats__stat', sub {
- Txt 'Tags';
- Div class => 'big-stats__value', $u->{c_tags};
- };
- };
- Div class => 'user-stats__text', sub {
- Dl class => 'dl--horizontal', sub {
- if(show_list $u) {
- Dt 'List stats';
- Dd sprintf '%d release%s of %d visual novel%s', $rel, $rel == 1 ? '' : 's', $vns, $vns == 1 ? '' : 's';
- }
- Dt 'Forum stats';
- Dd sprintf '%d post%s, %d new thread%s', $posts, $posts == 1 ? '' : 's', $threads, $threads == 1 ? '' : 's';
- Dt 'Registered';
- Dd date_display $u->{registered};
- };
- };
-}
-
-
-sub Stats {
- my $u = shift;
-
- my($count, $Graph) = show_list($u) ? VoteGraph u => $u->{id} : ();
-
- Div class => 'card card--white card--no-separators flex-expand mb-5', sub {
- Div class => 'card__section fs-medium', sub {
- Div class => 'user-stats', sub {
- Div class => 'user-stats__left', sub { StatsLeft $u };
- Div class => 'user-stats__right', sub {
- Div class => 'card__title mb-2', 'Vote distribution';
- $Graph->();
- } if $count;
- }
- }
- }
-}
-
-
-sub List {
- my $u = shift;
- return if !show_list $u;
-
- # XXX: This query doesn't catch vote or list *changes*, only new entries.
- # We don't store the modification date in the DB at the moment.
- my $l = tuwf->dbAlli(q{
- SELECT il.vid, EXTRACT('epoch' FROM GREATEST(v.date, l.added)) AS date, vn.title, vn.original, v.vote, l.status
- FROM (
- SELECT vid FROM votes WHERE uid = }, \$u->{id}, q{
- UNION SELECT vid FROM vnlists WHERE uid = }, \$u->{id}, q{
- ) AS il (vid)
- LEFT JOIN votes v ON v.vid = il.vid
- LEFT JOIN vnlists l ON l.vid = il.vid
- JOIN vn ON vn.id = il.vid
- WHERE v.uid = }, \$u->{id}, q{
- AND l.uid = }, \$u->{id}, q{
- ORDER BY GREATEST(v.date, l.added) DESC
- LIMIT 10
- });
- return if !@$l;
-
- Div class => 'card card--white card--no-separators mb-5', sub {
- Div class => 'card__header', sub {
- Div class => 'card__title', 'Recent list additions';
- };
- Table class => 'table table--responsive-single-sm fs-medium', sub {
- Thead sub {
- Tr sub {
- Th width => '15%', 'Date';
- Th width => '50%', 'Visual novel';
- Th width => '10%', 'Vote';
- Th width => '25%', 'Status';
- };
- };
- Tbody sub {
- for my $i (@$l) {
- Tr sub {
- Td class => 'tabular-nums muted', date_display $i->{date};
- Td sub {
- A href => "/v$i->{vid}", title => $i->{original}||$i->{title}, $i->{title};
- };
- Td vote_display $i->{vote};
- Td $i->{status} ? $VNLIST_STATUS{$i->{status}} : '';
- };
- }
- };
- };
- Div class => 'card__section fs-medium', sub {
- A href => "/u$u->{id}/list", 'View full list';
- }
- };
-}
-
-
-sub Edits {
- my $u = shift;
- # XXX: This is a lazy implementation, could probably share code/UI with the database entry history tables (as in VNDB 2)
-
- my $l = tuwf->dbAlli(q{
- SELECT ch.id, ch.itemid, ch.rev, ch.type, EXTRACT('epoch' FROM ch.added) AS added
- FROM changes ch
- WHERE ch.requester =}, \$u->{id}, q{
- ORDER BY ch.added DESC LIMIT 10
- });
- return if !@$l;
-
- # This can also be written as a UNION, haven't done any benchmarking yet.
- # It doesn't matter much with only 10 entries, but it will matter if this
- # query is re-used for other history browsing purposes.
- enrich id => q{
- SELECT ch.id, COALESCE(d.title, v.title, p.name, r.title, c.name, sa.name) AS title
- FROM changes ch
- LEFT JOIN docs_hist d ON ch.type = 'd' AND d.chid = ch.id
- LEFT JOIN vn_hist v ON ch.type = 'v' AND v.chid = ch.id
- LEFT JOIN producers_hist p ON ch.type = 'p' AND p.chid = ch.id
- LEFT JOIN releases_hist r ON ch.type = 'r' AND r.chid = ch.id
- LEFT JOIN chars_hist c ON ch.type = 'c' AND c.chid = ch.id
- LEFT JOIN staff_hist s ON ch.type = 's' AND s.chid = ch.id
- LEFT JOIN staff_alias_hist sa ON ch.type = 's' AND sa.chid = ch.id AND s.aid = sa.aid
- WHERE ch.id IN}, $l;
-
- Div class => 'card card--white card--no-separators mb-5', sub {
- Div class => 'card__header', sub {
- Div class => 'card__title', 'Recent database contributions';
- };
- Table class => 'table table--responsive-single-sm fs-medium', sub {
- Thead sub {
- Tr sub {
- Th width => '15%', 'Date';
- Th width => '10%', 'Rev.';
- Th width => '75%', 'Entry';
- };
- };
- Tbody sub {
- for my $i (@$l) {
- my $id = "$i->{type}$i->{itemid}.$i->{rev}";
- Tr sub {
- Td class => 'tabular-nums muted', date_display $i->{added};
- Td sub {
- A href => "/$id", $id;
- };
- Td sub {
- A href => "/$id", $i->{title};
- };
- }
- }
- }
- };
- Div class => 'card__section fs-medium', sub {
- A href => "/u$u->{id}/hist", 'View all';
- }
- };
-}
-
-
-TUWF::get qr{/$UID_RE}, sub {
- my $uid = tuwf->capture('id');
- my $u = tuwf->dbRowi(q{
- SELECT u.id, u.username, EXTRACT('epoch' FROM u.registered) AS registered, u.c_votes, u.c_changes, u.c_tags, hd.value AS hide_list
- FROM users u
- LEFT JOIN users_prefs hd ON hd.uid = u.id AND hd.key = 'hide_list'
- WHERE u.id =}, \$uid
- );
- return tuwf->resNotFound if !$u->{id};
-
- Framework
- title => lcfirst($u->{username}),
- index => 0,
- single_col => 1,
- top => sub {
- Div class => 'col-md', sub {
- EntryEdit u => $u;
- Div class => 'detail-page-title', ucfirst $u->{username};
- TopNav details => $u;
- }
- },
- sub {
- Stats $u;
- List $u;
- Edits $u;
- };
-};
-
-1;
diff --git a/lib/VN3/User/RegReset.pm b/lib/VN3/User/RegReset.pm
deleted file mode 100644
index ed815547..00000000
--- a/lib/VN3/User/RegReset.pm
+++ /dev/null
@@ -1,137 +0,0 @@
-# User registration and password reset. These functions share some common code.
-package VN3::User::RegReset;
-
-use VN3::Prelude;
-
-
-TUWF::get '/u/newpass' => sub {
- return tuwf->resRedirect('/', 'temp') if auth;
- Framework title => 'Password reset', center => 1, sub {
- Div 'data-elm-module' => 'User.PassReset', '';
- };
-};
-
-
-my $elm_BadEmail = elm_api 'BadEmail';
-my $elm_BadPass = elm_api 'BadPass';
-my $elm_Bot = elm_api 'Bot';
-my $elm_Taken = elm_api 'Taken';
-my $elm_DoubleEmail = elm_api 'DoubleEmail';
-my $elm_DoubleIP = elm_api 'DoubleIP';
-
-
-json_api '/u/newpass', {
- email => { email => 1 },
-}, sub {
- my $data = shift;
-
- my($id, $token) = auth->resetpass($data->{email});
- return $elm_BadEmail->() if !$id;
-
- my $name = tuwf->dbVali('SELECT username FROM users WHERE id =', \$id);
- my $body = sprintf
- "Hello %s,"
- ."\n\n"
- ."Your VNDB.org login has been disabled, you can now set a new password by following the link below:"
- ."\n\n"
- ."%s"
- ."\n\n"
- ."Now don't forget your password again! :-)"
- ."\n\n"
- ."vndb.org",
- $name, tuwf->reqBaseURI()."/u$id/setpass/$token";
-
- tuwf->mail($body,
- To => $data->{email},
- From => 'VNDB <noreply@vndb.org>',
- Subject => "Password reset for $name",
- );
- $elm_Success->();
-};
-
-
-my $reset_url = qr{/$UID_RE/setpass/(?<token>[a-f0-9]{40})};
-
-TUWF::get $reset_url, sub {
- return tuwf->resRedirect('/', 'temp') if auth;
-
- my $id = tuwf->capture('id');
- my $token = tuwf->capture('token');
- my $name = tuwf->dbVali('SELECT username FROM users WHERE id =', \$id);
-
- return tuwf->resNotFound if !$name || !auth->isvalidtoken($id, $token);
-
- Framework title => 'Set password', center => 1, sub {
- Div 'data-elm-module' => 'User.PassSet', 'data-elm-flags' => '"'.tuwf->reqPath().'"', '';
- };
-};
-
-
-json_api $reset_url, {
- pass => { password => 1 },
-}, sub {
- my $data = shift;
- my $id = tuwf->capture('id');
- my $token = tuwf->capture('token');
-
- return $elm_BadPass->() if tuwf->isUnsafePass($data->{pass});
- die "Invalid reset token" if !auth->setpass($id, $token, undef, $data->{pass});
- tuwf->dbExeci('UPDATE users SET email_confirmed = true WHERE id =', \$id);
- $elm_Success->()
-};
-
-
-TUWF::get '/u/register', sub {
- return tuwf->resRedirect('/', 'temp') if auth;
- Framework title => 'Register', center => 1, sub {
- Div 'data-elm-module' => 'User.Register', '';
- };
-};
-
-
-json_api '/u/register', {
- username => { username => 1 },
- email => { email => 1 },
- vns => { int => 1 },
-}, sub {
- my $data = shift;
-
- my $num = tuwf->dbVali("SELECT count FROM stats_cache WHERE section = 'vn'");
- return $elm_Bot->() if $data->{vns} < $num*0.995 || $data->{vns} > $num*1.005;
- return $elm_Taken->() if tuwf->dbVali('SELECT 1 FROM users WHERE username =', \$data->{username});
- return $elm_DoubleEmail->() if tuwf->dbVali(select => sql_func user_emailexists => \$data->{email});
-
- my $ip = tuwf->reqIP;
- return $elm_DoubleIP->() if tuwf->dbVali(
- q{SELECT 1 FROM users WHERE registered >= NOW()-'1 day'::interval AND ip <<},
- $ip =~ /:/ ? \"$ip/48" : \"$ip/30"
- );
-
- my $id = tuwf->dbVali('INSERT INTO users', {
- username => $data->{username},
- mail => $data->{email},
- ip => $ip,
- }, 'RETURNING id');
- my(undef, $token) = auth->resetpass($data->{email});
-
- my $body = sprintf
- "Hello %s,"
- ."\n\n"
- ."Someone has registered an account on VNDB.org with your email address. To confirm your registration, follow the link below."
- ."\n\n"
- ."%s"
- ."\n\n"
- ."If you don't remember creating an account on VNDB.org recently, please ignore this e-mail."
- ."\n\n"
- ."vndb.org",
- $data->{username}, tuwf->reqBaseURI()."/u$id/setpass/$token";
-
- tuwf->mail($body,
- To => $data->{email},
- From => 'VNDB <noreply@vndb.org>',
- Subject => "Confirm registration for $data->{username}",
- );
- $elm_Success->()
-};
-
-1;
diff --git a/lib/VN3/User/Settings.pm b/lib/VN3/User/Settings.pm
deleted file mode 100644
index a63de232..00000000
--- a/lib/VN3/User/Settings.pm
+++ /dev/null
@@ -1,98 +0,0 @@
-package VN3::User::Settings;
-
-use VN3::Prelude;
-
-
-my $FORM = {
- username => { username => 1 },
- mail => { email => 1 },
- perm => { uint => 1, func => sub { ($_[0] & ~auth->allPerms) == 0 } },
- ign_votes => { anybool => 1 },
- hide_list => { anybool => 1 },
- show_nsfw => { anybool => 1 },
- traits_sexual => { anybool => 1 },
- tags_all => { anybool => 1 },
- tags_cont => { anybool => 1 },
- tags_ero => { anybool => 1 },
- tags_tech => { anybool => 1 },
- spoilers => { uint => 1, range => [ 0, 2 ] },
-
- password => { _when => 'in', required => 0, type => 'hash', keys => {
- old => { password => 1 },
- new => { password => 1 }
- } },
-
- id => { _when => 'out', uint => 1 },
- authmod => { _when => 'out', anybool => 1 },
-};
-
-my $FORM_OUT = form_compile out => $FORM;
-my $FORM_IN = form_compile in => $FORM;
-
-elm_form UserEdit => $FORM_OUT, $FORM_IN;
-
-my $elm_BadPass = elm_api 'BadPass';
-my $elm_BadLogin = elm_api 'BadLogin';
-
-TUWF::get qr{/$UID_RE/edit}, sub {
- my $u = tuwf->dbRowi('SELECT id, username, perm, ign_votes FROM users WHERE id =', \tuwf->capture('id'));
-
- return tuwf->resNotFound if !can_edit u => $u;
-
- $u->{mail} = tuwf->dbVali(select => sql_func user_getmail => \$u->{id}, \auth->uid, sql_fromhex auth->token);
- $u->{authmod} = auth->permUsermod;
-
- # Let's not disclose this (though it's not hard to find out through other means)
- if(!auth->permUsermod) {
- $u->{ign_votes} = 0;
- $u->{perm} = auth->defaultPerms;
- }
-
- my $prefs = { map +($_->{key}, $_->{value}), @{ tuwf->dbAlli('SELECT key, value FROM users_prefs WHERE uid =', \$u->{id}) }};
- $u->{$_} = $prefs->{$_}||'' for qw/hide_list show_nsfw traits_sexual tags_all spoilers/;
- $u->{spoilers} ||= 0;
- $u->{"tags_$_"} = (($prefs->{tags_cat}||'cont,tech') =~ /$_/) for qw/cont ero tech/;
-
- my $title = $u->{id} == auth->uid ? 'My Preferences' : "Edit $u->{username}";
- Framework title => $title, noindex => 1, narrow => 1, sub {
- FullPageForm module => 'User.Settings', data => $u, schema => $FORM_OUT;
- };
-};
-
-
-json_api qr{/$UID_RE/edit}, $FORM_IN, sub {
- my $data = shift;
- my $id = tuwf->capture('id');
-
- return $elm_Unauth->() if !can_edit u => { id => $id };
-
- if(auth->permUsermod) {
- tuwf->dbExeci(update => users => set => {
- username => $data->{username},
- ign_votes => $data->{ign_votes},
- email_confirmed => 1,
- }, where => { id => $id });
- tuwf->dbExeci(select => sql_func user_setperm => \$id, \auth->uid, sql_fromhex(auth->token), \$data->{perm});
- }
-
- if($data->{password}) {
- return $elm_BadPass->() if tuwf->isUnsafePass($data->{password}{new});
-
- if(auth->uid == $id) {
- return $elm_BadLogin->() if !auth->setpass($id, undef, $data->{password}{old}, $data->{password}{new});
- } else {
- tuwf->dbExeci(select => sql_func user_admin_setpass => \$id, \auth->uid,
- sql_fromhex(auth->token), sql_fromhex auth->_preparepass($data->{password}{new})
- );
- }
- }
-
- tuwf->dbExeci(select => sql_func user_setmail => \$id, \auth->uid, sql_fromhex(auth->token), \$data->{mail});
-
- auth->prefSet($_, $data->{$_}, $id) for qw/hide_list show_nsfw traits_sexual tags_all spoilers/;
- auth->prefSet(tags_cat => join(',', map $data->{"tags_$_"} ? $_ : (), qw/cont ero tech/), $id);
-
- $elm_Success->();
-};
-
-1;
diff --git a/lib/VN3/User/VNList.pm b/lib/VN3/User/VNList.pm
deleted file mode 100644
index 922f81d6..00000000
--- a/lib/VN3/User/VNList.pm
+++ /dev/null
@@ -1,325 +0,0 @@
-package VN3::User::VNList;
-
-use POSIX 'ceil';
-use VN3::Prelude;
-use VN3::User::Lib;
-
-
-sub mkurl {
- my $opt = shift;
- $opt = { %$opt, @_ };
- delete $opt->{t} if $opt->{t} == -1;
- delete $opt->{g} if !$opt->{g};
- '?'.join ';', map "$_=$opt->{$_}", sort keys %$opt;
-}
-
-
-sub SideBar {
- my $opt = shift;
-
- Div class => 'fixed-size-left-sidebar-xl', sub {
- Div class => 'vertical-selector-label', 'Status';
- Div class => 'vertical-selector', sub {
- for (-1, keys %VNLIST_STATUS) {
- A href => mkurl($opt, t => $_, p => 1), mkclass(
- 'vertical-selector__item' => 1,
- 'vertical-selector__item--active' => $_ == $opt->{t}
- ), $_ < 0 ? 'All' : $VNLIST_STATUS{$_};
- }
- };
- };
-}
-
-
-sub NextPrev {
- my($opt, $count) = @_;
- my $numpage = ceil($count/50);
-
- Div class => 'd-lg-flex jc-between align-items-center', sub {
- Div class => 'd-flex align-items-center', '';
- Div class => 'd-block d-lg-none mb-2', '';
- Div class => 'd-flex jc-right align-items-center', sub {
- A href => mkurl($opt, p => $opt->{p}-1), mkclass(btn => 1, 'btn--disabled' => $opt->{p} <= 1), '< Prev';
- Div class => 'mx-3 semi-muted', sprintf 'page %d of %d', $opt->{p}, $numpage;
- A href => mkurl($opt, p => $opt->{p}+1), mkclass(btn => 1, 'btn--disabled' => $opt->{p} >= $numpage), 'Next >';
- };
- };
-}
-
-
-sub EditDropDown {
- my($u, $opt, $item) = @_;
- return if $u->{id} != (auth->uid||0);
- Div 'data-elm-module' => 'UVNList.Options',
- 'data-elm-flags' => JSON::XS->new->encode({uid => $u->{id}, item => $item}),
- '';
-}
-
-
-sub VNTable {
- my($u, $lst, $opt) = @_;
-
- my $SortHeader = sub {
- my($id, $label) = @_;
- my $isasc = $opt->{s} eq $id && $opt->{o} eq 'a';
- A mkclass(
- 'table-header' => 1,
- 'with-sort-icon' => 1,
- 'with-sort-icon--down' => !$isasc,
- 'with-sort-icon--up' => $isasc,
- 'with-sort-icon--active' => $opt->{s} eq $id,
- ), href => mkurl($opt, p => 1, s => $id, o => $isasc ? 'd' : 'a'), $label;
- };
-
- Table class => 'table table--responsive-single-sm fs-medium vn-list', sub {
- Thead sub {
- Tr sub {
- Th width => '15%', class => 'th--nopad', sub { $SortHeader->(date => 'Date' ) };
- Th width => '40%', class => 'th--nopad', sub { $SortHeader->(title => 'Title') };
- Th width => '10%', class => 'th--nopad', sub { $SortHeader->(vote => 'Vote' ) };
- Th width => '13%', 'Status';
- Th width => '7.33%', '';
- Th width => '7.33%', '';
- Th width => '7.33%', '';
- };
- };
- Tbody sub {
- for my $l (@$lst) {
- Tr sub {
- Td class => 'tabular-nums muted', date_display $l->{date};
- Td sub {
- A href => "/v$l->{id}", title => $l->{original}||$l->{title}, $l->{title};
- };
-
- if($u->{id} == (auth->uid||0)) {
- Td class => 'table-edit-overlay-base', sub {
- Div 'data-elm-module' => 'UVNList.Vote',
- 'data-elm-flags' => JSON::XS->new->encode({uid => int $u->{id}, vid => int $l->{id}, vote => ''.vote_display $l->{vote}}),
- vote_display $l->{vote};
- };
- Td class => 'table-edit-overlay-base', sub {
- Div 'data-elm-module' => 'UVNList.Status',
- 'data-elm-flags' => JSON::XS->new->encode({uid => int $u->{id}, vid => int $l->{id}, status => int $l->{status}||0}),
- $VNLIST_STATUS{$l->{status}||0};
- };
- } else {
- Td vote_display $l->{vote};
- Td $VNLIST_STATUS{$l->{status}||0};
- }
-
- # Release info
- Td sub {
- A href => 'javascript:;', class => 'vn-list__expand-releases', sub {
- Span class => 'expand-arrow mr-2', '';
- Txt sprintf '%d/%d', (scalar grep $_->{status}==2, @{$l->{rel}}), scalar @{$l->{rel}};
- } if @{$l->{rel}};
- };
-
- # Notes
- Td sub {
- # TODO: vn-list__expand-comment--empty for 'add comment' things
- A href => 'javascript:;', class => 'vn-list__expand-comment', sub {
- Span class => 'expand-arrow mr-2', '';
- Img class => 'svg-icon', src => tuwf->conf->{url_static}.'/v3/heavy/comment.svg';
- } if $l->{notes};
- };
-
- Td sub { EditDropDown $u, $opt, $l };
- };
-
- # Release info
- Tr class => 'vn-list__releases-row d-none', sub {
- Td colspan => '6', sub {
- Div class => 'vn-list__releases', sub {
- Table class => 'table table--responsive-single-sm ml-3', sub {
- Tbody sub {
- for my $r (@{$l->{rel}}) {
- Tr sub {
- Td width => '15%', class => 'tabular-nums muted pl-0', date_display $r->{date};
- Td width => '50%', sub {
- A href => "/v$r->{rid}", title => $r->{original}||$r->{title}, $r->{title};
- };
- # TODO: Editabe
- Td width => '20%', $RLIST_STATUS{$l->{status}};
- Td width => '15%', ''; # TODO: Edit menu
- }
- }
- }
- }
- }
- }
- } if @{$l->{rel}};
-
- # Notes
- Tr class => 'vn-list__comment-row d-none', sub {
- Td colspan => '6', sub {
- # TODO: Editable
- Div class => 'vn-list__comment ml-3', $l->{notes};
- }
- } if $l->{notes};
- };
- };
- };
-}
-
-
-sub VNGrid {
- my($u, $lst, $opt) = @_;
-
- Div class => 'vn-grid mb-4', sub {
- for my $l (@$lst) {
- Div class => 'vn-grid__item', sub {
- # TODO: NSFW hiding? What about missing images?
- Div class => 'vn-grid__item-bg', style => sprintf("background-image: url('%s')", tuwf->imgurl(cv => $l->{image})), '';
- Div class => 'vn-grid__item-overlay', sub {
- A href => 'javascript:;', class => 'vn-grid__item-link', ''; # TODO: Open modal on click
- Div class => 'vn-grid__item-top', sub {
- EditDropDown $u, $opt, $l;
- Div class => 'vn-grid__item-rating', sub {
- Img class => 'svg-icon', src => tuwf->conf->{url_static}.'/v3/heavy/comment.svg' if $l->{notes};
- Lit ' ';
- Txt vote_display $l->{vote};
- }
- };
- Div class => 'vn-grid__item-name', $l->{title};
- }
- }
- }
- }
-}
-
-
-sub List {
- my($u, $opt) = @_;
-
- my $lst = tuwf->dbAlli(q{
- SELECT v.id, v.title, v.original, vl.status, vl.notes, vo.vote, v.image, },
- sql_totime('LEAST(vl.added, vo.date)'), q{AS date,
- count(*) OVER() AS full_count
- FROM vn v
- LEFT JOIN votes vo ON vo.vid = v.id AND vo.uid =}, \$u->{id}, q{
- LEFT JOIN vnlists vl ON vl.vid = v.id AND vl.uid =}, \$u->{id}, q{
- WHERE }, sql_and(
- 'vo.vid IS NOT NULL OR vl.vid IS NOT NULL',
- $opt->{t} >= 1 ? sql('vl.status =', \$opt->{t}) : $opt->{t} == 0 ? 'vl.status = 0 OR vl.status IS NULL' : ()
- ),
- 'ORDER BY', {
- title => 'v.title',
- date => 'LEAST(vl.added, vo.date)',
- vote => 'vo.vote',
- }->{$opt->{s}},
- $opt->{o} eq 'a' ? 'ASC' : 'DESC',
- 'NULLS LAST',
- 'LIMIT', \50,
- 'OFFSET', \(($opt->{p}-1)*50)
- );
- my $count = @$lst ? $lst->[0]{full_count} : 0;
- delete $_->{full_count} for @$lst;
-
- enrich_list rel => id => vid => sub { sql q{
- SELECT rv.vid, rl.rid, rl.status, r.title, r.original, }, sql_totime('rl.added'), q{ AS date
- FROM rlists rl
- JOIN releases r ON r.id = rl.rid
- JOIN releases_vn rv ON rv.id = r.id
- WHERE rl.uid =}, \$u->{id}, q{AND rv.vid IN}, $_[0]
- }, $lst;
-
- Div class => 'col-md', sub {
- Div class => 'card card--white card--no-separators mb-5', sub {
- Div class => 'card__header', sub {
- Div class => 'card__title', 'List';
- Debug $lst;
- Div class => 'card__header-buttons', sub {
- Div class => 'btn-group', sub {
- A href => mkurl($opt, g => 0), mkclass(btn => 1, active => !$opt->{g}, 'js-show-vn-list' => 1), \&ListIcon;
- A href => mkurl($opt, g => 1), mkclass(btn => 1, active => $opt->{g}, 'js-show-vn-grid' => 1), \&GridIcon;
- };
- };
- };
-
- VNTable $u, $lst, $opt unless $opt->{g};
- Div class => 'card__body fs-medium', sub {
- VNGrid $u, $lst, $opt if $opt->{g};
- NextPrev $opt, $count;
- };
- }
- };
-}
-
-
-TUWF::get qr{/$UID_RE/list}, sub {
- my $uid = tuwf->capture('id');
- my $u = tuwf->dbRowi(q{
- SELECT u.id, u.username, hd.value AS hide_list
- FROM users u
- LEFT JOIN users_prefs hd ON hd.uid = u.id AND hd.key = 'hide_list'
- WHERE u.id =}, \$uid
- );
- return tuwf->resNotFound if !$u->{id} || !show_list $u;
-
- my $opt = tuwf->validate(get =>
- t => { vnlist_status => 1, required => 0, default => -1 }, # status
- p => { page => 1 }, # page
- o => { enum => ['d','a'], required => 0, default => 'a' }, # order (asc/desc)
- s => { enum => ['title', 'date', 'vote'], required => 0, default => 'title' }, # sort column
- g => { anybool => 1 }, # grid
- )->data;
-
- Framework
- title => $u->{username},
- index => 0,
- top => sub {
- Div class => 'col-md', sub {
- Div class => 'detail-page-title', ucfirst $u->{username};
- TopNav list => $u;
- }
- },
- sub {
- Div class => 'row', sub {
- SideBar $opt;
- List $u, $opt;
- };
- };
-};
-
-
-json_api '/u/setvote', {
- uid => { id => 1 },
- vid => { id => 1 },
- vote => { vnvote => 1 }
-}, sub {
- my $data = shift;
- return $elm_Unauth->() if (auth->uid||0) != $data->{uid};
-
- tuwf->dbExeci(
- 'DELETE FROM votes WHERE',
- { vid => $data->{vid}, uid => $data->{uid} }
- ) if !$data->{vote};
-
- tuwf->dbExeci(
- 'INSERT INTO votes',
- { vid => $data->{vid}, uid => $data->{uid}, vote => $data->{vote} },
- 'ON CONFLICT (vid, uid) DO UPDATE SET',
- { vote => $data->{vote} }
- ) if $data->{vote};
-
- $elm_Success->()
-};
-
-
-json_api '/u/setvnstatus', {
- uid => { id => 1 },
- vid => { id => 1 },
- status => { vnlist_status => 1 }
-}, sub {
- my $data = shift;
- return $elm_Unauth->() if (auth->uid||0) != $data->{uid};
-
- tuwf->dbExeci(
- 'INSERT INTO vnlists',
- { vid => $data->{vid}, uid => $data->{uid}, status => $data->{status} },
- 'ON CONFLICT (vid, uid) DO UPDATE SET',
- { status => $data->{status} }
- );
- $elm_Success->();
-};