Merge branch 'issue/2069' into 'develop'
authorHaelwenn <contact+git.pleroma.social@hacktivis.me>
Thu, 29 Oct 2020 23:39:15 +0000 (23:39 +0000)
committerHaelwenn <contact+git.pleroma.social@hacktivis.me>
Thu, 29 Oct 2020 23:39:15 +0000 (23:39 +0000)
[#2069] unread_conversation_count

See merge request pleroma/pleroma!2939

1  2 
lib/pleroma/user.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
test/pleroma/conversation/participation_test.exs
test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs

diff --combined lib/pleroma/user.ex
index a1e546b2d165573eb79ea9d38c27e2d8ca6615a5,7fc7a533edfa068149e2b26fe1029658ab3331d4..059d94e30f2361f3152e6502c66892f3644ad4e5
@@@ -25,6 -25,7 +25,6 @@@ defmodule Pleroma.User d
    alias Pleroma.Object
    alias Pleroma.Registration
    alias Pleroma.Repo
 -  alias Pleroma.RepoStreamer
    alias Pleroma.User
    alias Pleroma.UserRelationship
    alias Pleroma.Web
@@@ -82,7 -83,7 +82,7 @@@
    ]
  
    schema "users" do
 -    field(:bio, :string)
 +    field(:bio, :string, default: "")
      field(:raw_bio, :string)
      field(:email, :string)
      field(:name, :string)
      field(:note_count, :integer, default: 0)
      field(:follower_count, :integer, default: 0)
      field(:following_count, :integer, default: 0)
 -    field(:locked, :boolean, default: false)
 +    field(:is_locked, :boolean, default: false)
      field(:confirmation_pending, :boolean, default: false)
      field(:password_reset_pending, :boolean, default: false)
      field(:approval_pending, :boolean, default: false)
      field(:hide_followers, :boolean, default: false)
      field(:hide_follows, :boolean, default: false)
      field(:hide_favorites, :boolean, default: true)
