Skip to content

Commit

Permalink
Purge inactive friends and avoids (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
jauggy authored Dec 26, 2024
1 parent b8d22a8 commit 89fd9fa
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 2 deletions.
3 changes: 2 additions & 1 deletion lib/teiserver/account/libs/account_test_lib.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ defmodule Teiserver.Account.AccountTestLib do
name: data["name"] || "name_#{r}",
email: data["email"] || "email_#{r}",
colour: data["colour"] || "colour",
icon: data["icon"] || "icon"
icon: data["icon"] || "icon",
last_login_timex: data["last_login_timex"] || Timex.now()
},
:script
)
Expand Down
44 changes: 44 additions & 0 deletions lib/teiserver/account/libs/relationship_lib.ex
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,48 @@ defmodule Teiserver.Account.RelationshipLib do

results.rows
end

# Deletes inactive users in the ignore/avoid/block list of a user
# Returns the number of deletions
def delete_inactive_ignores_avoids_blocks(user_id, days_not_logged_in) do
query = """
delete from account_relationships ar
using account_users au
where au.id = ar.to_user_id
and ar.from_user_id = $1
and (au.last_login_timex is null OR
abs(DATE_PART('day', (now()- au.last_login_timex ))) > $2);
"""

results =
Ecto.Adapters.SQL.query!(Repo, query, [
user_id,
days_not_logged_in
])

decache_relationships(user_id)

results.num_rows
end

def get_inactive_ignores_avoids_blocks_count(user_id, days_not_logged_in) do
query =
"""
select count(*) from account_relationships ar
join account_users au
on au.id = ar.to_user_id
and ar.from_user_id = $1
and (au.last_login_timex is null OR
abs(DATE_PART('day', (now()- au.last_login_timex ))) > $2);
"""

result =
Ecto.Adapters.SQL.query!(Repo, query, [
user_id,
days_not_logged_in
])

[[inactive_count]] = result.rows
inactive_count
end
end
134 changes: 134 additions & 0 deletions lib/teiserver_web/live/account/relationship/index.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule TeiserverWeb.Account.RelationshipLive.Index do
@moduledoc false
alias Teiserver.Account.RelationshipLib
use TeiserverWeb, :live_view
alias Teiserver.Account

Expand All @@ -12,6 +13,8 @@ defmodule TeiserverWeb.Account.RelationshipLive.Index do
|> assign(:view_colour, Account.RelationshipLib.colour())
|> assign(:show_help, false)
|> put_empty_relationships
|> assign(:purge_cutoff, get_default_purge_cutoff_option())
|> assign(:purge_cutoff_options, get_purge_cutoff_options())

{:ok, socket}
end
Expand Down Expand Up @@ -54,6 +57,14 @@ defmodule TeiserverWeb.Account.RelationshipLive.Index do
|> update_user_search
end

defp apply_action(socket, :clean, _params) do
socket
|> assign(:page_title, "Relationships - Cleanup")
|> assign(:tab, :clean)
|> get_friends()
|> get_inactive_relationship_count()
end

@impl true
def handle_event("show-help", _, socket) do
{:noreply, socket |> assign(:show_help, true)}
Expand Down Expand Up @@ -255,6 +266,56 @@ defmodule TeiserverWeb.Account.RelationshipLive.Index do
{:noreply, socket}
end

def handle_event("purge-avoids", _params, socket) do
userid = socket.assigns.current_user.id
duration = socket.assigns[:purge_cutoff]
days = get_purge_days_cutoff(duration)
num_rows = RelationshipLib.delete_inactive_ignores_avoids_blocks(userid, days)

socket =
socket |> assign(:purge_avoids_message, "#{num_rows} inactive users purged.")

{:noreply, socket}
end

def handle_event("purge-friends", _params, socket) do
duration = socket.assigns[:purge_cutoff]
days_cutoff = get_purge_days_cutoff(duration)

# Get all friends of this user
friends = socket.assigns[:friends]

num_friends_deleted =
get_inactive_friends(friends, days_cutoff)
|> Enum.map(fn friend ->
Account.delete_friend(friend)
nil
end)
|> length()

socket =
socket |> assign(:purge_friends_message, "#{num_friends_deleted} inactive friends purged.")

