Merge remote-tracking branch 'pleroma/develop' into feature/disable-account
authorEgor Kislitsyn <egor@kislitsyn.com>
Tue, 7 May 2019 09:51:11 +0000 (16:51 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Tue, 7 May 2019 09:51:11 +0000 (16:51 +0700)
1  2 
config/config.exs
lib/pleroma/activity.ex
lib/pleroma/user.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/twitter_api.ex
test/user_test.exs

diff --combined config/config.exs
index 80f0c3f2590aefa2f5efc3553a48cb0006477d24,1e64b79a7a043ed4b4194c23e4910a7793c6e003..a89afd419c35908145762bb677c289199d7d56cb
@@@ -221,7 -221,8 +221,8 @@@ config :pleroma, :instance
    allowed_post_formats: [
      "text/plain",
      "text/html",
-     "text/markdown"
+     "text/markdown",
+     "text/bbcode"
    ],
    mrf_transparency: true,
    autofollowed_nicknames: [],
@@@ -326,7 -327,8 +327,8 @@@ config :pleroma, :media_proxy
        follow_redirect: true,
        pool: :media
      ]
-   ]
+   ],
+   whitelist: []
  
  config :pleroma, :chat, enabled: true
  
@@@ -415,7 -417,7 +417,8 @@@ config :pleroma_job_queue, :queues
    mailer: 10,
    transmogrifier: 20,
    scheduled_activities: 10,
 -  background: 5
++  background: 5,
 +  user: 10
  
  config :pleroma, :fetch_initial_posts,
    enabled: false,
@@@ -442,6 -444,9 +445,9 @@@ config :pleroma, :ldap
    base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
    uid: System.get_env("LDAP_UID") || "cn"
  
+ config :esshd,
+   enabled: false
  oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
  
  ueberauth_providers =
@@@ -467,6 -472,10 +473,10 @@@ config :pleroma, Pleroma.ScheduledActiv
    total_user_limit: 300,
    enabled: true
  
+ config :pleroma, :oauth2,
+   token_expires_in: 600,
+   issue_new_refresh_token: true
  # Import environment specific config. This must remain at the bottom
  # of this file so it overrides the configuration defined above.
  import_config "#{Mix.env()}.exs"
diff --combined lib/pleroma/activity.ex
index 9c1c804e060640bb8555923abe80bb131af6ca00,73e63bb144b6e83ef5cce904ade31ca6e452a0a5..2dcb97159736b99c8cf963d6c3e11ace14203667
@@@ -14,6 -14,8 +14,8 @@@ defmodule Pleroma.Activity d
    import Ecto.Query
  
    @type t :: %__MODULE__{}
+   @type actor :: String.t()
    @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
  
    # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
    end
  
    def get_by_id(id) do
 -    Repo.get(Activity, id)
 +    Activity
 +    |> where([a], a.id == ^id)
 +    |> restrict_deactivated_users()
 +    |> Repo.one()
    end
  
    def get_by_id_with_object(id) do
  
    def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
      create_by_object_ap_id(ap_id)
 +    |> restrict_deactivated_users()
      |> Repo.one()
    end
  
      |> Repo.all()
    end
  
+   @spec query_by_actor(actor()) :: Ecto.Query.t()
+   def query_by_actor(actor) do
+     from(a in Activity, where: a.actor == ^actor)
+   end
++
 +  def restrict_deactivated_users(query) do
 +    from(activity in query,
 +      where:
 +        fragment(
 +          "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
 +          activity.actor
 +        )
 +    )
 +  end
  end
diff --combined lib/pleroma/user.ex
index d103cd809645b6db381916579c30813ab139224d,fd2ce81ad3a99f73f55315a228d4e9e443b7a1fc..10ee01b8c1307388c9cb354f2d226b879bd84c5f
@@@ -10,7 -10,7 +10,7 @@@ defmodule Pleroma.User d
  
    alias Comeonin.Pbkdf2
    alias Pleroma.Activity
-   alias Pleroma.Formatter
+   alias Pleroma.Bookmark
    alias Pleroma.Notification
    alias Pleroma.Object
    alias Pleroma.Registration
