diff options
| author | Derf Null <derf@finalrewind.org> | 2023-06-04 18:21:36 +0200 | 
|---|---|---|
| committer | Derf Null <derf@finalrewind.org> | 2023-06-04 18:21:36 +0200 | 
| commit | 00eb6af1bd21df42fc41195ceed0fad73bbb5f27 (patch) | |
| tree | aea171b00a79681e403292b0ad06b01e5d9a21d1 /lib | |
| parent | 07fe4ecd1f73e7b111d4cd6f6bb8fd390b5f3151 (diff) | |
expose follows / social interaction in frontend
Diffstat (limited to 'lib')
| -rwxr-xr-x | lib/Travelynx.pm | 7 | ||||
| -rw-r--r-- | lib/Travelynx/Command/database.pm | 21 | ||||
| -rw-r--r-- | lib/Travelynx/Controller/Account.pm | 254 | ||||
| -rwxr-xr-x | lib/Travelynx/Controller/Profile.pm | 100 | ||||
| -rwxr-xr-x | lib/Travelynx/Controller/Traveling.pm | 1 | ||||
| -rw-r--r-- | lib/Travelynx/Model/Users.pm | 106 | 
6 files changed, 463 insertions, 26 deletions
| diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index adb6132..6dd77fd 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -399,6 +399,9 @@ sub startup {  			if ( $visibility eq 'travelynx' ) {  				return 'lock_open';  			} +			if ( $visibility eq 'followers' ) { +				return 'group'; +			}  			if ( $visibility eq 'unlisted' ) {  				return 'lock_outline';  			} @@ -2213,6 +2216,8 @@ sub startup {  	$authed_r->get('/account')->to('account#account');  	$authed_r->get('/account/privacy')->to('account#privacy'); +	$authed_r->get('/account/social')->to('account#social'); +	$authed_r->get('/account/social/:kind')->to('account#social_list');  	$authed_r->get('/account/profile')->to('account#profile');  	$authed_r->get('/account/hooks')->to('account#webhook');  	$authed_r->get('/account/traewelling')->to('traewelling#settings'); @@ -2240,6 +2245,7 @@ sub startup {  	$authed_r->get('/s/*station')->to('traveling#station');  	$authed_r->get('/confirm_mail/:token')->to('account#confirm_mail');  	$authed_r->post('/account/privacy')->to('account#privacy'); +	$authed_r->post('/account/social')->to('account#social');  	$authed_r->post('/account/profile')->to('account#profile');  	$authed_r->post('/account/hooks')->to('account#webhook');  	$authed_r->post('/account/traewelling')->to('traewelling#settings'); @@ -2254,6 +2260,7 @@ sub startup {  	$authed_r->post('/account/password')->to('account#change_password');  	$authed_r->post('/account/mail')->to('account#change_mail');  	$authed_r->post('/account/name')->to('account#change_name'); +	$authed_r->post('/social-action')->to('account#social_action');  	$authed_r->post('/delete')->to('account#delete');  	$authed_r->post('/logout')->to('account#do_logout');  	$authed_r->post('/set_token')->to('api#set_token'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 53168bf..3878502 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1545,6 +1545,27 @@ my @migrations = (  			}  		);  	}, + +	# v37 -> v38 +	sub { +		my ($db) = @_; +		$db->query( +			qq{ +				drop view followers; +				create view followers as select +					relations.object_id as self_id, +					users.id as id, +					users.name as name, +					users.accept_follows as accept_follows, +					r2.predicate as inverse_predicate +					from relations +					join users on relations.subject_id = users.id +					left join relations as r2 on relations.subject_id = r2.object_id +					where relations.predicate = 1; +				update schema_version set version = 38; +			} +		); +	},  );  # TODO add 'hafas' column to in_transit (and maybe journeys? undo/redo needs something to work with...) diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index af97c96..fe5d5cc 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -408,11 +408,8 @@ sub delete {  	my ($self) = @_;  	my $uid = $self->current_user->{id};  	if ( $self->validation->csrf_protect->has_error('csrf_token') ) { -		$self->render( -			'account', -			api_token => $self->users->get_api_token( uid => $uid ), -			invalid   => 'csrf', -		); +		$self->flash( invalid => 'csrf' ); +		$self->redirect_to('account');  		return;  	} @@ -424,11 +421,8 @@ sub delete {  			)  		  )  		{ -			$self->render( -				'account', -				api_token => $self->users->get_api_token( uid => $uid ), -				invalid   => 'deletion password' -			); +			$self->flash( invalid => 'deletion password' ); +			$self->redirect_to('account');  			return;  		}  		$self->users->flag_deletion( uid => $uid ); @@ -501,6 +495,228 @@ sub privacy {  	}  } +sub social { +	my ($self) = @_; + +	my $user = $self->current_user; + +	if ( $self->param('action') and $self->param('action') eq 'save' ) { +		if ( $self->validation->csrf_protect->has_error('csrf_token') ) { +			$self->render( +				'social', +				invalid => 'csrf', +			); +			return; +		} + +		my %opt; +		my $accept_follow = $self->param('accept_follow'); + +		if ( $accept_follow eq 'yes' ) { +			$opt{accept_follows} = 1; +		} +		elsif ( $accept_follow eq 'request' ) { +			$opt{accept_follow_requests} = 1; +		} + +		$self->users->set_social( +			uid => $user->{id}, +			%opt +		); + +		$self->flash( success => 'social' ); +		$self->redirect_to('account'); +	} +	else { +		if ( $user->{accept_follows} ) { +			$self->param( accept_follow => 'yes' ); +		} +		elsif ( $user->{accept_follow_requests} ) { +			$self->param( accept_follow => 'request' ); +		} +		else { +			$self->param( accept_follow => 'no' ); +		} +		$self->render( 'social', name => $user->{name} ); +	} +} + +sub social_list { +	my ($self) = @_; + +	my $kind = $self->stash('kind'); +	my $user = $self->current_user; + +	if ( $kind eq 'follow-requests' ) { +		my @follow_reqs +		  = $self->users->get_follow_requests( uid => $user->{id} ); +		$self->render( +			'social_list', +			type          => 'follow-requests', +			entries       => [@follow_reqs], +			notifications => $user->{notifications}, +		); +	} +	elsif ( $kind eq 'followers' ) { +		my @followers = $self->users->get_followers( uid => $user->{id} ); +		$self->render( +			'social_list', +			type          => 'followers', +			entries       => [@followers], +			notifications => $user->{notifications}, +		); +	} +	elsif ( $kind eq 'follows' ) { +		my @following = $self->users->get_followees( uid => $user->{id} ); +		$self->render( +			'social_list', +			type          => 'follows', +			entries       => [@following], +			notifications => $user->{notifications}, +		); +	} +	elsif ( $kind eq 'blocks' ) { +		my @blocked = $self->users->get_blocked_users( uid => $user->{id} ); +		$self->render( +			'social_list', +			type          => 'blocks', +			entries       => [@blocked], +			notifications => $user->{notifications}, +		); +	} +	else { +		$self->render( 'not_found', status => 404 ); +	} +} + +sub social_action { +	my ($self) = @_; + +	my $user        = $self->current_user; +	my $action      = $self->param('action'); +	my $target_ids  = $self->param('target'); +	my $redirect_to = $self->param('redirect_to'); + +	for my $key ( +		qw(follow request_follow follow_or_request unfollow remove_follower cancel_follow_request accept_follow_request reject_follow_request block unblock) +	  ) +	{ +		if ( $self->param($key) ) { +			$action     = $key; +			$target_ids = $self->param($key); +		} +	} + +	if ( $self->validation->csrf_protect->has_error('csrf_token') ) { +		$self->redirect_to('/'); +		return; +	} + +	if ( $action and $action eq 'clear_notifications' ) { +		$self->users->update_notifications( +			db                  => $self->pg->db, +			uid                 => $user->{id}, +			has_follow_requests => 0 +		); +		$self->flash( success => 'clear_notifications' ); +		$self->redirect_to('account'); +		return; +	} + +	if ( not( $action and $target_ids and $redirect_to ) ) { +		$self->redirect_to('/'); +		return; +	} + +	for my $target_id ( split( qr{,}, $target_ids ) ) { +		my $target = $self->users->get_privacy_by( uid => $target_id ); + +		if ( not $target ) { +			next; +		} + +		if ( $action eq 'follow' and $target->{accept_follows} ) { +			$self->users->follow( +				uid    => $user->{id}, +				target => $target->{id} +			); +		} +		elsif ( $action eq 'request_follow' +			and $target->{accept_follow_requests} ) +		{ +			$self->users->request_follow( +				uid    => $user->{id}, +				target => $target->{id} +			); +		} +		elsif ( $action eq 'follow_or_request' ) { +			if ( $target->{accept_follows} ) { +				$self->users->follow( +					uid    => $user->{id}, +					target => $target->{id} +				); +			} +			elsif ( $target->{accept_follow_requests} ) { +				$self->users->request_follow( +					uid    => $user->{id}, +					target => $target->{id} +				); +			} +		} +		elsif ( $action eq 'unfollow' ) { +			$self->users->unfollow( +				uid    => $user->{id}, +				target => $target->{id} +			); +		} +		elsif ( $action eq 'remove_follower' ) { +			$self->users->remove_follower( +				uid      => $user->{id}, +				follower => $target->{id} +			); +		} +		elsif ( $action eq 'cancel_follow_request' ) { +			$self->users->cancel_follow_request( +				uid    => $user->{id}, +				target => $target->{id} +			); +		} +		elsif ( $action eq 'accept_follow_request' ) { +			$self->users->accept_follow_request( +				uid       => $user->{id}, +				applicant => $target->{id} +			); +		} +		elsif ( $action eq 'reject_follow_request' ) { +			$self->users->reject_follow_request( +				uid       => $user->{id}, +				applicant => $target->{id} +			); +		} +		elsif ( $action eq 'block' ) { +			$self->users->block( +				uid    => $user->{id}, +				target => $target->{id} +			); +		} +		elsif ( $action eq 'unblock' ) { +			$self->users->unblock( +				uid    => $user->{id}, +				target => $target->{id} +			); +		} + +		if ( $redirect_to eq 'profile' ) { + +			# profile links do not perform bulk actions +			$self->redirect_to( '/p/' . $target->{name} ); +			return; +		} +	} + +	$self->redirect_to($redirect_to); +} +  sub profile {  	my ($self) = @_;  	my $user = $self->current_user; @@ -1012,11 +1228,21 @@ sub confirm_mail {  }  sub account { -	my ($self) = @_; -	my $uid = $self->current_user->{id}; +	my ($self)          = @_; +	my $uid             = $self->current_user->{id}; +	my $follow_requests = $self->users->has_follow_requests( uid => $uid ); +	my $followers       = $self->users->has_followers( uid => $uid ); +	my $following       = $self->users->has_followees( uid => $uid ); +	my $blocked         = $self->users->has_blocked_users( uid => $uid ); -	$self->render( 'account', -		api_token => $self->users->get_api_token( uid => $uid ) ); +	$self->render( +		'account', +		api_token           => $self->users->get_api_token( uid => $uid ), +		num_follow_requests => $follow_requests, +		num_followers       => $followers, +		num_following       => $following, +		num_blocked         => $blocked, +	);  	$self->users->mark_seen( uid => $uid );  } diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm index 86b8922..b11dd71 100755 --- a/lib/Travelynx/Controller/Profile.pm +++ b/lib/Travelynx/Controller/Profile.pm @@ -186,6 +186,23 @@ sub profile {  		metadata         => $profile->{metadata},  		public_level     => $user->{public_level},  		is_self          => $is_self, +		following        => ( $relation and $relation eq 'follows' ) ? 1 : 0, +		follow_requested => ( $relation and $relation eq 'requests_follow' ) +		? 1 +		: 0, +		can_follow => ( $my_user and $user->{accept_follows} and not $relation ) +		? 1 +		: 0, +		can_request_follow => +		  ( $my_user and $user->{accept_follow_requests} and not $relation ) +		? 1 +		: 0, +		follows_me => ( $inverse_relation and $inverse_relation eq 'follows' ) +		? 1 +		: 0, +		follow_reqs_me => +		  ( $inverse_relation and $inverse_relation eq 'requests_follow' ) ? 1 +		: 0,  		journey            => $status,  		journey_visibility => $visibility,  		journeys           => [@journeys], @@ -201,6 +218,24 @@ sub journey_details {  	$self->param( journey_id => $journey_id ); +	my $my_user; +	my $relation; +	my $inverse_relation; +	my $is_self; +	if ( $self->is_user_authenticated ) { +		$my_user = $self->current_user; +		if ( $my_user->{id} == $user->{id} ) { +			$is_self = 1; +			$my_user = undef; +		} +		else { +			$relation = $self->users->get_relation( +				subject => $my_user->{id}, +				object  => $user->{id} +			); +		} +	} +  	if ( not( $user and $journey_id and $journey_id =~ m{ ^ \d+ $ }x ) ) {  		$self->render(  			'journey', @@ -249,7 +284,12 @@ sub journey_details {  				and $self->journey_token_ok($journey) )  			or (  				$visibility eq 'travelynx' -				and ( ( $self->is_user_authenticated and not $is_past ) +				and ( ( $my_user and not $is_past ) +					or $self->journey_token_ok($journey) ) +			) +			or ( +				$visibility eq 'followers' +				and ( ( $relation and $relation eq 'follows' )  					or $self->journey_token_ok($journey) )  			)  		) @@ -337,6 +377,24 @@ sub user_status {  		return;  	} +	my $my_user; +	my $relation; +	my $inverse_relation; +	my $is_self; +	if ( $self->is_user_authenticated ) { +		$my_user = $self->current_user; +		if ( $my_user->{id} == $user->{id} ) { +			$is_self = 1; +			$my_user = undef; +		} +		else { +			$relation = $self->users->get_relation( +				subject => $my_user->{id}, +				object  => $user->{id} +			); +		} +	} +  	my $status = $self->get_user_status( $user->{id} );  	if ( @@ -364,7 +422,12 @@ sub user_status {  					and $self->journey_token_ok( $journey, $ts ) )  				or (  					$visibility eq 'travelynx' -					and (  $self->is_user_authenticated +					and (  $my_user +						or $self->journey_token_ok( $journey, $ts ) ) +				) +				or ( +					$visibility eq 'followers' +					and ( ( $relation and $relation eq 'follows' )  						or $self->journey_token_ok( $journey, $ts ) )  				)  			  ) @@ -408,7 +471,12 @@ sub user_status {  					and $self->status_token_ok( $status, $ts ) )  				or (  					$visibility eq 'travelynx' -					and (  $self->is_user_authenticated +					and (  $my_user +						or $self->status_token_ok( $status, $ts ) ) +				) +				or ( +					$visibility eq 'followers' +					and ( ( $relation and $relation eq 'follows' )  						or $self->status_token_ok( $status, $ts ) )  				)  			) @@ -486,6 +554,24 @@ sub status_card {  		return;  	} +	my $my_user; +	my $relation; +	my $inverse_relation; +	my $is_self; +	if ( $self->is_user_authenticated ) { +		$my_user = $self->current_user; +		if ( $my_user->{id} == $user->{id} ) { +			$is_self = 1; +			$my_user = undef; +		} +		else { +			$relation = $self->users->get_relation( +				subject => $my_user->{id}, +				object  => $user->{id} +			); +		} +	} +  	my $status = $self->get_user_status( $user->{id} );  	my $visibility;  	if ( $status->{checked_in} or $status->{arr_name} ) { @@ -500,7 +586,12 @@ sub status_card {  					and $self->status_token_ok($status) )  				or (  					$visibility eq 'travelynx' -					and (  $self->is_user_authenticated +					and (  $my_user +						or $self->status_token_ok($status) ) +				) +				or ( +					$visibility eq 'followers' +					and ( ( $relation and $relation eq 'follows' )  						or $self->status_token_ok($status) )  				)  			) @@ -523,6 +614,7 @@ sub status_card {  		public_level       => $user->{public_level},  		journey            => $status,  		journey_visibility => $visibility, +		from_profile       => $self->param('profile') ? 1 : 0,  	);  } diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 27005e8..5483e00 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -1459,6 +1459,7 @@ sub journey_details {  		if (   $visibility eq 'public'  			or $visibility eq 'travelynx' +			or $visibility eq 'followers'  			or $visibility eq 'unlisted' )  		{  			my $delay = 'pünktlich '; diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index 7326e34..ade9711 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -180,13 +180,14 @@ sub get_privacy_by {  	my $res = $db->select(  		'users', -		[ 'id', 'public_level', 'accept_follows' ], +		[ 'id', 'name', 'public_level', 'accept_follows' ],  		{ %where, status => 1 }  	);  	if ( my $user = $res->hash ) {  		return {  			id                 => $user->{id}, +			name               => $user->{name},  			public_level       => $user->{public_level},          # todo remove?  			default_visibility => $user->{public_level} & 0x7f,  			default_visibility_str => @@ -777,16 +778,16 @@ sub get_profile {  sub get_relation {  	my ( $self, %opt ) = @_; -	my $db     = $opt{db} // $self->{pg}->db; -	my $uid    = $opt{uid}; -	my $target = $opt{target}; +	my $db      = $opt{db} // $self->{pg}->db; +	my $subject = $opt{subject}; +	my $object  = $opt{object};  	my $res_h = $db->select(  		'relations',  		['predicate'],  		{ -			subject_id => $uid, -			object_id  => $target +			subject_id => $subject, +			object_id  => $object,  		}  	)->hash; @@ -824,6 +825,24 @@ sub update_notifications {  	$db->update( 'users', { notifications => $notifications }, { id => $uid } );  } +sub 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{follows}, +			object_id  => $target, +			ts         => DateTime->now( time_zone => 'Europe/Berlin' ), +		} +	); +} +  sub request_follow {  	my ( $self, %opt ) = @_; @@ -920,6 +939,16 @@ sub reject_follow_request {  	}  } +sub cancel_follow_request { +	my ( $self, %opt ) = @_; + +	$self->reject_follow_request( +		db        => $opt{db}, +		uid       => $opt{target}, +		applicant => $opt{uid}, +	); +} +  sub unfollow {  	my ( $self, %opt ) = @_; @@ -1005,9 +1034,50 @@ sub get_followers {  	my $db  = $opt{db} // $self->{pg}->db;  	my $uid = $opt{uid}; -	my $res = $db->select( 'followers', [ 'id', 'name' ], { self_id => $uid } ); +	my $res = $db->select( +		'followers', +		[ 'id', 'name', 'accept_follows', 'inverse_predicate' ], +		{ self_id => $uid } +	); + +	my @ret; +	while ( my $row = $res->hash ) { +		push( +			@ret, +			{ +				id             => $row->{id}, +				name           => $row->{name}, +				following_back => ( +					      $row->{inverse_predicate} +					  and $row->{inverse_predicate} == $predicate_atoi{follows} +				) ? 1 : 0, +				followback_requested => ( +					      $row->{inverse_predicate} +					  and $row->{inverse_predicate} +					  == $predicate_atoi{requests_follow} +				) ? 1 : 0, +				can_follow_back => ( +					not $row->{inverse_predicate} +					  and $row->{accept_follows} == 2 +				) ? 1 : 0, +				can_request_follow_back => ( +					not $row->{inverse_predicate} +					  and $row->{accept_follows} == 1 +				) ? 1 : 0, +			} +		); +	} +	return @ret; +} -	return $res->hashes->each; +sub has_followers { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	return $db->select( 'followers', 'count(*) as count', { self_id => $uid } ) +	  ->hash->{count};  }  sub get_follow_requests { @@ -1043,6 +1113,16 @@ sub get_followees {  	return $res->hashes->each;  } +sub has_followees { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	return $db->select( 'followees', 'count(*) as count', { self_id => $uid } ) +	  ->hash->{count}; +} +  sub get_blocked_users {  	my ( $self, %opt ) = @_; @@ -1055,4 +1135,14 @@ sub get_blocked_users {  	return $res->hashes->each;  } +sub has_blocked_users { +	my ( $self, %opt ) = @_; + +	my $db  = $opt{db} // $self->{pg}->db; +	my $uid = $opt{uid}; + +	return $db->select( 'blocked_users', 'count(*) as count', +		{ self_id => $uid } )->hash->{count}; +} +  1; | 
