From 9332f60a4370cc5ec9a000ef505e613f84b17d2f Mon Sep 17 00:00:00 2001
From: Derf Null <derf@finalrewind.org>
Date: Wed, 31 May 2023 22:16:27 +0200
Subject: prepare for follow relations and follow-only checkins

---
 lib/Travelynx/Command/database.pm |  48 ++++++++++
 lib/Travelynx/Model/Users.pm      | 194 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 242 insertions(+)

(limited to 'lib')

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;
-- 
cgit v1.2.3