@@@ -53,8 -53,8 +53,8 @@@
      field(:search_rank, :float, virtual: true)
      field(:search_type, :integer, virtual: true)
      field(:tags, {:array, :string}, default: [])
-     field(:bookmarks, {:array, :string}, default: [])
      field(:last_refreshed_at, :naive_datetime_usec)
+     has_many(:bookmarks, Bookmark)
      has_many(:notifications, Notification)
      has_many(:registrations, Registration)
      embeds_one(:info, Pleroma.User.Info)
    def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
  
    def user_info(%User{} = user) do
 -    oneself = if user.local, do: 1, else: 0
 -
      %{
 -      following_count: length(user.following) - oneself,
 +      following_count: following_count(user),
        note_count: user.info.note_count,
        follower_count: user.info.follower_count,
        locked: user.info.locked,
      }
    end
  
 +  defp restrict_deactivated(query) do
 +    from(u in query,
 +      where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
 +    )
 +  end
 +
 +  def following_count(%User{following: []}), do: 0
 +
 +  def following_count(%User{following: following, id: id}) do
 +    from(u in User,
 +      where: u.follower_address in ^following,
 +      where: u.id != ^id
 +    )
 +    |> restrict_deactivated()
 +    |> Repo.aggregate(:count, :id)
 +  end
 +
    def remote_user_creation(params) do
      params =
        params
      Enum.map(
        followed_identifiers,
        fn followed_identifier ->
-         with %User{} = followed <- get_or_fetch(followed_identifier),
+         with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
               {:ok, follower} <- maybe_direct_follow(follower, followed),
               {:ok, _} <- ActivityPub.follow(follower, followed) do
            followed
  
    def get_cached_by_nickname(nickname) do
      key = "nickname:#{nickname}"
-     Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
+     Cachex.fetch!(:user_cache, key, fn ->
+       user_result = get_or_fetch_by_nickname(nickname)
+       case user_result do
+         {:ok, user} -> {:commit, user}
+         {:error, _error} -> {:ignore, nil}
+       end
+     end)
    end
  
    def get_cached_by_nickname_or_id(nickname_or_id) do
  
    def get_or_fetch_by_nickname(nickname) do
      with %User{} = user <- get_by_nickname(nickname) do
-       user
+       {:ok, user}
      else
        _e ->
          with [_nick, _domain] <- String.split(nickname, "@"),
              {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
            end
  
-           user
+           {:ok, user}
          else
-           _e -> nil
+           _e -> {:error, "not found " <> nickname}
          end
      end
    end
        where: fragment("? <@ ?", ^[follower_address], u.following),
        where: u.id != ^id
      )
 +    |> restrict_deactivated()
    end
  
    def get_followers_query(user, page) do
        where: u.follower_address in ^following,
        where: u.id != ^id
      )
 +    |> restrict_deactivated()
    end
  
    def get_friends_query(user, page) do
  
      info_cng = User.Info.set_note_count(user.info, note_count)
  
 -    cng =
 -      change(user)
 -      |> put_embed(:info, info_cng)
 -
 -    update_and_set_cache(cng)
 +    user
 +    |> change()
 +    |> put_embed(:info, info_cng)
 +    |> update_and_set_cache()
    end
  
    def update_follower_count(%User{} = user) do
        |> where([u], ^user.follower_address in u.following)
        |> where([u], u.id != ^user.id)
        |> select([u], %{count: count(u.id)})
 +      |> restrict_deactivated()
  
      User
      |> where(id: ^user.id)
            ^processed_query
          )
      )
 +    |> restrict_deactivated()
    end
  
    defp trigram_search_subquery(term) do
        },
        where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
      )
 +    |> restrict_deactivated()
    end
  
    def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
      Enum.map(
        blocked_identifiers,
        fn blocked_identifier ->
-         with %User{} = blocked <- get_or_fetch(blocked_identifier),
+         with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
               {:ok, blocker} <- block(blocker, blocked),
               {:ok, _} <- ActivityPub.block(blocker, blocked) do
            blocked
      )
    end
  
 +  def deactivate_async(user, status \\ true) do
 +    PleromaJobQueue.enqueue(:user, __MODULE__, [:deactivate_async, user, status])
 +  end
 +
 +  def perform(:deactivate_async, user, status), do: deactivate(user, status)
 +
    def deactivate(%User{} = user, status \\ true) do
      info_cng = User.Info.set_activation_status(user.info, status)
  
 -    cng =
 -      change(user)
 -      |> put_embed(:info, info_cng)
 +    with {:ok, friends} <- User.get_friends(user),
 +         {:ok, followers} <- User.get_followers(user),
 +         {:ok, user} <-
 +           user
 +           |> change()
 +           |> put_embed(:info, info_cng)
 +           |> update_and_set_cache() do
 +      Enum.each(followers, &invalidate_cache(&1))
 +      Enum.each(friends, &update_follower_count(&1))
  
 -    update_and_set_cache(cng)
 +      {:ok, user}
 +    end
    end
  
    def update_notification_settings(%User{} = user, settings \\ %{}) do
      |> update_and_set_cache()
    end
  