-     field(:unread_conversation_count, :integer, default: 0)
      field(:pinned_activities, {:array, :string}, default: [])
      field(:email_notifications, :map, default: %{"digest" => false})
      field(:mascot, :map, default: nil)
      field(:pleroma_settings_store, :map, default: %{})
      field(:fields, {:array, :map}, default: [])
      field(:raw_fields, {:array, :map}, default: [])
 -    field(:discoverable, :boolean, default: false)
 +    field(:is_discoverable, :boolean, default: false)
      field(:invisible, :boolean, default: false)
      field(:allow_following_move, :boolean, default: true)
      field(:skip_thread_containment, :boolean, default: false)
    @spec account_status(User.t()) :: account_status()
    def account_status(%User{deactivated: true}), do: :deactivated
    def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
 -  def account_status(%User{approval_pending: true}), do: :approval_pending
 +  def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
  
 -  def account_status(%User{confirmation_pending: true}) do
 +  def account_status(%User{local: true, confirmation_pending: true}) do
      if Config.get([:instance, :account_activation_required]) do
        :confirmation_pending
      else
        params,
        [
          :bio,
 -        :name,
          :emoji,
          :ap_id,
          :inbox,
          :avatar,
          :ap_enabled,
          :banner,
 -        :locked,
 +        :is_locked,
          :last_refreshed_at,
          :uri,
          :follower_address,
          :follower_count,
          :fields,
          :following_count,
 -        :discoverable,
 +        :is_discoverable,
          :invisible,
          :actor_type,
          :also_known_as,
          :accepts_chat_messages
        ]
      )
 -    |> validate_required([:name, :ap_id])
 +    |> cast(params, [:name], empty_values: [])
 +    |> validate_required([:ap_id])
 +    |> validate_required([:name], trim: false)
      |> unique_constraint(:nickname)
      |> validate_format(:nickname, @email_regex)
      |> validate_length(:bio, max: bio_limit)
          :public_key,
          :inbox,
          :shared_inbox,
 -        :locked,
 +        :is_locked,
          :no_rich_text,
          :default_scope,
          :banner,
          :fields,
          :raw_fields,
          :pleroma_settings_store,
 -        :discoverable,
 +        :is_discoverable,
          :actor_type,
          :also_known_as,
          :accepts_chat_messages
      follow_all(user, autofollowed_users)
    end
  
 +  defp autofollowing_users(user) do
 +    candidates = Config.get([:instance, :autofollowing_nicknames])
 +
 +    User.Query.build(%{nickname: candidates, local: true, deactivated: false})
 +    |> Repo.all()
 +    |> Enum.each(&follow(&1, user, :follow_accept))
 +
 +    {:ok, :success}
 +  end
 +
    @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
    def register(%Ecto.Changeset{} = changeset) do
      with {:ok, user} <- Repo.insert(changeset) do
  
    def post_register_action(%User{} = user) do
      with {:ok, user} <- autofollow_users(user),
 +         {:ok, _} <- autofollowing_users(user),
           {:ok, user} <- set_cache(user),
           {:ok, _} <- send_welcome_email(user),
           {:ok, _} <- send_welcome_message(user),
    def send_welcome_email(_), do: {:ok, :noop}
  
    @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
 -  def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
 +  def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
 +      when is_binary(email) do
      if Config.get([:instance, :account_activation_required]) do
        send_confirmation_email(user)
        {:ok, :enqueued}
    @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
  
    # "Locked" (self-locked) users demand explicit authorization of follow requests
 -  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
 +  def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do
      follow(follower, followed, :follow_pending)
    end
  
          FollowingRelationship.unfollow(follower, followed)
          {:ok, followed} = update_follower_count(followed)
  
 -        {:ok, follower} =
 -          follower
 -          |> update_following_count()
 +        {:ok, follower} = update_following_count(follower)
  
          {:ok, follower, followed}
  
    end
  
    def locked?(%User{} = user) do
 -    user.locked || false
 +    user.is_locked || false
    end
  
    def get_by_id(id) do
      User.Query.build(%{followers: user, deactivated: false})
    end
  
 -  def get_followers_query(user, page) do
 +  def get_followers_query(%User{} = user, page) do
      user
      |> get_followers_query(nil)
      |> User.Query.paginate(page, 20)
    end
  
    @spec get_followers_query(User.t()) :: Ecto.Query.t()
 -  def get_followers_query(user), do: get_followers_query(user, nil)
 +  def get_followers_query(%User{} = user), do: get_followers_query(user, nil)
  
    @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
 -  def get_followers(user, page \\ nil) do
 +  def get_followers(%User{} = user, page \\ nil) do
      user
      |> get_followers_query(page)
      |> Repo.all()
    end
  
    @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
 -  def get_external_followers(user, page \\ nil) do
 +  def get_external_followers(%User{} = user, page \\ nil) do
      user
      |> get_followers_query(page)
      |> User.Query.build(%{external: true})
      |> Repo.all()
    end
  
 -  def get_followers_ids(user, page \\ nil) do
 +  def get_followers_ids(%User{} = user, page \\ nil) do
      user
      |> get_followers_query(page)
      |> select([u], u.id)
      User.Query.build(%{friends: user, deactivated: false})
    end
  
 -  def get_friends_query(user, page) do
 +  def get_friends_query(%User{} = user, page) do
      user
      |> get_friends_query(nil)
      |> User.Query.paginate(page, 20)
    end
  
    @spec get_friends_query(User.t()) :: Ecto.Query.t()
 -  def get_friends_query(user), do: get_friends_query(user, nil)
 +  def get_friends_query(%User{} = user), do: get_friends_query(user, nil)
  
 -  def get_friends(user, page \\ nil) do
 +  def get_friends(%User{} = user, page \\ nil) do
      user
      |> get_friends_query(page)
      |> Repo.all()
    end
  
 -  def get_friends_ap_ids(user) do
 +  def get_friends_ap_ids(%User{} = user) do
      user
      |> get_friends_query(nil)
      |> select([u], u.ap_id)
      |> Repo.all()
    end
  
 -  def get_friends_ids(user, page \\ nil) do
 +  def get_friends_ids(%User{} = user, page \\ nil) do
      user
      |> get_friends_query(page)
      |> select([u], u.id)
      |> update_and_set_cache()
    end
  
