diff options
| -rw-r--r-- | lib/Travelynx/Command/database.pm | 48 | ||||
| -rw-r--r-- | lib/Travelynx/Model/Users.pm | 194 | ||||
| -rw-r--r-- | t/21-relations.t | 375 | 
3 files changed, 617 insertions, 0 deletions
diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 39404c4..f906cb5 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1470,6 +1470,54 @@ my @migrations = (  			}  		);  	}, + +	# v34 -> v35 +	sub { +		my ($db) = @_; + +		# 1 : follows +		# 2 : follow requested +		# 3 : is blocked by +		$db->query( +			qq{ +				create table relations ( +					subject_id integer not null references users (id), +					predicate smallint not null, +					object_id integer not null references users (id), +					primary key (subject_id, object_id) +				); +				create view followers as select +					relations.object_id as self_id, +					users.id as id, +					users.name as name +					from relations +					join users on relations.subject_id = users.id +					where predicate = 1; +				create view followees as select +					relations.subject_id as self_id, +					users.id as id, +					users.name as name +					from relations +					join users on relations.object_id = users.id +					where predicate = 1; +				create view follow_requests as select +					relations.object_id as self_id, +					users.id as id, +					users.name as name +					from relations +					join users on relations.subject_id = users.id +					where predicate = 2; +				create view blocked_users as select +					relations.object_id as self_id, +					users.id as id, +					users.name as name +					from relations +					join users on relations.subject_id = users.id +					where predicate = 3; +				update schema_version set version = 35; +			} +		); +	},  );  sub sync_stations { diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 67296ca..9b0a115 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -27,6 +27,18 @@ my %visibility_atoi = (  	private   => 10,  ); +my %predicate_itoa = ( +	1 => 'follows', +	2 => 'requests_follow', +	3 => 'is_blocked_by', +); + +my %predicate_atoi = ( +	follows         => 1, +	requests_follow => 2, +	is_blocked_by   => 3, +); +  my @sb_templates = (  	undef,  	[ 'DBF',         'https://dbf.finalrewind.org/{name}?rt=1#{tt}{tn}' ], @@ -710,4 +722,186 @@ sub update_webhook_status {  	);  } +# TODO irgendwo muss auch noch ne einstellung rein, um follows / follow requests global zu deaktivieren + +sub get_relation { +	my ( $self, %opt ) = @_; + +	my $db     = $opt{db} // $self->{pg}->db; +	my $uid    = $opt{uid}; +	my $target = $opt{target}; + +	my $res_h = $db->select( +		'relations', +		['predicate'], +		{ +			subject_id => $uid, +			object_id  => $target +		} +	)->hash; + +	if ($res_h) { +		return $predicate_itoa{ $res_h->{predicate} }; +	} +	return; + +   #my $res_h = $db->select( 'relations', ['subject_id', 'predicate'], +   #	{ subject_id => [$uid, $target], object_id => [$target, $target] } )->hash; +} + +sub request_follow { +	my ( $self, %opt ) = @_; + +	my $db     = $opt{db} // $self->{pg}->db; +	my $uid    = $opt{uid}; +	my $target = $opt{target}; + +	$db->insert( +		'relations', +		{ +			subject_id => $uid, +			predicate  => $predicate_atoi{requests_follow}, +			object_id  => $target, +		} +	); +} + +sub accept_follow_request { +	my ( $self, %opt ) = @_; + +	my $db        = $opt{db} // $self->{pg}->db; +	my $uid       = $opt{uid}; +	my $applicant = $opt{applicant}; + +	$db->update( +		'relations', +		{ +			predicate => $predicate_atoi{follows}, +		}, +		{ +			subject_id => $applicant, +			predicate  => $predicate_atoi{requests_follow}, +			object_id  => $uid +		} +	); +} + +sub reject_follow_request { +	my ( $self, %opt ) = @_; + +	my $db        = $opt{db} // $self->{pg}->db; +	my $uid       = $opt{uid}; +	my $applicant = $opt{applicant}; + +	$db->delete( +		'relations', +		{ +			subject_id => $applicant, +			predicate  => $predicate_atoi{requests_follow}, +			object_id  => $uid +		} +	); +} + +sub remove_follower { +	my ( $self, %opt ) = @_; + +	my $db       = $opt{db} // $self->{pg}->db; +	my $uid      = $opt{uid}; +	my $follower = $opt{follower}; + +	$db->delete( +		'relations', +		{ +			subject_id => $follower, +			predicate  => $predicate_atoi{follows}, +			object_id  => $uid +		} +	); +} + +sub block { +	my ( $self, %opt ) = @_; + +	my $db     = $opt{db} // $self->{pg}->db; +	my $uid    = $opt{uid}; +	my $target = $opt{target}; + +	$db->insert( +		'relations', +		{ +			subject_id => $target, +			predicate  => $predicate_atoi{is_blocked_by}, +			object_id  => $uid +		}, +		{ +			on_conflict => \ +'(subject_id, object_id) do update set predicate = EXCLUDED.predicate' +		}, +	); +} + +sub unblock { +	my ( $self, %opt ) = @_; + +	my $db     = $opt{db} // $self->{pg}->db; +	my $uid    = $opt{uid}; +	my $target = $opt{target}; + +	$db->delete( +		'relations', +		{ +			subject_id => $target, +			predicate  => $predicate_atoi{is_blocked_by}, +			object_id  => $uid +		}, +	); +} + +sub get_followers { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	my $res = $db->select( 'followers', [ 'id', 'name' ], { self_id => $uid } ); + +	return $res->hashes->each; +} + +sub get_follow_requests { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	my $res +	  = $db->select( 'follow_requests', [ 'id', 'name' ], { self_id => $uid } ); + +	return $res->hashes->each; +} + +sub get_followees { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	my $res = $db->select( 'followees', [ 'id', 'name' ], { self_id => $uid } ); + +	return $res->hashes->each; +} + +sub get_blocked_users { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	my $res +	  = $db->select( 'blocked_users', [ 'id', 'name' ], { self_id => $uid } ); + +	return $res->hashes->each; +} +  1; diff --git a/t/21-relations.t b/t/21-relations.t new file mode 100644 index 0000000..d34e3ea --- /dev/null +++ b/t/21-relations.t @@ -0,0 +1,375 @@ +#!/usr/bin/env perl + +# Copyright (C) 2023 Birthe Friesel <derf@finalrewind.org> +# +# SPDX-License-Identifier: MIT + +use Mojo::Base -strict; + +# Tests journey entry and statistics + +use Test::More; +use Test::Mojo; + +# Include application +use FindBin; +require "$FindBin::Bin/../index.pl"; + +my $t = Test::Mojo->new('Travelynx'); + +if ( not $t->app->config->{db} ) { +	plan( skip_all => 'No database configured' ); +} + +$t->app->pg->db->query('drop schema if exists travelynx_test_21 cascade'); +$t->app->pg->db->query('create schema travelynx_test_21'); +$t->app->pg->db->query('set search_path to travelynx_test_21'); +$t->app->pg->on( +	connection => sub { +		my ( $pg, $dbh ) = @_; +		$dbh->do('set search_path to travelynx_test_21'); +	} +); + +$t->app->config->{mail}->{disabled} = 1; + +$t->app->start( 'database', 'migrate' ); + +my $u = $t->app->users; + +my $uid1 = $u->add( +	name     => 'test1', +	email    => 'test1@example.org', +	token    => 'abcd', +	password => q{}, +); + +my $uid2 = $u->add( +	name     => 'test2', +	email    => 'test2@example.org', +	token    => 'efgh', +	password => q{}, +); + +$u->verify_registration_token( +	uid   => $uid1, +	token => 'abcd' +); +$u->verify_registration_token( +	uid   => $uid2, +	token => 'efgh' +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	undef +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); + +$u->request_follow( +	uid    => $uid1, +	target => $uid2 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	'requests_follow' +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 1 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); +is_deeply( +	[ $u->get_follow_requests( uid => $uid2 ) ], +	[ { id => $uid1, name => 'test1' } ] +); + +$u->reject_follow_request( +	uid       => $uid2, +	applicant => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	undef +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); + +$u->request_follow( +	uid    => $uid1, +	target => $uid2 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	'requests_follow' +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 1 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); +is_deeply( +	[ $u->get_follow_requests( uid => $uid2 ) ], +	[ { id => $uid1, name => 'test1' } ] +); + +$u->accept_follow_request( +	uid       => $uid2, +	applicant => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	'follows' +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       1 ); +is( scalar $u->get_followees( uid => $uid1 ),       1 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); +is_deeply( +	[ $u->get_followers( uid => $uid2 ) ], +	[ { id => $uid1, name => 'test1' } ] +); +is_deeply( +	[ $u->get_followees( uid => $uid1 ) ], +	[ { id => $uid2, name => 'test2' } ] +); + +$u->remove_follower( +	uid      => $uid2, +	follower => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	undef +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); + +$u->request_follow( +	uid    => $uid1, +	target => $uid2 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	'requests_follow' +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); + +$u->block( +	uid    => $uid2, +	target => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	'is_blocked_by' +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   1 ); +is_deeply( +	[ $u->get_blocked_users( uid => $uid2 ) ], +	[ { id => $uid1, name => 'test1' } ] +); + +$u->unblock( +	uid    => $uid2, +	target => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	undef +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   0 ); + +$u->block( +	uid    => $uid2, +	target => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	'is_blocked_by' +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); +is( scalar $u->get_followers( uid => $uid1 ),       0 ); +is( scalar $u->get_followers( uid => $uid2 ),       0 ); +is( scalar $u->get_followees( uid => $uid1 ),       0 ); +is( scalar $u->get_followees( uid => $uid2 ),       0 ); +is( scalar $u->get_follow_requests( uid => $uid1 ), 0 ); +is( scalar $u->get_follow_requests( uid => $uid2 ), 0 ); +is( scalar $u->get_blocked_users( uid => $uid1 ),   0 ); +is( scalar $u->get_blocked_users( uid => $uid2 ),   1 ); +is_deeply( +	[ $u->get_blocked_users( uid => $uid2 ) ], +	[ { id => $uid1, name => 'test1' } ] +); + +$u->unblock( +	uid    => $uid2, +	target => $uid1 +); + +is( +	$u->get_relation( +		uid    => $uid1, +		target => $uid2 +	), +	undef +); +is( +	$u->get_relation( +		uid    => $uid2, +		target => $uid1 +	), +	undef +); + +$t->app->pg->db->query('drop schema travelynx_test_21 cascade'); +done_testing();  | 