-   def delete(%User{} = user) do
+   @spec delete(User.t()) :: :ok
+   def delete(%User{} = user),
+     do: PleromaJobQueue.enqueue(:background, __MODULE__, [:delete, user])
+   @spec perform(atom(), User.t()) :: {:ok, User.t()}
+   def perform(:delete, %User{} = user) do
      {:ok, user} = User.deactivate(user)
  
      # Remove all relationships
    end
  
    def delete_user_activities(%User{ap_id: ap_id} = user) do
-     Activity
-     |> where(actor: ^ap_id)
-     |> Activity.with_preloaded_object()
-     |> Repo.all()
-     |> Enum.each(fn
-       %{data: %{"type" => "Create"}} = activity ->
-         activity |> Object.normalize() |> ActivityPub.delete()
+     stream =
+       ap_id
+       |> Activity.query_by_actor()
+       |> Activity.with_preloaded_object()
+       |> Repo.stream()
  
-       # TODO: Do something with likes, follows, repeats.
-       _ ->
-         "Doing nothing"
-     end)
+     Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
  
      {:ok, user}
    end
  
+   defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
+     Object.normalize(activity) |> ActivityPub.delete()
+   end
+   defp delete_activity(_activity), do: "Doing nothing"
    def html_filter_policy(%User{info: %{no_rich_text: true}}) do
      Pleroma.HTML.Scrubber.TwitterText
    end
  
      case ap_try do
        {:ok, user} ->
-         user
+         {:ok, user}
  
        _ ->
          case OStatus.make_user(ap_id) do
-           {:ok, user} -> user
+           {:ok, user} -> {:ok, user}
            _ -> {:error, "Could not fetch by AP id"}
          end
      end
      user = get_cached_by_ap_id(ap_id)
  
      if !is_nil(user) and !User.needs_update?(user) do
-       user
+       {:ok, user}
      else
        # Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
        should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
  
-       user = fetch_by_ap_id(ap_id)
+       resp = fetch_by_ap_id(ap_id)
  
        if should_fetch_initial do
-         with %User{} = user do
+         with {:ok, %User{} = user} = resp do
            {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
          end
        end
  
-       user
+       resp
      end
    end
  
    end
  
    def get_public_key_for_ap_id(ap_id) do
-     with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
+     with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
           {:ok, public_key} <- public_key_from_info(user.info) do
        {:ok, public_key}
      else
      end
    end
  
-   def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
-   def parse_bio(nil, _user), do: ""
-   def parse_bio(bio, _user) when bio == "", do: bio
+   def parse_bio(bio) when is_binary(bio) and bio != "" do
+     bio
+     |> CommonUtils.format_input("text/plain", mentions_format: :full)
+     |> elem(0)
+   end
  
-   def parse_bio(bio, user) do
-     emoji =
-       (user.info.source_data["tag"] || [])
-       |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
-       |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
-         {String.trim(name, ":"), url}
-       end)
+   def parse_bio(_), do: ""
  
+   def parse_bio(bio, user) when is_binary(bio) and bio != "" do
      # TODO: get profile URLs other than user.ap_id
      profile_urls = [user.ap_id]
  
        rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
      )
      |> elem(0)