{:noreply, socket}
end

@doc """
Handles the dropdown for purge cutoff time
"""
@impl true
def handle_event("update-purge-cutoff", event, socket) do
[key] = event["_target"]
value = event[key]

socket =
socket
|> assign(:purge_cutoff, value)
|> get_inactive_relationship_count()
|> get_inactive_friend_count()

{:noreply, socket}
end

defp update_user_search(
%{assigns: %{live_action: :search, search_terms: terms} = assigns} = socket
) do
Expand Down Expand Up @@ -320,6 +381,20 @@ defmodule TeiserverWeb.Account.RelationshipLive.Index do
|> assign(:found_relationship, nil)
|> assign(:found_friendship, nil)
|> assign(:found_friendship_request, nil)
|> assign(:inactive_friend_count, 0)
end

defp get_inactive_relationship_count(
%{assigns: %{current_user: current_user, purge_cutoff: purge_cutoff}} = socket
) do
user_id = current_user.id
days = get_purge_days_cutoff(purge_cutoff)

inactive_relationship_count =
RelationshipLib.get_inactive_ignores_avoids_blocks_count(user_id, days)

socket = socket |> assign(:inactive_relationship_count, inactive_relationship_count)
socket
end

defp get_friends(%{assigns: %{current_user: current_user}} = socket) do
Expand Down Expand Up @@ -362,6 +437,7 @@ defmodule TeiserverWeb.Account.RelationshipLive.Index do
|> assign(:incoming_friend_requests, incoming_friend_requests)
|> assign(:outgoing_friend_requests, outgoing_friend_requests)
|> assign(:friends, friends)
|> get_inactive_friend_count()
end

defp get_follows(%{assigns: %{current_user: current_user}} = socket) do
Expand Down Expand Up @@ -413,4 +489,62 @@ defmodule TeiserverWeb.Account.RelationshipLive.Index do
|> assign(:ignores, ignores)
|> assign(:blocks, blocks)
end

def get_purge_cutoff_options() do
["1 month", "3 months", "6 months", "1 year"]
end

def get_default_purge_cutoff_option() do
"6 months"
end

@spec get_purge_days_cutoff(String.t()) :: float()
def get_purge_days_cutoff(duration) do
with [_, raw_number, type] <- Regex.run(~r/(\d)+ (month|year)/, duration),
{number, ""} <- Integer.parse(raw_number) do
cond do
type == "year" ->
number * 365

type == "month" ->
number * 365.0 / 12
end
else
nil -> {:error, "invalid duration passed: #{duration}"}
{_, _rest} -> {:error, "invalid number in duration #{duration}"}
end
end

defp get_inactive_friend_count(socket) do
friends = socket.assigns.friends
purge_cutoff = socket.assigns.purge_cutoff

socket =
socket |> assign(:inactive_friend_count, get_inactive_friend_count(friends, purge_cutoff))

socket
end

defp get_inactive_friend_count(friends, purge_cutoff_text) do
days_cutoff = get_purge_days_cutoff(purge_cutoff_text)

get_inactive_friends(friends, days_cutoff)
|> length()
end

def get_inactive_friends(friends, days_cutoff) do
Enum.filter(friends, fn friend ->
last_login = friend.other_user.last_login_timex
days = get_days_diff(last_login, Timex.now())
days > days_cutoff
end)
end

def get_days_diff(datetime1, datetime2) do
cond do
datetime1 == nil -> 0
datetime2 == nil -> 0
true -> abs(DateTime.diff(datetime1, datetime2, :day))
end
end
end
62 changes: 62 additions & 0 deletions lib/teiserver_web/live/account/relationship/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<.tab_nav url={~p"/account/relationship/search"} selected={@tab == :search}>
<Fontawesome.icon icon="search" style="solid" /> Search users
</.tab_nav>
<.tab_nav url={~p"/account/relationship/clean"} selected={@tab == :clean}>
<Fontawesome.icon icon="broom" style="solid" /> Cleanup
</.tab_nav>
</.tab_header>
</div>
</div>
Expand Down Expand Up @@ -412,3 +415,62 @@
</div>
</div>
</div>

<div :if={@live_action == :clean} class="row mt-2 mb-3">
<div class="col-xl-6">
<h4 class="text-danger">
Avoid Cleanup
</h4>