-   def set_unread_conversation_count(%User{local: true} = user) do
-     unread_query = Participation.unread_conversation_count_for_user(user)
-     User
-     |> join(:inner, [u], p in subquery(unread_query))
-     |> update([u, p],
-       set: [unread_conversation_count: p.count]
-     )
-     |> where([u], u.id == ^user.id)
-     |> select([u], u)
-     |> Repo.update_all([])
-     |> case do
-       {1, [user]} -> set_cache(user)
-       _ -> {:error, user}
-     end
-   end
-   def set_unread_conversation_count(user), do: {:ok, user}
-   def increment_unread_conversation_count(conversation, %User{local: true} = user) do
-     unread_query =
-       Participation.unread_conversation_count_for_user(user)
-       |> where([p], p.conversation_id == ^conversation.id)
-     User
-     |> join(:inner, [u], p in subquery(unread_query))
-     |> update([u, p],
-       inc: [unread_conversation_count: 1]
-     )
-     |> where([u], u.id == ^user.id)
-     |> where([u, p], p.count == 0)
-     |> select([u], u)
-     |> Repo.update_all([])
-     |> case do
-       {1, [user]} -> set_cache(user)
-       _ -> {:error, user}
-     end
-   end
-   def increment_unread_conversation_count(_, user), do: {:ok, user}
    @spec get_users_from_set([String.t()], keyword()) :: [User.t()]
    def get_users_from_set(ap_ids, opts \\ []) do
      local_only = Keyword.get(opts, :local_only, true)
      # "Right to be forgotten"
      # https://gdpr.eu/right-to-be-forgotten/
      change(user, %{
 -      bio: nil,
 +      bio: "",
        raw_bio: nil,
        email: nil,
        name: nil,
        note_count: 0,
        follower_count: 0,
        following_count: 0,
 -      locked: false,
 +      is_locked: false,
        confirmation_pending: false,
        password_reset_pending: false,
        approval_pending: false,
        pleroma_settings_store: %{},
        fields: [],
        raw_fields: [],
 -      discoverable: false,
 +      is_discoverable: false,
        also_known_as: []
      })
    end
  
    def perform(:deactivate_async, user, status), do: deactivate(user, status)
  
 -  @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
 -  def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
 -      when is_list(blocked_identifiers) do
 -    Enum.map(
 -      blocked_identifiers,
 -      fn blocked_identifier ->
 -        with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
 -             {:ok, _block} <- CommonAPI.block(blocker, blocked) do
 -          blocked
 -        else
 -          err ->
 -            Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
 -            err
 -        end
 -      end
 -    )
 -  end
 -
 -  def perform(:follow_import, %User{} = follower, followed_identifiers)
 -      when is_list(followed_identifiers) do
 -    Enum.map(
 -      followed_identifiers,
 -      fn followed_identifier ->
 -        with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
 -             {:ok, follower} <- maybe_direct_follow(follower, followed),
 -             {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
 -          followed
 -        else
 -          err ->
 -            Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
 -            err
 -        end
 -      end
 -    )
 -  end
 -
    @spec external_users_query() :: Ecto.Query.t()
    def external_users_query do
      User.Query.build(%{
      Repo.all(query)
    end
  
 -  def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
 -    BackgroundWorker.enqueue("blocks_import", %{
 -      "blocker_id" => blocker.id,
 -      "blocked_identifiers" => blocked_identifiers
 -    })
 -  end
 -
 -  def follow_import(%User{} = follower, followed_identifiers)
 -      when is_list(followed_identifiers) do
 -    BackgroundWorker.enqueue("follow_import", %{
 -      "follower_id" => follower.id,
 -      "followed_identifiers" => followed_identifiers
 -    })
 -  end
 -
    def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
      Notification
      |> join(:inner, [n], activity in assoc(n, :activity))
    def delete_user_activities(%User{ap_id: ap_id} = user) do
      ap_id
      |> Activity.Queries.by_actor()
 -    |> RepoStreamer.chunk_stream(50)
 +    |> Repo.chunk_stream(50, :batches)
      |> Stream.each(fn activities ->
        Enum.each(activities, fn activity -> delete_activity(activity, user) end)
      end)
  
    def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
  
 -  def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
 +  def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
  
 -  def get_or_fetch_by_ap_id(ap_id) do
 +  def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
      cached_user = get_cached_by_ap_id(ap_id)
  
 -    maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id)
 +    maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
  
      case {cached_user, maybe_fetched_user} do
        {_, {:ok, %User{} = user}} ->
  
    def public_key(_), do: {:error, "key not found"}
  
 -  def get_public_key_for_ap_id(ap_id) do
 -    with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
 +  def get_public_key_for_ap_id(ap_id, opts \\ []) do
 +    with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts),
           {:ok, public_key} <- public_key(user) do
        {:ok, public_key}
      else
      Enum.map(users, &toggle_confirmation/1)
    end
  
 +  @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
 +  def need_confirmation(%User{} = user, bool) do
 +    user
 +    |> confirmation_changeset(need_confirmation: bool)
 +    |> update_and_set_cache()
 +  end
 +
    def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
      mascot
    end
        max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
        params = %{pinned_activities: user.pinned_activities ++ [id]}
  
 +      # if pinned activity was scheduled for deletion, we remove job
 +      if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do
 +        Oban.cancel_job(expiration.id)
 +      end
 +
        user
        |> cast(params, [:pinned_activities])
        |> validate_length(:pinned_activities,
      |> update_and_set_cache()
    end
  
 -  def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
 +  def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do
      params = %{pinned_activities: List.delete(user.pinned_activities, id)}
  
 +    # if pinned activity was scheduled for deletion, we reschedule it for deletion
 +    if data["expires_at"] do
 +      # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation
 +      {:ok, expires_at} =
 +        data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast()
 +
 +      Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
 +        activity_id: id,
 +        expires_at: expires_at
 +      })
 +    end
 +
      user
      |> cast(params, [:pinned_activities])
      |> update_and_set_cache()