-     |> Formatter.emojify(emoji)
    end
  
+   def parse_bio(_, _), do: ""
    def tag(user_identifiers, tags) when is_list(user_identifiers) do
      Repo.transaction(fn ->
        for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
      updated_user
    end
  
-   def bookmark(%User{} = user, status_id) do
-     bookmarks = Enum.uniq(user.bookmarks ++ [status_id])
-     update_bookmarks(user, bookmarks)
-   end
-   def unbookmark(%User{} = user, status_id) do
-     bookmarks = Enum.uniq(user.bookmarks -- [status_id])
-     update_bookmarks(user, bookmarks)
-   end
-   def update_bookmarks(%User{} = user, bookmarks) do
-     user
-     |> change(%{bookmarks: bookmarks})
-     |> update_and_set_cache
-   end
    defp normalize_tags(tags) do
      [tags]
      |> List.flatten()
index 6bf54d1ccfe83fa7c4a8dd9c819d322b144f8273,483a2153fb31db107b200b6a18215c151d50ab6f..d06bc64eac30d515f965fe87eb9b466e8150a23a
@@@ -168,7 -168,6 +168,6 @@@ defmodule Pleroma.Web.ActivityPub.Activ
      public = "https://www.w3.org/ns/activitystreams#Public"
  
      if activity.data["type"] in ["Create", "Announce", "Delete"] do
-       object = Object.normalize(activity)
        Pleroma.Web.Streamer.stream("user", activity)
        Pleroma.Web.Streamer.stream("list", activity)
  
          end
  
          if activity.data["type"] in ["Create"] do
+           object = Object.normalize(activity)
            object.data
            |> Map.get("tag", [])
            |> Enum.filter(fn tag -> is_bitstring(tag) end)
      |> restrict_reblogs(opts)
      |> restrict_pinned(opts)
      |> restrict_muted_reblogs(opts)
 +    |> Activity.restrict_deactivated_users()
    end
  
    def fetch_activities(recipients, opts \\ %{}) do
index 6c8c2fe248a5d860bff3f4f15bff8b0c74ae30d9,c03f8ab3a9e4ab7b4c3a920c8c3a45d94119a638..7b7fd912b367e3b386c0ed0fa838b3a8e900401b
@@@ -352,7 -352,7 +352,7 @@@ defmodule Pleroma.Web.TwitterAPI.UtilCo
    def delete_account(%{assigns: %{user: user}} = conn, params) do
      case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
        {:ok, user} ->
-         Task.start(fn -> User.delete(user) end)
+         User.delete(user)
          json(conn, %{status: "success"})
  
        {:error, msg} ->
      end
    end
  
 +  def disable_account(%{assigns: %{user: user}} = conn, params) do
 +    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
 +      {:ok, user} ->
 +        User.deactivate_async(user)
 +        json(conn, %{status: "success"})
 +
 +      {:error, msg} ->
 +        json(conn, %{error: msg})
 +    end
 +  end
 +
    def captcha(conn, _params) do
      json(conn, Pleroma.Captcha.new())
    end
index 2353a95a8059de9d6639c4a4d49fe00caf4c620d,3a777464784078dda93c10e388a8c41d11463e02..1e48b0b39e5ac848e7b59ceeb849d5faf9f11ad8
@@@ -231,15 -231,12 +231,15 @@@ defmodule Pleroma.Web.TwitterAPI.Twitte
    def get_user(user \\ nil, params) do
      case params do
        %{"user_id" => user_id} ->
 -        case target = User.get_cached_by_nickname_or_id(user_id) do
 +        case User.get_cached_by_nickname_or_id(user_id) do
            nil ->
              {:error, "No user with such user_id"}
  
 -          _ ->
 -            {:ok, target}
 +          %User{info: %{deactivated: true}} ->
 +            {:error, "User has been disabled"}
 +
 +          user ->
 +            {:ok, user}
          end
  
        %{"screen_name" => nickname} ->
    end
  
    def get_external_profile(for_user, uri) do
-     with %User{} = user <- User.get_or_fetch(uri) do
+     with {:ok, %User{} = user} <- User.get_or_fetch(uri) do
        {:ok, UserView.render("show.json", %{user: user, for: for_user})}
      else
        _e ->
diff --combined test/user_test.exs
index 2966d1f88b541269e98b189eaf7c9eb961efe698,adc77a26416ddd67ecf6c92c3725289ee9fec3c7..00c06dfaac6a5c2689a849ebbc51e89ed76dcaab
@@@ -8,7 -8,6 +8,7 @@@ defmodule Pleroma.UserTest d
    alias Pleroma.Object
    alias Pleroma.Repo
    alias Pleroma.User
 +  alias Pleroma.Web.ActivityPub.ActivityPub
    alias Pleroma.Web.CommonAPI
  
    use Pleroma.DataCase
    test "fetches correct profile for nickname beginning with number" do
      # Use old-style integer ID to try to reproduce the problem
      user = insert(:user, %{id: 1080})
 -    userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"})
 -    assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname)
 +    user_with_numbers = insert(:user, %{nickname: "#{user.id}garbage"})
 +    assert user_with_numbers == User.get_cached_by_nickname_or_id(user_with_numbers.nickname)
    end
  
    describe "user registration" do
    describe "get_or_fetch/1" do
      test "gets an existing user by nickname" do
        user = insert(:user)
