Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Purge inactive friends and avoids #541

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
jauggy marked this conversation as resolved.
Show resolved Hide resolved
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.")
jauggy marked this conversation as resolved.
Show resolved Hide resolved

{: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 %>
jauggy marked this conversation as resolved.
Show resolved Hide resolved
</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
Loading