index 82fdca5572984456403c76f565dc7a407195bcc0,1bf53600cbc2095ce9294f50465692ae57c1e8e3..3158d09ed8dcd11cd10ab82292c74acd814b5ac6
@@@ -181,10 -181,8 +181,10 @@@ defmodule Pleroma.Web.MastodonAPI.Accou
      user = User.sanitize_html(user, User.html_filter_policy(opts[:for]))
      display_name = user.name || user.nickname
  
 -    image = User.avatar_url(user) |> MediaProxy.url()
 +    avatar = User.avatar_url(user) |> MediaProxy.url()
 +    avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
      header = User.banner_url(user) |> MediaProxy.url()
 +    header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
  
      following_count =
        if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
        username: username_from_nickname(user.nickname),
        acct: user.nickname,
        display_name: display_name,
 -      locked: user.locked,
 +      locked: user.is_locked,
        created_at: Utils.to_masto_date(user.inserted_at),
        followers_count: followers_count,
        following_count: following_count,
        statuses_count: user.note_count,
 -      note: user.bio || "",
 +      note: user.bio,
        url: user.uri || user.ap_id,
 -      avatar: image,
 -      avatar_static: image,
 +      avatar: avatar,
 +      avatar_static: avatar_static,
        header: header,
 -      header_static: header,
 +      header_static: header_static,
        emojis: emojis,
        fields: user.fields,
        bot: bot,
          sensitive: false,
          fields: user.raw_fields,
          pleroma: %{
 -          discoverable: user.discoverable,
 +          discoverable: user.is_discoverable,
            actor_type: user.actor_type
          }
        },
      data
      |> Kernel.put_in(
        [:pleroma, :unread_conversation_count],
-       user.unread_conversation_count
+       Pleroma.Conversation.Participation.unread_count(user)
      )
    end
  
index 59a1b6492d75df3e0924e69a1c5183c1522686dc,5a603dcc1cdb18994cbcb81f1efb8a409c35030f..5a603dcc1cdb18994cbcb81f1efb8a409c35030f
@@@ -37,9 -37,8 +37,8 @@@ defmodule Pleroma.Conversation.Particip
  
      [%{read: true}] = Participation.for_user(user)
      [%{read: false} = participation] = Participation.for_user(other_user)
-     assert User.get_cached_by_id(user.id).unread_conversation_count == 0
-     assert User.get_cached_by_id(other_user.id).unread_conversation_count == 1
+     assert Participation.unread_count(user) == 0
+     assert Participation.unread_count(other_user) == 1
  
      {:ok, _} =
        CommonAPI.post(other_user, %{
@@@ -54,8 -53,8 +53,8 @@@
      [%{read: false}] = Participation.for_user(user)
      [%{read: true}] = Participation.for_user(other_user)
  
-     assert User.get_cached_by_id(user.id).unread_conversation_count == 1
-     assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
+     assert Participation.unread_count(user) == 1
+     assert Participation.unread_count(other_user) == 0
    end
  
    test "for a new conversation, it sets the recipents of the participation" do
        assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
                 Participation.for_user(blocker)
  
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
+       assert Participation.unread_count(blocker) == 4
  
        {:ok, _user_relationship} = User.block(blocker, blocked)
  
        assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
                 Participation.for_user(blocker)
  
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 1
+       assert Participation.unread_count(blocker) == 1
  
        # The conversation is not marked as read for the blocked user
        assert [_, _, %{read: false}] = Participation.for_user(blocked)
-       assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+       assert Participation.unread_count(blocker) == 1
  
        # The conversation is not marked as read for the third user
        assert [%{read: false}, _, _] = Participation.for_user(third_user)
-       assert User.get_cached_by_id(third_user.id).unread_conversation_count == 1
+       assert Participation.unread_count(third_user) == 1
      end
  
      test "the new conversation with the blocked user is not marked as unread " do
          })
  
        assert [%{read: true}] = Participation.for_user(blocker)
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+       assert Participation.unread_count(blocker) == 0
  
        # When the blocked user is a recipient
        {:ok, _direct2} =
          })
  
        assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+       assert Participation.unread_count(blocker) == 0
  
        assert [%{read: false}, _] = Participation.for_user(blocked)