-       fetched_user = User.get_or_fetch(user.nickname)
+       {:ok, fetched_user} = User.get_or_fetch(user.nickname)
  
        assert user == fetched_user
      end
            info: %{}
          )
  
-       fetched_user = User.get_or_fetch(ap_id)
+       {:ok, fetched_user} = User.get_or_fetch(ap_id)
        freshed_user = refresh_record(user)
        assert freshed_user == fetched_user
      end
    describe "fetching a user from nickname or trying to build one" do
      test "gets an existing user" do
        user = insert(:user)
-       fetched_user = User.get_or_fetch_by_nickname(user.nickname)
+       {:ok, fetched_user} = User.get_or_fetch_by_nickname(user.nickname)
  
        assert user == fetched_user
      end
  
      test "gets an existing user, case insensitive" do
        user = insert(:user, nickname: "nick")
-       fetched_user = User.get_or_fetch_by_nickname("NICK")
+       {:ok, fetched_user} = User.get_or_fetch_by_nickname("NICK")
  
        assert user == fetched_user
      end
      test "gets an existing user by fully qualified nickname" do
        user = insert(:user)
  
-       fetched_user =
+       {:ok, fetched_user} =
          User.get_or_fetch_by_nickname(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
  
        assert user == fetched_user
        user = insert(:user, nickname: "nick")
        casing_altered_fqn = String.upcase(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
  
-       fetched_user = User.get_or_fetch_by_nickname(casing_altered_fqn)
+       {:ok, fetched_user} = User.get_or_fetch_by_nickname(casing_altered_fqn)
  
        assert user == fetched_user
      end
  
      test "fetches an external user via ostatus if no user exists" do
-       fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
+       {:ok, fetched_user} = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
        assert fetched_user.nickname == "shp@social.heldscal.la"
      end
  
      test "returns nil if no user could be fetched" do
-       fetched_user = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
-       assert fetched_user == nil
+       {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
+       assert fetched_user == "not found nonexistant@social.heldscal.la"
      end
  
      test "returns nil for nonexistant local user" do
-       fetched_user = User.get_or_fetch_by_nickname("nonexistant")
-       assert fetched_user == nil
+       {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant")
+       assert fetched_user == "not found nonexistant"
      end
  
      test "updates an existing user, if stale" do
  
        assert orig_user.last_refreshed_at == a_week_ago
  
-       user = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
+       {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
        assert user.info.source_data["endpoints"]
  
        refute user.last_refreshed_at == orig_user.last_refreshed_at
      assert addressed in recipients
    end
  
 -  test ".deactivate can de-activate then re-activate a user" do
 -    user = insert(:user)
 -    assert false == user.info.deactivated
 -    {:ok, user} = User.deactivate(user)
 -    assert true == user.info.deactivated
 -    {:ok, user} = User.deactivate(user, false)
 -    assert false == user.info.deactivated
 +  describe ".deactivate" do
 +    test "can de-activate then re-activate a user" do
 +      user = insert(:user)
 +      assert false == user.info.deactivated
 +      {:ok, user} = User.deactivate(user)
 +      assert true == user.info.deactivated
 +      {:ok, user} = User.deactivate(user, false)
 +      assert false == user.info.deactivated
 +    end
 +
 +    test "hide a user from followers " do
 +      user = insert(:user)
 +      user2 = insert(:user)
 +
 +      {:ok, user} = User.follow(user, user2)
 +      {:ok, _user} = User.deactivate(user)
 +
 +      info = User.get_cached_user_info(user2)
 +
 +      assert info.follower_count == 0
 +      assert {:ok, []} = User.get_followers(user2)
 +    end
 +
 +    test "hide a user from friends" do
 +      user = insert(:user)
 +      user2 = insert(:user)
 +
 +      {:ok, user2} = User.follow(user2, user)
 +      assert User.following_count(user2) == 1
 +
 +      {:ok, _user} = User.deactivate(user)
 +
 +      info = User.get_cached_user_info(user2)
 +
 +      assert info.following_count == 0
 +      assert User.following_count(user2) == 0
 +      assert {:ok, []} = User.get_friends(user2)
 +    end
 +
 +    test "hide a user's statuses from timelines and notifications" do
 +      user = insert(:user)
 +      user2 = insert(:user)
 +
 +      {:ok, user2} = User.follow(user2, user)
 +
 +      {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"})
 +
 +      [notification] = Pleroma.Notification.for_user(user2)
 +      assert notification.activity.id == activity.id
 +
 +      assert [activity] == ActivityPub.fetch_public_activities(%{})
 +
 +      assert [activity] ==
 +               ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
 +               |> ActivityPub.contain_timeline(user2)
 +
 +      {:ok, _user} = User.deactivate(user)
 +
 +      assert [] == ActivityPub.fetch_public_activities(%{})
 +      assert [] == Pleroma.Notification.for_user(user2)
 +
 +      assert [] ==
 +               ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
 +               |> ActivityPub.contain_timeline(user2)
 +    end
    end
  
    test ".delete_user_activities deletes all create activities" do
      user = insert(:user)
  
      {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
-     {:ok, _} = User.delete_user_activities(user)
  
-     # TODO: Remove favorites, repeats, delete activities.
-     refute Activity.get_by_id(activity.id)
+     Ecto.Adapters.SQL.Sandbox.unboxed_run(Repo, fn ->
+       {:ok, _} = User.delete_user_activities(user)
+       # TODO: Remove favorites, repeats, delete activities.
+       refute Activity.get_by_id(activity.id)
+     end)
    end
  
    test ".delete deactivates a user, all follow relationships and all create activities" do
        expected_text =
          "A.k.a. <span class='h-card'><a data-user='#{remote_user.id}' class='u-url mention' href='#{
            remote_user.ap_id
-         }'>" <> "@<span>nick@domain.com</span></a></span>"
+         }'>@<span>nick@domain.com</span></a></span>"
  
        assert expected_text == User.parse_bio(bio, user)
      end
      end
    end
  
-   test "bookmarks" do
-     user = insert(:user)
-     {:ok, activity1} =
-       CommonAPI.post(user, %{
-         "status" => "heweoo!"
-       })
-     id1 = Object.normalize(activity1).data["id"]
-     {:ok, activity2} =
-       CommonAPI.post(user, %{
-         "status" => "heweoo!"
-       })
-     id2 = Object.normalize(activity2).data["id"]
-     assert {:ok, user_state1} = User.bookmark(user, id1)
-     assert user_state1.bookmarks == [id1]
-     assert {:ok, user_state2} = User.unbookmark(user, id1)
-     assert user_state2.bookmarks == []
-     assert {:ok, user_state3} = User.bookmark(user, id2)
-     assert user_state3.bookmarks == [id2]
-   end
    test "follower count is updated when a follower is blocked" do
      user = insert(:user)
      follower = insert(:user)