travelynx/lib/Travelynx/Model/Users.pm

481 lines
8.7 KiB
Perl

package Travelynx::Model::Users;
# Copyright (C) 2020 Daniel Friesel
#
# SPDX-License-Identifier: AGPL-3.0-or-later
use strict;
use warnings;
use 5.020;
use DateTime;
sub new {
my ( $class, %opt ) = @_;
return bless( \%opt, $class );
}
sub mark_seen {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db;
$db->update(
'users',
{
last_seen => DateTime->now( time_zone => 'Europe/Berlin' ),
deletion_notified => undef
},
{ id => $uid }
);
}
sub mark_deletion_notified {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $db = $opt{db} // $self->{pg}->db;
$db->update(
'users',
{
deletion_notified => DateTime->now( time_zone => 'Europe/Berlin' ),
},
{ id => $uid }
);
}
sub verify_registration_token {
my ( $self, %opt ) = @_;
my $uid = $opt{uid};
my $token = $opt{token};
my $db = $opt{db} // $self->{pg}->db;
my $tx;
if ( not $opt{in_transaction} ) {
$tx = $db->begin;
}
my $res = $db->select(
'pending_registrations',
'count(*) as count',
{
user_id => $uid,
token => $token
}
);
if ( $res->hash->{count} ) {
$db->update( 'users', { status => 1 }, { id => $uid } );
$db->delete( 'pending_registrations', { user_id => $uid } );
if ( not $opt{in_transaction} ) {
$tx->commit;
}
return 1;
}
return;
}
sub get_uid_by_name_and_mail {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $name = $opt{name};
my $email = $opt{email};
my $res = $db->select(
'users',
['id'],
{
name => $name,
email => $email,
status => 1
}
);
if ( my $user = $res->hash ) {
return $user->{id};
}
return;
}
sub get_privacy_by_name {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $name = $opt{name};
my $res = $db->select(
'users',
[ 'id', 'public_level' ],
{
name => $name,
status => 1
}
);
if ( my $user = $res->hash ) {
return $user;
}
return;
}
sub set_privacy {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $public_level = $opt{level};
$db->update( 'users', { public_level => $public_level }, { id => $uid } );
}
sub mark_for_password_reset {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $token = $opt{token};
my $res = $db->select(
'pending_passwords',
'count(*) as count',
{ user_id => $uid }
);
if ( $res->hash->{count} ) {
return 'in progress';
}
$db->insert(
'pending_passwords',
{
user_id => $uid,
token => $token,
requested_at => DateTime->now( time_zone => 'Europe/Berlin' )
}
);
return undef;
}
sub verify_password_token {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $token = $opt{token};
my $res = $db->select(
'pending_passwords',
'count(*) as count',
{
user_id => $uid,
token => $token
}
);
if ( $res->hash->{count} ) {
return 1;
}
return;
}
sub mark_for_mail_change {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $email = $opt{email};
my $token = $opt{token};
$db->insert(
'pending_mails',
{
user_id => $uid,
email => $email,
token => $token,
requested_at => DateTime->now( time_zone => 'Europe/Berlin' )
},
{
on_conflict => \
'(user_id) do update set email = EXCLUDED.email, token = EXCLUDED.token, requested_at = EXCLUDED.requested_at'
},
);
}
sub change_mail_with_token {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $token = $opt{token};
my $tx = $db->begin;
my $res_h = $db->select(
'pending_mails',
['email'],
{
user_id => $uid,
token => $token
}
)->hash;
if ($res_h) {
$db->update( 'users', { email => $res_h->{email} }, { id => $uid } );
$db->delete( 'pending_mails', { user_id => $uid } );
$tx->commit;
return 1;
}
return;
}
sub is_name_invalid {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $name = $opt{name};
if ( not length($name) ) {
return 'user_empty';
}
if ( $name !~ m{ ^ [0-9a-zA-Z_-]+ $ }x ) {
return 'user_format';
}
if (
$self->user_name_exists(
db => $db,
name => $name
)
)
{
return 'user_collision';
}
return;
}
sub change_name {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
eval { $db->update( 'users', { name => $opt{name} }, { id => $uid } ); };
if ($@) {
return 0;
}
return 1;
}
sub remove_password_token {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $token = $opt{token};
$db->delete(
'pending_passwords',
{
user_id => $uid,
token => $token
}
);
}
sub get_data {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $user = $db->select(
'users',
'id, name, status, public_level, email, '
. 'extract(epoch from registered_at) as registered_at_ts, '
. 'extract(epoch from last_seen) as last_seen_ts, '
. 'extract(epoch from deletion_requested) as deletion_requested_ts',
{ id => $uid }
)->hash;
if ($user) {
return {
id => $user->{id},
name => $user->{name},
status => $user->{status},
is_public => $user->{public_level},
email => $user->{email},
registered_at => DateTime->from_epoch(
epoch => $user->{registered_at_ts},
time_zone => 'Europe/Berlin'
),
last_seen => DateTime->from_epoch(
epoch => $user->{last_seen_ts},
time_zone => 'Europe/Berlin'
),
deletion_requested => $user->{deletion_requested_ts}
? DateTime->from_epoch(
epoch => $user->{deletion_requested_ts},
time_zone => 'Europe/Berlin'
)
: undef,
};
}
return undef;
}
sub get_login_data {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $name = $opt{name};
my $res_h = $db->select(
'users',
'id, name, status, password as password_hash',
{ name => $name }
)->hash;
return $res_h;
}
sub add_user {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $user_name = $opt{name};
my $email = $opt{email};
my $token = $opt{token};
my $password = $opt{password_hash};
# This helper must be called during a transaction, as user creation
# may fail even after the database entry has been generated, e.g. if
# the registration mail cannot be sent. We therefore use $db (the
# database handle performing the transaction) instead of $self->pg->db
# (which may be a new handle not belonging to the transaction).
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
my $res = $db->insert(
'users',
{
name => $user_name,
status => 0,
public_level => 0,
email => $email,
password => $password,
registered_at => $now,
last_seen => $now,
},
{ returning => 'id' }
);
my $uid = $res->hash->{id};
$db->insert(
'pending_registrations',
{
user_id => $uid,
token => $token
}
);
return $uid;
}
sub flag_deletion {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
$db->update(
'users',
{ deletion_requested => $now },
{
id => $uid,
}
);
}
sub unflag_deletion {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
$db->update(
'users',
{
deletion_requested => undef,
},
{
id => $uid,
}
);
}
sub set_password_hash {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $password = $opt{password_hash};
$db->update( 'users', { password => $password }, { id => $uid } );
}
sub user_name_exists {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $user_name = $opt{name};
my $count
= $db->select( 'users', 'count(*) as count', { name => $user_name } )
->hash->{count};
if ($count) {
return 1;
}
return 0;
}
sub mail_is_blacklisted {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $mail = $opt{email};
my $count = $db->select(
'users',
'count(*) as count',
{
email => $mail,
status => 0,
}
)->hash->{count};
if ($count) {
return 1;
}
$count = $db->select(
'mail_blacklist',
'count(*) as count',
{
email => $mail,
num_tries => { '>', 1 },
}
)->hash->{count};
if ($count) {
return 1;
}
return 0;
}
sub use_history {
my ( $self, %opt ) = @_;
my $db = $opt{db} // $self->{pg}->db;
my $uid = $opt{uid};
my $value = $opt{set};
if ($value) {
$db->update( 'users', { use_history => $value }, { id => $uid } );
}
else {
return $db->select( 'users', ['use_history'], { id => $uid } )
->hash->{use_history};
}
}
1;