-       assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+       assert Participation.unread_count(blocked) == 1
      end
  
      test "the conversation with the blocked user is not marked as unread on a reply" do
  
        {:ok, _user_relationship} = User.block(blocker, blocked)
        assert [%{read: true}] = Participation.for_user(blocker)
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
  
+       assert Participation.unread_count(blocker) == 0
        assert [blocked_participation] = Participation.for_user(blocked)
  
        # When it's a reply from the blocked user
          })
  
        assert [%{read: true}] = Participation.for_user(blocker)
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
  
+       assert Participation.unread_count(blocker) == 0
        assert [third_user_participation] = Participation.for_user(third_user)
  
        # When it's a reply from the third user
          })
  
        assert [%{read: true}] = Participation.for_user(blocker)
-       assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+       assert Participation.unread_count(blocker) == 0
  
        # Marked as unread for the blocked user
        assert [%{read: false}] = Participation.for_user(blocked)
-       assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+       assert Participation.unread_count(blocked) == 1
      end
    end
  end
index 3e21e6bf178ddab076fbb7f2ebbb40cefbb81be1,b23b22752013359765111d5d867310491f2e429b..b23b22752013359765111d5d867310491f2e429b
@@@ -5,6 -5,7 +5,7 @@@
  defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
    use Pleroma.Web.ConnCase
  
+   alias Pleroma.Conversation.Participation
    alias Pleroma.User
    alias Pleroma.Web.CommonAPI
  
        user_three: user_three,
        conn: conn
      } do
-       assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+       assert Participation.unread_count(user_two) == 0
        {:ok, direct} = create_direct_message(user_one, [user_two, user_three])
  
-       assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
+       assert Participation.unread_count(user_two) == 1
  
        {:ok, _follower_only} =
          CommonAPI.post(user_one, %{
@@@ -59,7 -60,7 +60,7 @@@
        assert is_binary(res_id)
        assert unread == false
        assert res_last_status["id"] == direct.id
-       assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
+       assert Participation.unread_count(user_one) == 0
      end
  
      test "observes limit params", %{
      user_two = insert(:user)
      {:ok, direct} = create_direct_message(user_one, [user_two])
  
-     assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
-     assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
+     assert Participation.unread_count(user_one) == 0
+     assert Participation.unread_count(user_two) == 1
  
      user_two_conn =
        build_conn()
        |> post("/api/v1/conversations/#{direct_conversation_id}/read")
        |> json_response_and_validate_schema(200)
  
-     assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
-     assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+     assert Participation.unread_count(user_one) == 0
+     assert Participation.unread_count(user_two) == 0
  
      # The conversation is marked as unread on reply
      {:ok, _} =
        |> get("/api/v1/conversations")
        |> json_response_and_validate_schema(200)
  
-     assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
-     assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+     assert Participation.unread_count(user_one) == 1
+     assert Participation.unread_count(user_two) == 0
  
      # A reply doesn't increment the user's unread_conversation_count if the conversation is unread
      {:ok, _} =
          in_reply_to_status_id: direct.id
        })
  
-     assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
-     assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
+     assert Participation.unread_count(user_one) == 1
+     assert Participation.unread_count(user_two) == 0
    end
  
    test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do
index e6d0b3e371fca65059ed83cfc1e4c19b6c839983,f2feeaaef1f111220cdf4e21157ee653a08bcc54..f2feeaaef1f111220cdf4e21157ee653a08bcc54
@@@ -121,7 -121,7 +121,7 @@@ defmodule Pleroma.Web.PleromaAPI.Conver
      [participation2, participation1] = Participation.for_user(other_user)
      assert Participation.get(participation2.id).read == false
      assert Participation.get(participation1.id).read == false
-     assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2
+     assert Participation.unread_count(other_user) == 2
  
      [%{"unread" => false}, %{"unread" => false}] =
        conn
      [participation2, participation1] = Participation.for_user(other_user)
      assert Participation.get(participation2.id).read == true
      assert Participation.get(participation1.id).read == true
-     assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
+     assert Participation.unread_count(other_user) == 0
    end
  end