Merge remote-tracking branch 'remotes/upstream/develop' into 1335-user-api-id-fields-relations

# Conflicts:
#	lib/pleroma/user/search.ex
#	test/user_test.exs
This commit is contained in:
Ivan Tashkinov 2019-11-21 16:47:52 +03:00
commit cad9b325e5
11 changed files with 185 additions and 58 deletions

View file

@ -101,7 +101,7 @@ def following(%User{} = user) do
|> select([r, u], u.follower_address) |> select([r, u], u.follower_address)
|> Repo.all() |> Repo.all()
if not user.local or user.nickname in [nil, "internal.fetch"] do if not user.local or user.invisible do
following following
else else
[user.follower_address | following] [user.follower_address | following]

View file

@ -94,8 +94,7 @@ defmodule Pleroma.User do
field(:source_data, :map, default: %{}) field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0) field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0) field(:follower_count, :integer, default: 0)
# Should be filled in only for remote users field(:following_count, :integer, default: 0)
field(:following_count, :integer, default: nil)
field(:locked, :boolean, default: false) field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false) field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false)
@ -229,6 +228,8 @@ def auth_active?(%User{}), do: true
def visible_for?(user, for_user \\ nil) 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{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
def visible_for?(%User{} = user, for_user) do def visible_for?(%User{} = user, for_user) do
@ -272,19 +273,17 @@ def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
def ap_following(%User{} = user), do: "#{ap_id(user)}/following" def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
def user_info(%User{} = user, args \\ %{}) do def user_info(%User{} = user, args \\ %{}) do
following_count = following_count = Map.get(args, :following_count, user.following_count)
Map.get(args, :following_count, user.following_count || following_count(user))
follower_count = Map.get(args, :follower_count, user.follower_count) follower_count = Map.get(args, :follower_count, user.follower_count)
%{ %{
note_count: user.note_count, note_count: user.note_count,
locked: user.locked, locked: user.locked,
confirmation_pending: user.confirmation_pending, 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 end
def follow_state(%User{} = user, %User{} = target) do def follow_state(%User{} = user, %User{} = target) do
@ -617,14 +616,9 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities." @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()} @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do def follow_all(follower, followeds) do
followeds = followeds
Enum.reject(followeds, fn followed -> |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
blocks?(follower, followed) || blocks?(followed, follower) |> Enum.each(&follow(follower, &1, "accept"))
end)
Enum.each(followeds, &follow(follower, &1, "accept"))
Enum.each(followeds, &update_follower_count/1)
set_cache(follower) set_cache(follower)
end end
@ -644,11 +638,11 @@ def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
true -> true ->
FollowingRelationship.follow(follower, followed, state) FollowingRelationship.follow(follower, followed, state)
follower = maybe_update_following_count(follower)
{:ok, _} = update_follower_count(followed) {:ok, _} = update_follower_count(followed)
set_cache(follower) follower
|> update_following_count()
|> set_cache()
end end
end end
@ -656,11 +650,12 @@ def unfollow(%User{} = follower, %User{} = followed) do
if following?(follower, followed) and follower.ap_id != followed.ap_id do if following?(follower, followed) and follower.ap_id != followed.ap_id do
FollowingRelationship.unfollow(follower, followed) FollowingRelationship.unfollow(follower, followed)
follower = maybe_update_following_count(follower)
{:ok, followed} = update_follower_count(followed) {: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)} {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
else else
@ -990,8 +985,8 @@ def update_follower_count(%User{} = user) do
end end
end end
@spec maybe_update_following_count(User.t()) :: User.t() @spec update_following_count(User.t()) :: User.t()
def maybe_update_following_count(%User{local: false} = user) do def update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do if Pleroma.Config.get([:instance, :external_user_synchronization]) do
maybe_fetch_follow_information(user) maybe_fetch_follow_information(user)
else else
@ -999,7 +994,13 @@ def maybe_update_following_count(%User{local: false} = user) do
end end
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 def set_unread_conversation_count(%User{local: true} = user) do
unread_query = Participation.unread_conversation_count_for_user(user) unread_query = Participation.unread_conversation_count_for_user(user)
@ -1219,7 +1220,12 @@ def deactivate(users, status) when is_list(users) do
def deactivate(%User{} = user, status) do def deactivate(%User{} = user, status) do
with {:ok, user} <- set_activation_status(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. # Only update local user counts, remote will be update during the next pull.
user user
@ -1439,19 +1445,20 @@ def get_or_fetch_by_ap_id(ap_id) do
end end
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 def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
with %User{} = user <- get_cached_by_ap_id(uri) do with user when is_nil(user) <- get_cached_by_ap_id(uri) do
user
else
_ ->
{:ok, user} = {:ok, user} =
%User{} %User{
|> cast(%{}, [:ap_id, :nickname, :local]) invisible: true,
|> put_change(:ap_id, uri) local: true,
|> put_change(:nickname, nickname) ap_id: uri,
|> put_change(:local, true) nickname: nickname,
|> put_change(:follower_address, uri <> "/followers") follower_address: uri <> "/followers"
}
|> Repo.insert() |> Repo.insert()
user user

View file

@ -45,6 +45,7 @@ defp search_query(query_string, for_user, following) do
for_user for_user
|> base_query(following) |> base_query(following)
|> filter_blocked_user(for_user) |> filter_blocked_user(for_user)
|> filter_invisible_users()
|> filter_blocked_domains(for_user) |> filter_blocked_domains(for_user)
|> fts_search(query_string) |> fts_search(query_string)
|> trigram_rank(query_string) |> trigram_rank(query_string)
@ -98,6 +99,10 @@ defp trigram_rank(query, query_string) do
defp base_query(_user, false), do: User defp base_query(_user, false), do: User
defp base_query(user, true), do: User.get_followers_query(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 defp filter_blocked_user(query, %User{} = blocker) do
query query
|> join(:left, [u], b in Pleroma.UserRelationship, |> join(:left, [u], b in Pleroma.UserRelationship,

View file

@ -14,7 +14,6 @@ def get_actor do
relay_ap_id() relay_ap_id()
|> User.get_or_create_service_actor_by_ap_id() |> User.get_or_create_service_actor_by_ap_id()
{:ok, actor} = User.set_invisible(actor, true)
actor actor
end end

View file

@ -238,7 +238,7 @@ def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id" @doc "GET /api/v1/accounts/:id"
def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do 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), 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) render(conn, "show.json", user: user, for: for_user)
else else
_e -> render_error(conn, :not_found, "Can't find user") _e -> render_error(conn, :not_found, "Can't find user")

22
mix.exs
View file

@ -195,25 +195,19 @@ defp version(version) do
identifier_filter = ~r/[^0-9a-z\-]+/i identifier_filter = ~r/[^0-9a-z\-]+/i
# Pre-release version, denoted from patch version with a hyphen # Pre-release version, denoted from patch version with a hyphen
{git_tag, git_pre_release} = git_pre_release =
with {tag, 0} <- with {tag, 0} <-
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true), System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
tag = String.trim(tag), {describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]) do
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]), describe
describe = String.trim(describe), |> String.trim()
ahead <- String.replace(describe, tag, ""), |> String.replace(String.trim(tag), "")
ahead <- String.trim_leading(ahead, "-") do |> String.trim_leading("-")
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))} |> String.trim()
else else
_ -> _ ->
{commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"]) {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 end
# Branch name as pre-release version component, denoted with a dot # Branch name as pre-release version component, denoted with a dot

View file

@ -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

View file

@ -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

View file

@ -15,6 +15,14 @@ defmodule Pleroma.UserSearchTest do
end end
describe "User.search" do 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 test "accepts limit parameter" do
Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"})) Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
assert length(User.search("john", limit: 3)) == 3 assert length(User.search("john", limit: 3)) == 3

View file

@ -25,6 +25,25 @@ defmodule Pleroma.UserTest do
clear_config([:instance, :account_activation_required]) 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 describe "AP ID user relationships" do
setup do setup do
{:ok, user: insert(:user)} {:ok, user: insert(:user)}
@ -198,9 +217,10 @@ test "follow takes a user and another user" do
{:ok, user} = User.follow(user, followed) {:ok, user} = User.follow(user, followed)
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
followed = User.get_cached_by_ap_id(followed.ap_id) followed = User.get_cached_by_ap_id(followed.ap_id)
assert followed.follower_count == 1 assert followed.follower_count == 1
assert user.following_count == 1
assert User.ap_followers(followed) in User.following(user) assert User.ap_followers(followed) in User.following(user)
end end
@ -1002,12 +1022,14 @@ test "hide a user from friends" do
user2 = insert(:user) user2 = insert(:user)
{:ok, user2} = User.follow(user2, user) {:ok, user2} = User.follow(user2, user)
assert user2.following_count == 1
assert User.following_count(user2) == 1 assert User.following_count(user2) == 1
{:ok, _user} = User.deactivate(user) {:ok, _user} = User.deactivate(user)
info = User.get_cached_user_info(user2) info = User.get_cached_user_info(user2)
assert refresh_record(user2).following_count == 0
assert info.following_count == 0 assert info.following_count == 0
assert User.following_count(user2) == 0 assert User.following_count(user2) == 0
assert [] = User.get_friends(user2) assert [] = User.get_friends(user2)

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -118,6 +119,28 @@ test "accounts fetches correct account for nicknames beginning with numbers", %{
refute acc_one == acc_two refute acc_one == acc_two
assert acc_two == acc_three assert acc_two == acc_three
end 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 end
describe "user timelines" do describe "user timelines" do