Merge remote-tracking branch 'remotes/upstream/develop' into 1335-user-api-id-fields...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Thu, 21 Nov 2019 13:47:52 +0000 (16:47 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Thu, 21 Nov 2019 13:47:52 +0000 (16:47 +0300)
# Conflicts:
# lib/pleroma/user/search.ex
# test/user_test.exs

lib/pleroma/following_relationship.ex
lib/pleroma/user.ex
lib/pleroma/user/search.ex
lib/pleroma/web/activity_pub/relay.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
mix.exs
priv/repo/migrations/20191104133100_set_visible_service_actors.exs [new file with mode: 0644]
test/following_relationship_test.exs [new file with mode: 0644]
test/user_search_test.exs
test/user_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs

index 2ffac17ee135667774c0cd64f110d6425fc81706..3aff9fb76ba6dee1b1f47734f66028918371911f 100644 (file)
@@ -101,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do
       |> select([r, u], u.follower_address)
       |> Repo.all()
 
-    if not user.local or user.nickname in [nil, "internal.fetch"] do
+    if not user.local or user.invisible do
       following
     else
       [user.follower_address | following]
index d97c19d38926383b53974ae279207d8a9fa97bec..383402447a09d725061ddf830323dd9a0e7ae05c 100644 (file)
@@ -94,8 +94,7 @@ defmodule Pleroma.User do
     field(:source_data, :map, default: %{})
     field(:note_count, :integer, default: 0)
     field(:follower_count, :integer, default: 0)
-    # Should be filled in only for remote users
-    field(:following_count, :integer, default: nil)
+    field(:following_count, :integer, default: 0)
     field(:locked, :boolean, default: false)
     field(:confirmation_pending, :boolean, default: false)
     field(:password_reset_pending, :boolean, default: false)
@@ -229,6 +228,8 @@ defmodule Pleroma.User do
 
   def visible_for?(user, for_user \\ nil)
 
+  def visible_for?(%User{invisible: true}, _), do: false
+
   def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
 
   def visible_for?(%User{} = user, for_user) do
@@ -272,19 +273,17 @@ defmodule Pleroma.User do
   def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
 
   def user_info(%User{} = user, args \\ %{}) do
-    following_count =
-      Map.get(args, :following_count, user.following_count || following_count(user))
-
+    following_count = Map.get(args, :following_count, user.following_count)
     follower_count = Map.get(args, :follower_count, user.follower_count)
 
     %{
       note_count: user.note_count,
       locked: user.locked,
       confirmation_pending: user.confirmation_pending,
-      default_scope: user.default_scope
+      default_scope: user.default_scope,
+      follower_count: follower_count,
+      following_count: following_count
     }
-    |> Map.put(:following_count, following_count)
-    |> Map.put(:follower_count, follower_count)
   end
 
   def follow_state(%User{} = user, %User{} = target) do
@@ -617,14 +616,9 @@ defmodule Pleroma.User do
   @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
   @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
   def follow_all(follower, followeds) do
-    followeds =
-      Enum.reject(followeds, fn followed ->
-        blocks?(follower, followed) || blocks?(followed, follower)
-      end)
-
-    Enum.each(followeds, &follow(follower, &1, "accept"))
-
-    Enum.each(followeds, &update_follower_count/1)
+    followeds
+    |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
+    |> Enum.each(&follow(follower, &1, "accept"))
 
     set_cache(follower)
   end
@@ -644,11 +638,11 @@ defmodule Pleroma.User do
       true ->
         FollowingRelationship.follow(follower, followed, state)
 
-        follower = maybe_update_following_count(follower)
-
         {:ok, _} = update_follower_count(followed)
 
-        set_cache(follower)
+        follower
+        |> update_following_count()
+        |> set_cache()
     end
   end
 
@@ -656,11 +650,12 @@ defmodule Pleroma.User do
     if following?(follower, followed) and follower.ap_id != followed.ap_id do
       FollowingRelationship.unfollow(follower, followed)
 
-      follower = maybe_update_following_count(follower)
-
       {:ok, followed} = update_follower_count(followed)
 
-      set_cache(follower)
+      {:ok, follower} =
+        follower
+        |> update_following_count()
+        |> set_cache()
 
       {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
     else
@@ -990,8 +985,8 @@ defmodule Pleroma.User do
     end
   end
 
-  @spec maybe_update_following_count(User.t()) :: User.t()
-  def maybe_update_following_count(%User{local: false} = user) do
+  @spec update_following_count(User.t()) :: User.t()
+  def update_following_count(%User{local: false} = user) do
     if Pleroma.Config.get([:instance, :external_user_synchronization]) do
       maybe_fetch_follow_information(user)
     else
@@ -999,7 +994,13 @@ defmodule Pleroma.User do
     end
   end
 
-  def maybe_update_following_count(user), do: user
+  def update_following_count(%User{local: true} = user) do
+    following_count = FollowingRelationship.following_count(user)
+
+    user
+    |> follow_information_changeset(%{following_count: following_count})
+    |> Repo.update!()
+  end
 
   def set_unread_conversation_count(%User{local: true} = user) do
     unread_query = Participation.unread_conversation_count_for_user(user)
@@ -1219,7 +1220,12 @@ defmodule Pleroma.User do
 
   def deactivate(%User{} = user, status) do
     with {:ok, user} <- set_activation_status(user, status) do
-      Enum.each(get_followers(user), &invalidate_cache/1)
+      user
+      |> get_followers()
+      |> Enum.filter(& &1.local)
+      |> Enum.each(fn follower ->
+        follower |> update_following_count() |> set_cache()
+      end)
 
       # Only update local user counts, remote will be update during the next pull.
       user
@@ -1439,22 +1445,23 @@ defmodule Pleroma.User do
     end
   end
 
-  @doc "Creates an internal service actor by URI if missing.  Optionally takes nickname for addressing."
+  @doc """
+  Creates an internal service actor by URI if missing.
+  Optionally takes nickname for addressing.
+  """
   def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
-    with %User{} = user <- get_cached_by_ap_id(uri) do
-      user
-    else
-      _ ->
-        {:ok, user} =
-          %User{}
-          |> cast(%{}, [:ap_id, :nickname, :local])
-          |> put_change(:ap_id, uri)
-          |> put_change(:nickname, nickname)
-          |> put_change(:local, true)
-          |> put_change(:follower_address, uri <> "/followers")
-          |> Repo.insert()
+    with user when is_nil(user) <- get_cached_by_ap_id(uri) do
+      {:ok, user} =
+        %User{
+          invisible: true,
+          local: true,
+          ap_id: uri,
+          nickname: nickname,
+          follower_address: uri <> "/followers"
+        }
+        |> Repo.insert()
 
-        user
+      user
     end
   end
 
index 2b3c7b5b4f08129b36a6dfe7984c3e511b6db94b..6b55df483b76944ba6276f25033ea30f76841628 100644 (file)
@@ -45,6 +45,7 @@ defmodule Pleroma.User.Search do
     for_user
     |> base_query(following)
     |> filter_blocked_user(for_user)
+    |> filter_invisible_users()
     |> filter_blocked_domains(for_user)
     |> fts_search(query_string)
     |> trigram_rank(query_string)
@@ -98,6 +99,10 @@ defmodule Pleroma.User.Search do
   defp base_query(_user, false), do: User
   defp base_query(user, true), do: User.get_followers_query(user)
 
+  defp filter_invisible_users(query) do
+    from(q in query, where: q.invisible == false)
+  end
+
   defp filter_blocked_user(query, %User{} = blocker) do
     query
     |> join(:left, [u], b in Pleroma.UserRelationship,
index fc2619680f8e00d96606e7918b1291d65519dbe5..99a804568ccf0830529d55d1c17e916fce146c5f 100644 (file)
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.Relay do
       relay_ap_id()
       |> User.get_or_create_service_actor_by_ap_id()
 
-    {:ok, actor} = User.set_invisible(actor, true)
     actor
   end
 
index 5b6158b939e09d020282ed34e7c9c5785cd8ae84..5ad33d4a4ee13356a311de1c4d50a3f973b4d296 100644 (file)
@@ -238,7 +238,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   @doc "GET /api/v1/accounts/:id"
   def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
-         true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
+         true <- User.visible_for?(user, for_user) do
       render(conn, "show.json", user: user, for: for_user)
     else
       _e -> render_error(conn, :not_found, "Can't find user")
diff --git a/mix.exs b/mix.exs
index c870b330f31fbefe4101c3a0e98cbf62713836b9..47f237d99af8a9c72143b7087c4932a09525544c 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -195,27 +195,21 @@ defmodule Pleroma.Mixfile do
     identifier_filter = ~r/[^0-9a-z\-]+/i
 
     # Pre-release version, denoted from patch version with a hyphen
-    {git_tag, git_pre_release} =
+    git_pre_release =
       with {tag, 0} <-
              System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
-           tag = String.trim(tag),
-           {describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
-           describe = String.trim(describe),
-           ahead <- String.replace(describe, tag, ""),
-           ahead <- String.trim_leading(ahead, "-") do
-        {String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
+           {describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]) do
+        describe
+        |> String.trim()
+        |> String.replace(String.trim(tag), "")
+        |> String.trim_leading("-")
+        |> String.trim()
       else
         _ ->
           {commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
-          {nil, "0-g" <> String.trim(commit_hash)}
+          "0-g" <> String.trim(commit_hash)
       end
 
-    if git_tag && version != git_tag do
-      Mix.shell().error(
-        "Application version #{inspect(version)} does not match git tag #{inspect(git_tag)}"
-      )
-    end
-
     # Branch name as pre-release version component, denoted with a dot
     branch_name =
       with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),
diff --git a/priv/repo/migrations/20191104133100_set_visible_service_actors.exs b/priv/repo/migrations/20191104133100_set_visible_service_actors.exs
new file mode 100644 (file)
index 0000000..6290709
--- /dev/null
@@ -0,0 +1,22 @@
+defmodule Pleroma.Repo.Migrations.SetVisibleServiceActors do
+  use Ecto.Migration
+  import Ecto.Query
+  alias Pleroma.Repo
+
+  def up do
+    user_nicknames = ["relay", "internal.fetch"]
+
+    from(
+      u in "users",
+      where: u.nickname in ^user_nicknames,
+      update: [
+        set: [invisible: true]
+      ]
+    )
+    |> Repo.update_all([])
+  end
+
+  def down do
+    :ok
+  end
+end
diff --git a/test/following_relationship_test.exs b/test/following_relationship_test.exs
new file mode 100644 (file)
index 0000000..93c0798
--- /dev/null
@@ -0,0 +1,47 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.FollowingRelationshipTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.FollowingRelationship
+  alias Pleroma.Web.ActivityPub.InternalFetchActor
+  alias Pleroma.Web.ActivityPub.Relay
+
+  import Pleroma.Factory
+
+  describe "following/1" do
+    test "returns following addresses without internal.fetch" do
+      user = insert(:user)
+      fetch_actor = InternalFetchActor.get_actor()
+      FollowingRelationship.follow(fetch_actor, user, "accept")
+      assert FollowingRelationship.following(fetch_actor) == [user.follower_address]
+    end
+
+    test "returns following addresses without relay" do
+      user = insert(:user)
+      relay_actor = Relay.get_actor()
+      FollowingRelationship.follow(relay_actor, user, "accept")
+      assert FollowingRelationship.following(relay_actor) == [user.follower_address]
+    end
+
+    test "returns following addresses without remote user" do
+      user = insert(:user)
+      actor = insert(:user, local: false)
+      FollowingRelationship.follow(actor, user, "accept")
+      assert FollowingRelationship.following(actor) == [user.follower_address]
+    end
+
+    test "returns following addresses with local user" do
+      user = insert(:user)
+      actor = insert(:user, local: true)
+      FollowingRelationship.follow(actor, user, "accept")
+
+      assert FollowingRelationship.following(actor) == [
+               actor.follower_address,
+               user.follower_address
+             ]
+    end
+  end
+end
index 721af1e5bb9bbecc1d677f3309dceb8126d1d1dd..98841dbbda2c231f586a3b17896ca08815f2b737 100644 (file)
@@ -15,6 +15,14 @@ defmodule Pleroma.UserSearchTest do
   end
 
   describe "User.search" do
+    test "excluded invisible users from results" do
+      user = insert(:user, %{nickname: "john t1000"})
+      insert(:user, %{invisible: true, nickname: "john t800"})
+
+      [found_user] = User.search("john")
+      assert found_user.id == user.id
+    end
+
     test "accepts limit parameter" do
       Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
       assert length(User.search("john", limit: 3)) == 3
index ae90edbe956eadbf39929f2088f0ce4b74feeb4c..1f30622010e580334a0c55787af7f7791c1f62d3 100644 (file)
@@ -25,6 +25,25 @@ defmodule Pleroma.UserTest do
 
   clear_config([:instance, :account_activation_required])
 
+  describe "service actors" do
+    test "returns invisible actor" do
+      uri = "#{Pleroma.Web.Endpoint.url()}/internal/fetch-test"
+      followers_uri = "#{uri}/followers"
+      user = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test")
+
+      assert %User{
+               nickname: "internal.fetch-test",
+               invisible: true,
+               local: true,
+               ap_id: ^uri,
+               follower_address: ^followers_uri
+             } = user
+
+      user2 = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test")
+      assert user.id == user2.id
+    end
+  end
+
   describe "AP ID user relationships" do
     setup do
       {:ok, user: insert(:user)}
@@ -198,9 +217,10 @@ defmodule Pleroma.UserTest do
     {:ok, user} = User.follow(user, followed)
 
     user = User.get_cached_by_id(user.id)
-
     followed = User.get_cached_by_ap_id(followed.ap_id)
+
     assert followed.follower_count == 1
+    assert user.following_count == 1
 
     assert User.ap_followers(followed) in User.following(user)
   end
@@ -1002,12 +1022,14 @@ defmodule Pleroma.UserTest do
       user2 = insert(:user)
 
       {:ok, user2} = User.follow(user2, user)
+      assert user2.following_count == 1
       assert User.following_count(user2) == 1
 
       {:ok, _user} = User.deactivate(user)
 
       info = User.get_cached_user_info(user2)
 
+      assert refresh_record(user2).following_count == 0
       assert info.following_count == 0
       assert User.following_count(user2) == 0
       assert [] = User.get_friends(user2)
index 9ff008a50b3764b9e874bb95f7747d7b8c24f720..4446934041c8d4558d84e13026f3d48a19aded32 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.InternalFetchActor
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OAuth.Token
 
@@ -118,6 +119,28 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       refute acc_one == acc_two
       assert acc_two == acc_three
     end
+
+    test "returns 404 when user is invisible", %{conn: conn} do
+      user = insert(:user, %{invisible: true})
+
+      resp =
+        conn
+        |> get("/api/v1/accounts/#{user.nickname}")
+        |> json_response(404)
+
+      assert %{"error" => "Can't find user"} = resp
+    end
+
+    test "returns 404 for internal.fetch actor", %{conn: conn} do
+      %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor()
+
+      resp =
+        conn
+        |> get("/api/v1/accounts/internal.fetch")
+        |> json_response(404)
+
+      assert %{"error" => "Can't find user"} = resp
+    end
   end
 
   describe "user timelines" do