summaryrefslogtreecommitdiff
path: root/lib/VNDB/Handler/Users.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNDB/Handler/Users.pm')
-rw-r--r--lib/VNDB/Handler/Users.pm467
1 files changed, 467 insertions, 0 deletions
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
new file mode 100644
index 00000000..088e5a31
--- /dev/null
+++ b/lib/VNDB/Handler/Users.pm
@@ -0,0 +1,467 @@
+
+package VNDB::Handler::Users;
+
+use strict;
+use warnings;
+use YAWF ':html';
+use Digest::MD5 'md5_hex';
+use VNDB::Func;
+
+
+YAWF::register(
+ qr{u([1-9]\d*)} => \&userpage,
+ qr{u/login} => \&login,
+ qr{u/logout} => \&logout,
+ qr{u/newpass} => \&newpass,
+ qr{u/newpass/sent} => \&newpass_sent,
+ qr{u/register} => \&register,
+ qr{u([1-9]\d*)/edit} => \&edit,
+ qr{u([1-9]\d*)/del(/[od])?} => \&delete,
+ qr{u/(all|[0a-z])} => \&list,
+);
+
+
+sub userpage {
+ my($self, $uid) = @_;
+
+ my $u = $self->dbUserGet(uid => $uid, what => 'stats')->[0];
+ return 404 if !$u->{id};
+
+ my $votes = $u->{c_votes} && $self->dbVoteStats(uid => $uid);
+
+ $self->htmlHeader(title => ucfirst($u->{username})."'s profile");
+ $self->htmlMainTabs('u', $u);
+ div class => 'mainbox userpage';
+ h1 ucfirst($u->{username})."'s profile";
+
+ table;
+ Tr;
+ td class => 'key', ' ';
+ td ' ';
+ end;
+ my $i = 0;
+
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Username';
+ td;
+ txt ucfirst($u->{username}).' (';
+ a href => "/u$uid", "u$uid";
+ txt ')';
+ end;
+ end;
+
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Registered';
+ td date $u->{registered};
+ end;
+
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Edits';
+ td;
+ if($u->{c_changes}) {
+ a href => "/u$uid/hist", $u->{c_changes};
+ } else {
+ txt '-';
+ }
+ end;
+ end;
+
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Votes';
+ td;
+ if(!$u->{show_list}) {
+ txt 'hidden';
+ } elsif($votes) {
+ my($total, $count) = (0, 0);
+ for (1..@$votes) {
+ $total += $_*$votes->[$_-1];
+ $count += $votes->[$_-1];
+ }
+ a href => "/u$uid/list?v=1", $count;
+ txt sprintf ' (%.2f average)', $total/$count;
+ } else {
+ txt '-';
+ }
+ end;
+ end;
+
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'List stats';
+ td !$u->{show_list} ? 'hidden' :
+ sprintf '%d releases of %d visual novels', $u->{releasecount}, $u->{vncount};
+ end;
+
+ Tr ++$i % 2 ? (class => 'odd') : ();
+ td 'Forum stats';
+ td sprintf '%d posts, %d new threads', $u->{postcount}, $u->{threadcount};
+ end;
+ end;
+ end;
+
+ if($u->{show_list} && $votes) {
+ div class => 'mainbox';
+ h1 'Vote statistics';
+ $self->htmlVoteStats(u => $u, $votes);
+ end;
+ }
+
+ if($u->{c_changes}) {
+ my $list = $self->dbRevisionGet(what => 'item user', uid => $uid, results => 5);
+ h1 class => 'boxtitle';
+ a href => "/u$uid/hist", 'Recent changes';
+ end;
+ $self->htmlHistory($list, { p => 1 }, 0, "/u$uid/hist");
+ }
+ $self->htmlFooter;
+}
+
+
+sub login {
+ my $self = shift;
+
+ return $self->resRedirect('/') if $self->authInfo->{id};
+
+ my $frm;
+ if($self->reqMethod eq 'POST') {
+ $frm = $self->formValidate(
+ { name => 'usrname', required => 1, minlength => 2, maxlength => 15, template => 'pname' },
+ { name => 'usrpass', required => 1, minlength => 4, maxlength => 15, template => 'asciiprint' },
+ );
+
+ (my $ref = $self->reqHeader('Referer')||'/') =~ s/^\Q$self->{url}//;
+ return if !$frm->{_err} && $self->authLogin($frm->{usrname}, $frm->{usrpass}, $ref);
+ $frm->{_err} = [ 'login_failed' ] if !$frm->{_err};
+ }
+
+ $self->htmlHeader(title => 'Login', noindex => 1);
+ $self->htmlForm({ frm => $frm, action => '/u/login' }, Login => [
+ [ input => name => 'Username', short => 'usrname' ],
+ [ static => content => '<a href="/u/register">No account yet?</a>' ],
+ [ passwd => name => 'Password', short => 'usrpass' ],
+ [ static => content => '<a href="/u/newpass">Forgot your password?</a>' ],
+ ]);
+ $self->htmlFooter;
+}
+
+
+sub logout {
+ shift->authLogout;
+}
+
+
+sub newpass {
+ my $self = shift;
+
+ return $self->resRedirect('/') if $self->authInfo->{id};
+
+ my($frm, $u);
+ if($self->reqMethod eq 'POST') {
+ $frm = $self->formValidate(
+ { name => 'mail', required => 1, template => 'mail' },
+ );
+ if(!$frm->{_err}) {
+ $u = $self->dbUserGet(mail => $frm->{mail})->[0];
+ $frm->{_err} = [ 'nomail' ] if !$u || !$u->{id};
+ }
+ if(!$frm->{_err}) {
+ my @chars = ( 'A'..'Z', 'a'..'z', 0..9 );
+ my $pass = join '', map $chars[int rand $#chars+1], 0..8;
+ $self->dbUserEdit($u->{id}, passwd => md5_hex($pass));
+ $self->mail(
+ sprintf(join('', <DATA>), $u->{username}, $pass),
+ To => $u->{mail},
+ From => 'VNDB <noreply@vndb.org>',
+ Subject => 'New password for '.$u->{username}
+ );
+ return $self->resRedirect('/u/newpass/sent', 'post');
+ }
+ }
+
+ $self->htmlHeader(title => 'Forgot Password', noindex => 1);
+ div class => 'mainbox';
+ h1 'Forgot Password';
+ p "Forgot your password and can't login to VNDB anymore?\n"
+ ."Don't worry! Just give us the email address you used to register on VNDB,\n"
+ ."and we'll send you a new password within a few minutes!";
+ end;
+ $self->htmlForm({ frm => $frm, action => '/u/newpass' }, 'Reset Password' => [
+ [ input => name => 'Email', short => 'mail' ],
+ ]);
+ $self->htmlFooter;
+}
+
+
+sub newpass_sent {
+ my $self = shift;
+ return $self->resRedirect('/') if $self->authInfo->{id};
+ $self->htmlHeader(title => 'New Password', noindex => 1);
+ div class => 'mainbox';
+ h1 'New Password';
+ div class => 'notice';
+ h2 'Password Reset';
+ p;
+ txt "Your password has been reset and your new password should reach your mailbox in a few minutes.\n"
+ ."You can always change your password again after logging in.\n\n";
+ lit '<a href="/u/login">Login</a> - <a href="/">Home</a>';
+ end;
+ end;
+ end;
+ $self->htmlFooter;
+}
+
+
+sub register {
+ my $self = shift;
+ return $self->resRedirect('/') if $self->authInfo->{id};
+
+ my $frm;
+ if($self->reqMethod eq 'POST') {
+ $frm = $self->formValidate(
+ { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
+ { name => 'mail', template => 'mail' },
+ { name => 'usrpass', minlength => 4, maxlength => 15, template => 'asciiprint' },
+ { name => 'usrpass2', minlength => 4, maxlength => 15, template => 'asciiprint' },
+ );
+ push @{$frm->{_err}}, 'passmatch' if $frm->{usrpass} ne $frm->{usrpass2};
+ push @{$frm->{_err}}, 'usrexists' if $frm->{usrname} eq 'anonymous' || !$frm->{_err} && $self->dbUserGet(username => $frm->{usrname})->[0]{id};
+ push @{$frm->{_err}}, 'mailexists' if !$frm->{_err} && $self->dbUserGet(mail => $frm->{mail})->[0]{id};
+
+ if(!$frm->{_err}) {
+ $self->dbUserAdd($frm->{usrname}, md5_hex($frm->{usrpass}), $frm->{mail});
+ return $self->authLogin($frm->{usrname}, $frm->{usrpass}, '/');
+ }
+ }
+
+ $self->htmlHeader(title => 'Create an Account', noindex => 1);
+ div class => 'mainbox';
+ h1 'Create an Account';
+ h2 'Why should I register?';
+ p 'Creating an account is completely painless, the only thing we need to know is your prefered username '
+ .'and a password. You can just use any email address that isn\'t yours, as we don\'t even confirm '
+ .'that the address you gave us is really yours. Keep in mind, however, that you would probably '
+ .'want to remember your password if you do choose to give us an invalid email address...';
+
+ p 'Anyway, having an account here has a few advantages over being just a regular visitor:';
+ ul;
+ li 'You can contribute to the database by editing any entries and adding new ones';
+ li 'Keep track of all visual novels and releases you have, you\'d like to play, are playing, or have finished playing';
+ li 'Vote on the visual novels you liked or disliked';
+ li 'Contribute to the discussions on the boards';
+ li 'And boast about the fact that you have an account on the best visual novel database in the world!';
+ end;
+ end;
+
+ $self->htmlForm({ frm => $frm, action => '/u/register' }, 'New Account' => [
+ [ input => short => 'usrname', name => 'Username' ],
+ [ static => content => 'Requested username. Must be lowercase and can only consist of alphanumeric characters.' ],
+ [ input => short => 'mail', name => 'Email' ],
+ [ static => content => 'Your email address will only be used in case you lose your password. We will never send'
+ .' spam or newsletters unless you explicitly ask us for it.<br /><br />' ],
+ [ passwd => short => 'usrpass', name => 'Password' ],
+ [ passwd => short => 'usrpass2', name => 'Confirm pass.' ],
+ ]);
+ $self->htmlFooter;
+}
+
+
+sub edit {
+ my($self, $uid) = @_;
+
+ # are we allowed to edit this user?
+ return $self->htmlDenied if !$self->authInfo->{id} || $self->authInfo->{id} != $uid && !$self->authCan('usermod');
+
+ # fetch user info (cached if uid == loggedin uid)
+ my $u = $self->authInfo->{id} == $uid ? $self->authInfo : $self->dbUserGet(uid => $uid)->[0];
+ return 404 if !$u->{id};
+
+ # check POST data
+ my $frm;
+ if($self->reqMethod eq 'POST') {
+ $frm = $self->formValidate(
+ $self->authCan('usermod') ? (
+ { name => 'usrname', template => 'pname', minlength => 2, maxlength => 15 },
+ { name => 'rank', enum => [ 1..$#{$self->{user_ranks}} ] },
+ ) : (),
+ { name => 'mail', template => 'mail' },
+ { name => 'usrpass', required => 0, minlength => 4, maxlength => 15, template => 'asciiprint' },
+ { name => 'usrpass2', required => 0, minlength => 4, maxlength => 15, template => 'asciiprint' },
+ { name => 'flags_list', required => 0, default => 0 },
+ { name => 'flags_nsfw', required => 0, default => 0 },
+ );
+ push @{$frm->{_err}}, 'passmatch' if ($frm->{usrpass} || $frm->{usrpass2}) && $frm->{usrpass} ne $frm->{usrpass2};
+ if(!$frm->{_err}) {
+ my %o;
+ $o{username} = $frm->{usrname} if $frm->{usrname};
+ $o{rank} = $frm->{rank} if $frm->{rank};
+ $o{mail} = $frm->{mail};
+ $o{passwd} = md5_hex($frm->{usrpass}) if $frm->{usrpass};
+ $o{show_list} = $frm->{flags_list} ? 1 : 0;
+ $o{show_nsfw} = $frm->{flags_nsfw} ? 1 : 0;
+ $self->dbUserEdit($uid, %o);
+ return $self->resRedirect("/u$uid/edit?d=1", 'post') if $uid != $self->authInfo->{id} || !$frm->{usrpass};
+ return $self->authLogin($frm->{usrname}||$u->{username}, $frm->{usrpass}, "/u$uid/edit?d=1");
+ }
+ }
+
+ # fill out default values
+ $frm->{usrname} ||= $u->{username};
+ $frm->{rank} ||= $u->{rank};
+ $frm->{mail} ||= $u->{mail};
+ $frm->{flags_list} = $u->{show_list} if !defined $frm->{flags_list};
+ $frm->{flags_nsfw} = $u->{show_nsfw} if !defined $frm->{flags_nsfw};
+
+ # create the page
+ my $title = $self->authInfo->{id} != $uid ? "Edit $u->{username}'s Account" : 'My Account';
+ $self->htmlHeader(title => $title, noindex => 1);
+ $self->htmlMainTabs('u', $u, 'edit');
+ if($self->reqParam('d')) {
+ div class => 'mainbox';
+ h1 'Settings saved';
+ div class => 'notice';
+ p 'Settings successfully saved.';
+ end;
+ end
+ }
+ $self->htmlForm({ frm => $frm, action => "/u$uid/edit" }, $title => [
+ [ part => title => 'General Info' ],
+ $self->authCan('usermod') ? (
+ [ input => short => 'usrname', name => 'Username' ],
+ [ select => short => 'rank', name => 'Rank', options => [
+ map [ $_, $self->{user_ranks}[$_][0] ], 1..$#{$self->{user_ranks}} ] ],
+ ) : (
+ [ static => label => 'Username', content => $frm->{usrname} ],
+ ),
+ [ input => short => 'mail', name => 'Email' ],
+
+ [ part => title => 'Change Password' ],
+ [ static => content => 'Leave blank to keep your current password' ],
+ [ input => short => 'usrpass', name => 'Password' ],
+ [ passwd => short => 'usrpass2', name => 'Confirm pass.' ],
+
+ [ part => title => 'Options' ],
+ [ check => short => 'flags_list', name =>
+ qq|Allow other people to see my visual novel list (<a href="/u$uid/list">/u$uid/list</a>) |.
+ qq|and wishlist (<a href="/u$uid/wish">/u$uid/wish</a>)| ],
+ [ check => short => 'flags_nsfw', name => 'Disable warnings for images that are not safe for work.' ],
+ ]);
+ $self->htmlFooter;
+}
+
+
+sub delete {
+ my($self, $uid, $act) = @_;
+ return $self->htmlDenied if !$self->authCan('usermod');
+
+ # confirm
+ if(!$act) {
+ my $u = $self->dbUserGet(uid => $uid)->[0];
+ return 404 if !$u->{id};
+ $self->htmlHeader(title => 'Delete user', noindex => 1);
+ $self->htmlMainTabs('u', $u, 'del');
+ div class => 'mainbox';
+ div class => 'warning';
+ h2 'Delete user';
+ p;
+ lit qq|Are you sure you want to remove <a href="/u$uid">$u->{username}</a>'s account?<br /><br />|
+ .qq|<a href="/u$uid/del/o">Yes, I'm not kidding!</a>|;
+ end;
+ end;
+ end;
+ $self->htmlFooter;
+ }
+ # delete
+ elsif($act eq '/o') {
+ $self->dbUserDel($uid);
+ $self->resRedirect("/u$uid/del/d", 'post');
+ }
+ # done
+ elsif($act eq '/d') {
+ $self->htmlHeader(title => 'Delete user', noindex => 1);
+ div class => 'mainbox';
+ div class => 'notice';
+ p 'User deleted.';
+ end;
+ end;
+ $self->htmlFooter;
+ }
+}
+
+
+sub list {
+ my($self, $char) = @_;
+
+ my $f = $self->formValidate(
+ { name => 's', required => 0, default => 'username', enum => [ qw|username registered votes changes| ] },
+ { name => 'o', required => 0, default => 'a', enum => [ 'a','d' ] },
+ { name => 'p', required => 0, default => 1, template => 'int' },
+ );
+ return 404 if $f->{_err};
+
+ $self->htmlHeader(title => 'Browse users');
+
+ div class => 'mainbox';
+ h1 'Browse users';
+ p class => 'browseopts';
+ for ('all', 'a'..'z', 0) {
+ a href => "/u/$_", $_ eq $char ? (class => 'optselected') : (), $_ ? uc $_ : '#';
+ }
+ end;
+ end;
+
+ my($list, $np) = $self->dbUserGet(
+ order => ($f->{s} eq 'changes' ? 'c_' : $f->{s} eq 'votes' ? 'NOT show_list, c_' : '').$f->{s}.($f->{o} eq 'a' ? ' ASC' : ' DESC'),
+ $char ne 'all' ? (
+ firstchar => $char ) : (),
+ results => 50,
+ page => $f->{p},
+ );
+
+ $self->htmlBrowse(
+ items => $list,
+ options => $f,
+ nextpage => $np,
+ pageurl => "/u/$char?o=$f->{o};s=$f->{s}",
+ sorturl => "/u/$char",
+ header => [
+ [ 'Username', 'username' ],
+ [ 'Registered', 'registered' ],
+ [ 'Votes', 'votes' ],
+ [ 'Edits', 'changes' ],
+ ],
+ row => sub {
+ my($s, $n, $l) = @_;
+ Tr $n % 2 ? (class => 'odd') : ();
+ td class => 'tc1';
+ a href => '/u'.$l->{id}, $l->{username};
+ end;
+ td class => 'tc2', date $l->{registered};
+ td class => 'tc3';
+ lit !$l->{show_list} ? '-' : !$l->{c_votes} ? 0 :
+ qq|<a href="/u$l->{id}/list">$l->{c_votes}</a>|;
+ end;
+ td class => 'tc4';
+ lit !$l->{c_changes} ? 0 : qq|<a href="/u$l->{id}/hist">$l->{c_changes}</a>|;
+ end;
+ end;
+ },
+ );
+ $self->htmlFooter;
+}
+
+
+1;
+
+
+# Contents of the password-reset email
+__DATA__
+Hello %s,
+
+Your password has been reset, you can now login at http://vndb.org/ with the
+following information:
+
+Username: %1$s
+Password: %s
+
+Now don't forget your password again! :-)
+
+vndb.org