aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Chvanikov <chvanikoff@gmail.com>2019-04-19 22:17:54 +0700
committerRoman Chvanikov <chvanikoff@gmail.com>2019-04-19 22:19:00 +0700
commit8add1194448cfc183dce01b86451422195d44023 (patch)
treeb61d01c03933a3d6a35421199e3ef84e40efdd2d
parentaeafa0b2ef996f15f9ff4a6ade70a693b12b208f (diff)
downloadpleroma-8add1194448cfc183dce01b86451422195d44023.tar.gz
Add User.list_inactive_users_query/1
-rw-r--r--lib/pleroma/user.ex38
-rw-r--r--priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs9
-rw-r--r--test/user_test.exs103
3 files changed, 150 insertions, 0 deletions
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 0982f6ed8..c67a7b7a1 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1447,4 +1447,42 @@ defmodule Pleroma.User do
def showing_reblogs?(%User{} = user, %User{} = target) do
target.ap_id not in user.info.muted_reblogs
end
+
+ @doc """
+ The function returns a query to get users with no activity for given interval of days.
+ Inactive users are those who didn't read any notification, or had any activity where
+ the user is the activity's actor, during `inactivity_threshold` days.
+ Deactivated users will not appear in this list.
+
+ ## Examples
+
+ iex> Pleroma.User.list_inactive_users()
+ %Ecto.Query{}
+ """
+ @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
+ def list_inactive_users_query(inactivity_threshold \\ 7) do
+ negative_inactivity_threshold = -inactivity_threshold
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+ # Subqueries are not supported in `where` clauses, join gets too complicated.
+ has_read_notifications =
+ from(n in Pleroma.Notification,
+ where: n.seen == true,
+ group_by: n.id,
+ having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
+ select: n.user_id
+ )
+ |> Pleroma.Repo.all()
+
+ from(u in Pleroma.User,
+ left_join: a in Pleroma.Activity,
+ on: u.ap_id == a.actor,
+ where: not is_nil(u.nickname),
+ where: fragment("not (?->'deactivated' @> 'true')", u.info),
+ where: u.id not in ^has_read_notifications,
+ group_by: u.id,
+ having:
+ max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
+ is_nil(max(a.inserted_at))
+ )
+ end
end
diff --git a/priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs b/priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs
new file mode 100644
index 000000000..4312b171f
--- /dev/null
+++ b/priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddSigninAndLastDigestDatesToUser do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add(:last_digest_emailed_at, :naive_datetime, default: fragment("now()"))
+ end
+ end
+end
diff --git a/test/user_test.exs b/test/user_test.exs
index d2167a970..ba02997dc 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1167,4 +1167,107 @@ defmodule Pleroma.UserTest do
assert Map.get(user_show, "followers_count") == 2
end
+
+ describe "list_inactive_users_query/1" do
+ defp days_ago(days) do
+ NaiveDateTime.add(
+ NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+ -days * 60 * 60 * 24,
+ :second
+ )
+ end
+
+ test "Users are inactive by default" do
+ total = 10
+
+ users =
+ Enum.map(1..total, fn _ ->
+ insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+ end)
+
+ inactive_users_ids =
+ Pleroma.User.list_inactive_users_query()
+ |> Pleroma.Repo.all()
+ |> Enum.map(& &1.id)
+
+ Enum.each(users, fn user ->
+ assert user.id in inactive_users_ids
+ end)
+ end
+
+ test "Only includes users who has no recent activity" do
+ total = 10
+
+ users =
+ Enum.map(1..total, fn _ ->
+ insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+ end)
+
+ {inactive, active} = Enum.split(users, trunc(total / 2))
+
+ Enum.map(active, fn user ->
+ to = Enum.random(users -- [user])
+
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
+ "status" => "hey @#{to.nickname}"
+ })
+ end)
+
+ inactive_users_ids =
+ Pleroma.User.list_inactive_users_query()
+ |> Pleroma.Repo.all()
+ |> Enum.map(& &1.id)
+
+ Enum.each(active, fn user ->
+ refute user.id in inactive_users_ids
+ end)
+
+ Enum.each(inactive, fn user ->
+ assert user.id in inactive_users_ids
+ end)
+ end
+
+ test "Only includes users with no read notifications" do
+ total = 10
+
+ users =
+ Enum.map(1..total, fn _ ->
+ insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+ end)
+
+ [sender | recipients] = users
+ {inactive, active} = Enum.split(recipients, trunc(total / 2))
+
+ Enum.each(recipients, fn to ->
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
+ "status" => "hey @#{to.nickname}"
+ })
+
+ {:ok, _} =
+ Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
+ "status" => "hey again @#{to.nickname}"
+ })
+ end)
+
+ Enum.each(active, fn user ->
+ [n1, _n2] = Pleroma.Notification.for_user(user)
+ {:ok, _} = Pleroma.Notification.read_one(user, n1.id)
+ end)
+
+ inactive_users_ids =
+ Pleroma.User.list_inactive_users_query()
+ |> Pleroma.Repo.all()
+ |> Enum.map(& &1.id)
+
+ Enum.each(active, fn user ->
+ refute user.id in inactive_users_ids
+ end)
+
+ Enum.each(inactive, fn user ->
+ assert user.id in inactive_users_ids
+ end)
+ end
+ end
end