2019-03-25 18:54:49 +00:00
|
|
|
package Travelynx::Command::maintenance;
|
2020-12-03 20:42:17 +00:00
|
|
|
|
2020-11-27 21:12:56 +00:00
|
|
|
# Copyright (C) 2020 Daniel Friesel
|
|
|
|
#
|
2021-01-29 17:32:13 +00:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
2019-03-25 18:54:49 +00:00
|
|
|
use Mojo::Base 'Mojolicious::Command';
|
|
|
|
|
|
|
|
use DateTime;
|
|
|
|
|
2019-12-20 17:56:57 +00:00
|
|
|
has description => 'Prune unverified users, incomplete checkins etc';
|
2019-03-25 18:54:49 +00:00
|
|
|
|
|
|
|
has usage => sub { shift->extract_usage };
|
|
|
|
|
|
|
|
sub run {
|
|
|
|
my ( $self, $filename ) = @_;
|
|
|
|
|
2022-02-18 16:21:49 +00:00
|
|
|
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
|
|
|
my $verification_deadline = $now->clone->subtract( hours => 48 );
|
|
|
|
my $deletion_deadline = $now->clone->subtract( hours => 72 );
|
|
|
|
my $old_deadline = $now->clone->subtract( years => 1 );
|
2022-02-14 20:58:30 +00:00
|
|
|
my $old_notification_deadline = $now->clone->subtract( weeks => 4 );
|
2019-04-22 11:42:41 +00:00
|
|
|
|
|
|
|
my $db = $self->app->pg->db;
|
|
|
|
my $tx = $db->begin;
|
|
|
|
|
|
|
|
my $unverified = $db->select(
|
|
|
|
'users',
|
|
|
|
'id, email, extract(epoch from registered_at) as registered_ts',
|
|
|
|
{
|
|
|
|
status => 0,
|
|
|
|
registered_at => { '<', $verification_deadline }
|
2019-03-25 18:54:49 +00:00
|
|
|
}
|
2019-04-22 11:42:41 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
for my $user ( $unverified->hashes->each ) {
|
|
|
|
my $mail = $user->{email};
|
|
|
|
my $reg_date = DateTime->from_epoch(
|
|
|
|
epoch => $user->{registered_ts},
|
|
|
|
time_zone => 'Europe/Berlin'
|
|
|
|
);
|
|
|
|
|
|
|
|
my $pending
|
2019-04-30 10:08:51 +00:00
|
|
|
= $db->select( 'mail_blacklist', ['num_tries'], { email => $mail } );
|
2019-04-22 11:42:41 +00:00
|
|
|
my $pending_h = $pending->hash;
|
|
|
|
|
|
|
|
if ($pending_h) {
|
|
|
|
my $num_tries = $pending_h->{num_tries} + 1;
|
|
|
|
$db->update(
|
2019-04-30 10:08:51 +00:00
|
|
|
'mail_blacklist',
|
2019-04-22 11:42:41 +00:00
|
|
|
{
|
|
|
|
num_tries => $num_tries,
|
|
|
|
last_try => $reg_date
|
|
|
|
},
|
|
|
|
{ email => $mail }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$db->insert(
|
2019-04-30 10:08:51 +00:00
|
|
|
'mail_blacklist',
|
2019-04-22 11:42:41 +00:00
|
|
|
{
|
|
|
|
email => $mail,
|
|
|
|
num_tries => 1,
|
|
|
|
last_try => $reg_date
|
|
|
|
}
|
|
|
|
);
|
2019-03-31 15:58:05 +00:00
|
|
|
}
|
2019-04-30 16:05:07 +00:00
|
|
|
$db->delete( 'pending_registrations', { user_id => $user->{id} } );
|
|
|
|
$db->delete( 'users', { id => $user->{id} } );
|
2019-04-22 11:42:41 +00:00
|
|
|
printf( "Pruned unverified user %d\n", $user->{id} );
|
2019-03-31 15:58:05 +00:00
|
|
|
}
|
2019-04-22 11:42:41 +00:00
|
|
|
|
2021-03-07 18:06:21 +00:00
|
|
|
my $res = $db->delete( 'pending_passwords',
|
2019-04-29 18:12:59 +00:00
|
|
|
{ requested_at => { '<', $verification_deadline } } );
|
|
|
|
|
|
|
|
if ( my $rows = $res->rows ) {
|
|
|
|
printf( "Pruned %d pending password reset(s)\n", $rows );
|
|
|
|
}
|
|
|
|
|
2019-04-30 10:08:51 +00:00
|
|
|
$res = $db->delete( 'pending_mails',
|
|
|
|
{ requested_at => { '<', $verification_deadline } } );
|
|
|
|
|
|
|
|
if ( my $rows = $res->rows ) {
|
|
|
|
printf( "Pruned %d pending mail change(s)\n", $rows );
|
|
|
|
}
|
|
|
|
|
2022-02-14 20:58:30 +00:00
|
|
|
my $to_notify = $db->select(
|
|
|
|
'users',
|
|
|
|
[ 'id', 'name', 'email', 'last_seen' ],
|
|
|
|
{
|
|
|
|
last_seen => { '<', $old_deadline },
|
|
|
|
deletion_notified => undef
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
for my $user ( $to_notify->hashes->each ) {
|
2022-02-18 16:21:49 +00:00
|
|
|
say "Sending account deletion notification to uid $user->{id}...";
|
2022-02-14 20:58:30 +00:00
|
|
|
$self->app->sendmail->age_deletion_notification(
|
|
|
|
name => $user->{name},
|
|
|
|
email => $user->{email},
|
|
|
|
last_seen => $user->{last_seen},
|
|
|
|
login_url => $self->app->base_url_for('login')->to_abs,
|
|
|
|
account_url => $self->app->base_url_for('account')->to_abs,
|
|
|
|
imprint_url => $self->app->base_url_for('impressum')->to_abs,
|
|
|
|
);
|
|
|
|
$self->app->users->mark_deletion_notified( uid => $user->{id} );
|
|
|
|
}
|
|
|
|
|
2019-04-22 11:42:41 +00:00
|
|
|
my $to_delete = $db->select( 'users', ['id'],
|
|
|
|
{ deletion_requested => { '<', $deletion_deadline } } );
|
|
|
|
my @uids_to_delete = $to_delete->arrays->map( sub { shift->[0] } )->each;
|
|
|
|
|
2022-02-14 20:58:30 +00:00
|
|
|
$to_delete = $db->select(
|
|
|
|
'users',
|
|
|
|
['id'],
|
|
|
|
{
|
|
|
|
last_seen => { '<', $old_deadline },
|
|
|
|
deletion_notified => { '<', $old_notification_deadline }
|
|
|
|
}
|
|
|
|
);
|
2019-04-30 10:47:32 +00:00
|
|
|
|
|
|
|
push( @uids_to_delete,
|
|
|
|
$to_delete->arrays->map( sub { shift->[0] } )->each );
|
|
|
|
|
2019-04-22 11:42:41 +00:00
|
|
|
if ( @uids_to_delete > 10 ) {
|
|
|
|
printf STDERR (
|
|
|
|
"About to delete %d accounts, which is quite a lot.\n",
|
2019-03-31 15:58:05 +00:00
|
|
|
scalar @uids_to_delete
|
|
|
|
);
|
2022-07-10 08:42:05 +00:00
|
|
|
for my $uid (@uids_to_delete) {
|
|
|
|
my $journeys_res = $db->select(
|
|
|
|
'journeys',
|
|
|
|
'count(*) as count',
|
|
|
|
{ user_id => $uid }
|
|
|
|
)->hash;
|
|
|
|
printf STDERR (
|
|
|
|
" - UID %5d (%4d journeys)\n",
|
|
|
|
$uid, $journeys_res->{count}
|
|
|
|
);
|
|
|
|
}
|
2019-04-22 11:42:41 +00:00
|
|
|
say STDERR 'Aborting maintenance. Please investigate.';
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for my $uid (@uids_to_delete) {
|
|
|
|
say "Deleting uid ${uid}...";
|
2023-01-22 12:07:46 +00:00
|
|
|
my $count = $self->app->users->delete(
|
|
|
|
uid => $uid,
|
|
|
|
db => $db,
|
|
|
|
in_transaction => 1
|
|
|
|
);
|
2019-04-24 06:45:56 +00:00
|
|
|
printf( " %d tokens, %d monthly stats, %d journeys\n",
|
2023-01-22 12:07:46 +00:00
|
|
|
$count->{tokens}, $count->{stats}, $count->{journeys} );
|
2019-03-31 15:58:05 +00:00
|
|
|
}
|
|
|
|
|
2019-04-22 11:42:41 +00:00
|
|
|
$tx->commit;
|
2020-01-30 17:06:04 +00:00
|
|
|
|
2020-12-03 20:42:17 +00:00
|
|
|
# Computing stats may take a while, but we've got all time in the
|
|
|
|
# world here. This means users won't have to wait when loading their
|
|
|
|
# own journey log.
|
|
|
|
say 'Generating missing stats ...';
|
|
|
|
for
|
|
|
|
my $user ( $db->select( 'users', ['id'], { status => 1 } )->hashes->each )
|
|
|
|
{
|
|
|
|
$tx = $db->begin;
|
|
|
|
$self->app->journeys->generate_missing_stats( uid => $user->{id} );
|
|
|
|
$self->app->journeys->get_stats(
|
|
|
|
uid => $user->{id},
|
|
|
|
year => $now->year
|
|
|
|
);
|
|
|
|
$tx->commit;
|
|
|
|
}
|
2019-03-25 18:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
Usage: index.pl maintenance
|
|
|
|
|
|
|
|
Prunes unverified users.
|