<form method="post" class="col-md-6 ">
<span>
Purge users from your ignore, avoid, and block list that have not logged in for:
</span>
<.input
type="select"
options={@purge_cutoff_options}
name="algorithm"
value={@purge_cutoff}
phx-change="update-purge-cutoff"
/>
<div
class="btn btn-danger btn-sm col-sm-12 mt-2"
phx-click="purge-avoids"
data-confirm={"Are you sure you want to purge inactive users from your ignore/avoid/block lists? This will remove #{@inactive_relationship_count} users."}
>
Purge inactive ignores / avoids / blocks
</div>
<%= if(assigns[:purge_avoids_message]) do %>
<p class="mt-2"><%= @purge_avoids_message %></p>
<% end %>
</form>
<br />
<br />
<br />
<h4 class="text-success">
Friend Cleanup
</h4>
<form method="post" class="col-md-6">
<span>
Purge users from your friend list that have not logged in for:
</span>
<.input
type="select"
options={@purge_cutoff_options}
name="algorithm"
value={@purge_cutoff}
phx-change="update-purge-cutoff"
/>
<div
class="btn btn-success btn-sm col-sm-12 mt-2"
phx-click="purge-friends"
data-confirm={"Are you sure you want to purge inactive users from your friend list? This will remove #{@inactive_friend_count} friends."}
>
Purge inactive Friends
</div>
<%= if(assigns[:purge_friends_message]) do %>
<p class="mt-2"><%= @purge_friends_message %></p>
<% end %>
</form>
</div>
</div>
1 change: 1 addition & 0 deletions lib/teiserver_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ defmodule TeiserverWeb.Router do
live "/relationship/follow", RelationshipLive.Index, :follow
live "/relationship/avoid", RelationshipLive.Index, :avoid
live "/relationship/search", RelationshipLive.Index, :search
live "/relationship/clean", RelationshipLive.Index, :clean
end

live_session :account_settings,
Expand Down
58 changes: 58 additions & 0 deletions test/teiserver/account/relationship_lib_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule Teiserver.Account.RelationshipLibTest do
use Teiserver.DataCase, async: true

alias Teiserver.Account.AccountTestLib
alias Teiserver.Account.RelationshipLib

test "purging inactive relationships" do
# Create two accounts
user1 = AccountTestLib.user_fixture()
user2 = AccountTestLib.user_fixture()

old_login = DateTime.add(Timex.now(), -31, :day)

user3 =
AccountTestLib.user_fixture(%{
"last_login_timex" => old_login
})

assert user1.id != nil
assert user2.id != nil
assert user3.id != nil

RelationshipLib.avoid_user(user1.id, user2.id)
RelationshipLib.avoid_user(user1.id, user3.id)

avoid_list = RelationshipLib.list_userids_avoided_by_userid(user1.id)
assert Enum.member?(avoid_list, user2.id)
assert Enum.member?(avoid_list, user3.id)

# Check count of inactive relationships
inactive_count = RelationshipLib.get_inactive_ignores_avoids_blocks_count(user1.id, 30)
assert inactive_count == 1

# Purge old relationships
RelationshipLib.delete_inactive_ignores_avoids_blocks(user1.id, 30)

avoid_list = RelationshipLib.list_userids_avoided_by_userid(user1.id)
assert Enum.member?(avoid_list, user2.id)
refute Enum.member?(avoid_list, user3.id)

# Add users to ignore list
RelationshipLib.ignore_user(user1.id, user2.id)
RelationshipLib.ignore_user(user1.id, user3.id)

# Check ignores
ignore_list = RelationshipLib.list_userids_ignored_by_userid(user1.id)
assert Enum.member?(ignore_list, user2.id)
assert Enum.member?(ignore_list, user3.id)

# Purge ignores
RelationshipLib.delete_inactive_ignores_avoids_blocks(user1.id, 30)

# Check ignores again
ignore_list = RelationshipLib.list_userids_ignored_by_userid(user1.id)
assert Enum.member?(ignore_list, user2.id)
refute Enum.member?(ignore_list, user3.id)
end
end
Loading

0 comments on commit 89fd9fa

Please sign in to comment.