diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/VNDB/DB/Users.pm | 70 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 151 | ||||
-rw-r--r-- | lib/VNWeb/Prelude.pm | 7 | ||||
-rw-r--r-- | lib/VNWeb/User/Notifications.pm | 174 |
4 files changed, 178 insertions, 224 deletions
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm index a0c209c2..d253a52b 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -7,8 +7,6 @@ use Exporter 'import'; our @EXPORT = qw| dbUserGet dbUserDel - dbNotifyGet dbNotifyMarkRead dbNotifyRemove - dbThrottleGet dbThrottleSet |; @@ -88,73 +86,5 @@ sub dbUserDel { $_[0]->dbExec(q|DELETE FROM users WHERE id = ?|, $_[1]); } - -# %options->{ uid id what results page reverse } -# what: titles -sub dbNotifyGet { - my($s, %o) = @_; - $o{what} ||= ''; - $o{results} ||= 10; - $o{page} ||= 1; - - my %where = ( - 'n.uid = ?' => $o{uid}, - $o{id} ? ( - 'n.id = ?' => $o{id} ) : (), - defined($o{read}) ? ( - 'n.read !s' => $o{read} ? 'IS NOT NULL' : 'IS NULL' ) : (), - ); - - my @join = ( - $o{what} =~ /titles/ ? 'LEFT JOIN users u ON n.c_byuser = u.id' : (), - ); - - my @select = ( - qw|n.id n.ntype n.ltype n.iid n.subid|, - q|extract('epoch' from n.date) as date|, - q|extract('epoch' from n.read) as read|, - $o{what} =~ /titles/ ? ('n.c_title', VNWeb::DB::sql_user()) : (), - ); - - my($r, $np) = $s->dbPage(\%o, q| - SELECT !s - FROM notifications n - !s - !W - ORDER BY n.id !s - |, join(', ', @select), join(' ', @join), \%where, $o{reverse} ? 'DESC' : 'ASC'); - return wantarray ? ($r, $np) : $r; -} - - -# ids -sub dbNotifyMarkRead { - my $s = shift; - $s->dbExec('UPDATE notifications SET read = NOW() WHERE id IN(!l)', \@_); -} - - -# ids -sub dbNotifyRemove { - my $s = shift; - $s->dbExec('DELETE FROM notifications WHERE id IN(!l)', \@_); -} - - -# ip -sub dbThrottleGet { - my $s = shift; - my $t = $s->dbRow("SELECT extract('epoch' from timeout) as timeout FROM login_throttle WHERE ip = ?", shift)->{timeout}; - return $t && $t >= time ? $t : time; -} - -# ip, timeout -sub dbThrottleSet { - my($s, $ip, $timeout) = @_; - !$timeout ? $s->dbExec('DELETE FROM login_throttle WHERE ip = ?', $ip) - : $s->dbExec('UPDATE login_throttle SET timeout = to_timestamp(?) WHERE ip = ?', $timeout, $ip) - || $s->dbExec('INSERT INTO login_throttle (ip, timeout) VALUES (?, to_timestamp(?))', $ip, $timeout); -} - 1; diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index e10fcaa5..933825e7 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -15,8 +15,6 @@ TUWF::register( qr{u([1-9]\d*)/posts} => \&posts, qr{u([1-9]\d*)/del(/[od])?} => \&delete, qr{u/(all|[0a-z])} => \&list, - qr{u([1-9]\d*)/notifies} => \¬ifies, - qr{u([1-9]\d*)/notify/([1-9]\d*)} => \&readnotify, ); @@ -188,154 +186,5 @@ sub list { } -sub notifies { - my($self, $uid) = @_; - - my $u = $self->dbUserGet(uid => $uid)->[0]; - return $self->htmlDenied if !$u->{id} || $uid != $self->authInfo->{id}; - - my $f = $self->formValidate( - { get => 'p', required => 0, default => 1, template => 'page' }, - { get => 'r', required => 0, default => 0, enum => [0,1] }, - ); - return $self->resNotFound if $f->{_err}; - - # changing the notification settings - my $saved; - if($self->reqMethod() eq 'POST' && $self->reqPost('set')) { - return if !$self->authCheckCode; - my $frm = $self->formValidate( - { post => 'notify_dbedit', required => 0, default => 0, enum => [0,1] }, - { post => 'notify_announce', required => 0, default => 0, enum => [0,1] } - ); - return $self->resNotFound if $frm->{_err}; - $self->authPref($_, $frm->{$_}) for ('notify_dbedit', 'notify_announce'); - $saved = 1; - - # updating notifications - } elsif($self->reqMethod() eq 'POST') { - return if !$self->authCheckCode; - my $frm = $self->formValidate( - { post => 'notifysel', multi => 1, required => 0, template => 'id' }, - { post => 'markread', required => 0 }, - { post => 'remove', required => 0 } - ); - return $self->resNotFound if $frm->{_err}; - my @ids = grep $_, @{$frm->{notifysel}}; - $self->dbNotifyMarkRead(@ids) if @ids && $frm->{markread}; - $self->dbNotifyRemove(@ids) if @ids && $frm->{remove}; - } - - my($list, $np) = $self->dbNotifyGet( - uid => $uid, - page => $f->{p}, - results => 25, - what => 'titles', - read => $f->{r} == 1 ? undef : 0, - reverse => $f->{r} == 1, - ); - - $self->htmlHeader(title => 'My notifications', noindex => 1); - $self->htmlMainTabs(u => $u); - div class => 'mainbox'; - h1 'My notifications'; - p class => 'browseopts'; - a !$f->{r} ? (class => 'optselected') : (), href => "/u$uid/notifies?r=0", 'Unread notifications'; - a $f->{r} ? (class => 'optselected') : (), href => "/u$uid/notifies?r=1", 'All notifications'; - end; - p 'No notifications!' if !@$list; - end; - - my $code = $self->authGetCode("/u$uid/notifies"); - - my %ntypes = ( - pm => 'Private Message', - dbdel => 'Entry you contributed to has been deleted', - listdel => 'VN in your (wish)list has been deleted', - dbedit => 'Entry you contributed to has been edited', - announce => 'Site announcement', - ); - - if(@$list) { - form action => "/u$uid/notifies?r=$f->{r};formcode=$code", method => 'post', id => 'notifies'; - $self->htmlBrowse( - items => $list, - options => $f, - nextpage => $np, - class => 'notifies', - pageurl => "/u$uid/notifies?r=$f->{r}", - header => [ - [ '' ], - [ 'Type' ], - [ 'Age' ], - [ 'ID' ], - [ 'Action' ], - ], - row => sub { - my($s, $n, $l) = @_; - Tr $l->{read} ? () : (class => 'unread'); - td class => 'tc1'; - input type => 'checkbox', name => 'notifysel', value => "$l->{id}"; - end; - td class => 'tc2', $ntypes{$l->{ntype}}; - td class => 'tc3', fmtage $l->{date}; - td class => 'tc4'; - a href => "/u$uid/notify/$l->{id}", "$l->{ltype}$l->{iid}".($l->{subid}?".$l->{subid}":''); - end; - td class => 'tc5 clickable', id => "notify_$l->{id}"; - txt $l->{ltype} eq 't' ? 'Edit of ' : $l->{subid} == 1 ? 'New thread ' : 'Reply to '; - i $l->{c_title}; - txt ' by '; - i VNWeb::HTML::user_displayname($l); - end; - end 'tr'; - }, - footer => sub { - Tr; - td colspan => 5; - input type => 'checkbox', class => 'checkall', name => 'notifysel', value => 0; - txt ' '; - input type => 'submit', name => 'markread', value => 'mark selected read'; - input type => 'submit', name => 'remove', value => 'remove selected'; - b class => 'grayedout', ' (Read notifications are automatically removed after one month)'; - end; - end; - } - ); - end; - } - - form method => 'post', action => "/u$uid/notifies?formcode=$code"; - div class => 'mainbox'; - h1 'Settings'; - div class => 'notice', 'Settings successfully saved.' if $saved; - p; - for('dbedit', 'announce') { - input type => 'checkbox', name => "notify_$_", id => "notify_$_", value => 1, - $self->authPref("notify_$_") ? (checked => 'checked') : (); - label for => "notify_$_", $_ eq 'dbedit' - ? ' Notify me about edits of database entries I contributed to.' - : ' Notify me about site announcements.'; - br; - } - input type => 'submit', name => 'set', value => 'Save'; - end; - end; - end 'form'; - $self->htmlFooter; -} - - -sub readnotify { - my($self, $uid, $nid) = @_; - return $self->htmlDenied if !$self->authInfo->{id} || $uid != $self->authInfo->{id}; - my $n = $self->dbNotifyGet(uid => $uid, id => $nid)->[0]; - return $self->resNotFound if !$n->{iid}; - $self->dbNotifyMarkRead($n->{id}) if !$n->{read}; - # NOTE: for t+.+ IDs, this will create a double redirect, which is rather awkward... - $self->resRedirect("/$n->{ltype}$n->{iid}".($n->{subid}?".$n->{subid}":''), 'perm'); -} - - 1; diff --git a/lib/VNWeb/Prelude.pm b/lib/VNWeb/Prelude.pm index cfcd0031..66f78425 100644 --- a/lib/VNWeb/Prelude.pm +++ b/lib/VNWeb/Prelude.pm @@ -13,7 +13,7 @@ # use VNDB::BBCode; # use VNDB::Types; # use VNDB::Config; -# use VNDB::Func 'fmtdate', 'fmtvote'; +# use VNDB::Func 'fmtdate', 'fmtage', 'fmtvote'; # use VNWeb::Auth; # use VNWeb::HTML; # use VNWeb::DB; @@ -55,7 +55,7 @@ sub import { use VNDB::BBCode; use VNDB::Types; use VNDB::Config; - use VNDB::Func 'fmtdate', 'fmtvote'; + use VNDB::Func 'fmtdate', 'fmtage', 'fmtvote'; use VNWeb::Auth; use VNWeb::HTML; use VNWeb::DB; @@ -71,10 +71,11 @@ sub import { # Regular expressions for use in path registration -my $num = qr{[1-9][0-9]{0,6}}; +my $num = qr{[1-9][0-9]{0,8}}; my $id = qr{(?<id>$num)}; my $rev = qr{(?:\.(?<rev>$num))}; our %RE = ( + num => qr{(?<num>$num)}, uid => qr{u$id}, vid => qr{v$id}, rid => qr{r$id}, diff --git a/lib/VNWeb/User/Notifications.pm b/lib/VNWeb/User/Notifications.pm new file mode 100644 index 00000000..f37fc7ca --- /dev/null +++ b/lib/VNWeb/User/Notifications.pm @@ -0,0 +1,174 @@ +package VNWeb::User::Notifications; + +use VNWeb::Prelude; + +my %ntypes = ( + pm => 'Private Message', + dbdel => 'Entry you contributed to has been deleted', + listdel => 'VN in your (wish)list has been deleted', + dbedit => 'Entry you contributed to has been edited', + announce => 'Site announcement', +); + + +sub settings_ { + my $id = shift; + + h1_ 'Settings'; + form_ action => "/u$id/notify_options", method => 'POST', sub { + input_ type => 'hidden', class => 'hidden', name => 'csrf', value => auth->csrftoken; + p_ sub { + label_ sub { + input_ type => 'checkbox', name => 'dbedit', auth->pref('notify_dbedit') ? (checked => 'checked') : (); + txt_ ' Notify me about edits of database entries I contributed to.'; + }; + br_; + label_ sub { + input_ type => 'checkbox', name => 'announce', auth->pref('notify_announce') ? (checked => 'checked') : (); + txt_ ' Notify me about site announcements.'; + }; + br_; + input_ type => 'submit', class => 'submit', value => 'Save'; + } + }; +} + + +sub listing_ { + my($id, $opt, $count, $list) = @_; + + my sub url { "/u$id/notifies?r=$opt->{r}&p=$_" } + + my sub tbl_ { + thead_ sub { tr_ sub { + td_ ''; + td_ 'Type'; + td_ 'Age'; + td_ 'ID'; + td_ 'Action'; + }}; + tfoot_ sub { tr_ sub { + td_ colspan => 5, sub { + input_ type => 'checkbox', class => 'checkall', name => 'notifysel', value => 0; + txt_ ' '; + input_ type => 'submit', class => 'submit', name => 'markread', value => 'mark selected read'; + input_ type => 'submit', class => 'submit', name => 'remove', value => 'remove selected'; + b_ class => 'grayedout', ' (Read notifications are automatically removed after one month)'; + } + }}; + tr_ $_->{read} ? () : (class => 'unread'), sub { + my $l = $_; + my $lid = $l->{ltype}.$l->{iid}.($l->{subid}?'.'.$l->{subid}:''); + my $url = "/u$id/notify/$l->{id}/$lid"; + td_ class => 'tc1', sub { input_ type => 'checkbox', name => 'notifysel', value => $l->{id}; }; + td_ class => 'tc2', $ntypes{$l->{ntype}}; + td_ class => 'tc3', fmtage $l->{date}; + td_ class => 'tc4', sub { a_ href => $url, $lid }; + td_ class => 'tc5', sub { + a_ href => $url, sub { + txt_ $l->{ltype} eq 't' ? 'Edit of ' : $l->{subid} == 1 ? 'New thread ' : 'Reply to '; + i_ $l->{c_title}; + txt_ ' by '; + i_ user_displayname $l; + }; + }; + } for @$list; + } + + form_ action => "/u$id/notify_update", method => 'POST', sub { + input_ type => 'hidden', class => 'hidden', name => 'url', value => do { local $_ = $opt->{p}; url }; + paginate_ \&url, $opt->{p}, [$count, 25], 't'; + div_ class => 'mainbox browse notifies', sub { + table_ class => 'stripe', \&tbl_; + }; + paginate_ \&url, $opt->{p}, [$count, 25], 'b'; + } if $count; +} + + +TUWF::get qr{/$RE{uid}/notifies}, sub { + my $id = tuwf->capture('id'); + return tuwf->resNotFound if !auth || $id != auth->uid; + + my $opt = eval { tuwf->validate(get => + p => { page => 1 }, + r => { anybool => 1 }, + )->data } || { p => 1, r => 0 }; + + my $where = sql_and( + sql('uid =', \$id), + $opt->{r} ? () : 'read IS NULL' + ); + my $count = tuwf->dbVali('SELECT count(*) FROM notifications WHERE', $where); + my($list) = tuwf->dbPagei({ results => 25, page => $opt->{p} }, + 'SELECT n.id, n.ntype, n.ltype, n.iid, n.subid, n.c_title + , ', sql_totime('n.date'), ' as date + , ', sql_totime('n.read'), ' as read + , ', sql_user(), + 'FROM notifications n + LEFT JOIN users u ON u.id = n.c_byuser + WHERE ', $where, + 'ORDER BY n.id', $opt->{r} ? 'DESC' : 'ASC' + ); + + framework_ title => 'My notifications', index => 0, + sub { + div_ class => 'mainbox', sub { + h1_ 'My notifications'; + p_ class => 'browseopts', sub { + a_ !$opt->{r} ? (class => 'optselected') : (), href => '?r=0', 'Unread notifications'; + a_ $opt->{r} ? (class => 'optselected') : (), href => '?r=1', 'All notifications'; + }; + p_ 'No notifications!' if !$count; + }; + listing_ $id, $opt, $count, $list; + div_ class => 'mainbox', sub { settings_ $id }; + }; +}; + + +TUWF::post qr{/$RE{uid}/notify_options}, sub { + my $id = tuwf->capture('id'); + return tuwf->resNotFound if !auth || $id != auth->uid; + + my $frm = tuwf->validate(post => + csrf => {}, + dbedit => { anybool => 1 }, + announce => { anybool => 1 }, + )->data; + return tuwf->resNotFound if !auth->csrfcheck($frm->{csrf}); + + auth->prefSet(notify_dbedit => $frm->{dbedit}); + auth->prefSet(notify_announce => $frm->{announce}); + tuwf->resRedirect("/u$id/notifies", 'post'); +}; + + +TUWF::post qr{/$RE{uid}/notify_update}, sub { + my $id = tuwf->capture('id'); + return tuwf->resNotFound if !auth || $id != auth->uid; + + my $frm = tuwf->validate(post => + url => { regex => qr{^/u$id/notifies} }, + notifysel => { required => 0, type => 'array', scalar => 1, values => { id => 1 } }, + markread => { anybool => 1 }, + remove => { anybool => 1 }, + )->data; + + if($frm->{notifysel}->@*) { + my $where = sql 'uid =', \$id, ' AND id IN', $frm->{notifysel}; + tuwf->dbExeci('DELETE FROM notifications WHERE', $where) if $frm->{remove}; + tuwf->dbExeci('UPDATE notifications SET read = NOW() WHERE', $where) if $frm->{markread}; + } + tuwf->resRedirect($frm->{url}, 'post'); +}; + + +TUWF::get qr{/$RE{uid}/notify/$RE{num}/(?<lid>[a-z0-9\.]+)}, sub { + my $id = tuwf->capture('id'); + return tuwf->resNotFound if !auth || $id != auth->uid; + tuwf->dbExeci('UPDATE notifications SET read = NOW() WHERE uid =', \$id, ' AND id =', \tuwf->capture('num')); + tuwf->resRedirect('/'.tuwf->capture('lid'), 'temp'); +}; + +1; |