forked from AkkomaGang/akkoma
Merge branch 'develop' into issue/1276
This commit is contained in:
commit
b27a92e8fa
66 changed files with 1283 additions and 377 deletions
|
@ -25,7 +25,7 @@ While we don’t provide docker files, other people have written very good ones.
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* Postgresql version 9.6 or newer
|
* Postgresql version 9.6 or newer, including the contrib modules
|
||||||
* Elixir version 1.7 or newer. If your distribution only has an old version available, check [Elixir’s install page](https://elixir-lang.org/install.html) or use a tool like [asdf](https://github.com/asdf-vm/asdf).
|
* Elixir version 1.7 or newer. If your distribution only has an old version available, check [Elixir’s install page](https://elixir-lang.org/install.html) or use a tool like [asdf](https://github.com/asdf-vm/asdf).
|
||||||
* Build-essential tools
|
* Build-essential tools
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ This is useful for running Pleroma inside Tor or I2P.
|
||||||
|
|
||||||
## Customization and contribution
|
## Customization and contribution
|
||||||
|
|
||||||
The [Pleroma Documentation](https://docs-develop.pleroma.social/readme.html) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
|
The [Pleroma Documentation](https://docs-develop.pleroma.social) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
@ -39,10 +39,12 @@ def query_timelines(user) do
|
||||||
"muting_user" => user
|
"muting_user" => user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
following = User.following(user)
|
||||||
|
|
||||||
Benchee.run(%{
|
Benchee.run(%{
|
||||||
"User home timeline" => fn ->
|
"User home timeline" => fn ->
|
||||||
Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
|
Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
|
||||||
[user.ap_id | user.following],
|
following,
|
||||||
home_timeline_params
|
home_timeline_params
|
||||||
)
|
)
|
||||||
end,
|
end,
|
||||||
|
@ -60,7 +62,7 @@ def query_timelines(user) do
|
||||||
|
|
||||||
home_activities =
|
home_activities =
|
||||||
Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
|
Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
|
||||||
[user.ap_id | user.following],
|
following,
|
||||||
home_timeline_params
|
home_timeline_params
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -45,15 +45,13 @@ defp generate_user_data(i) do
|
||||||
%{
|
%{
|
||||||
ap_id: ap_id,
|
ap_id: ap_id,
|
||||||
follower_address: ap_id <> "/followers",
|
follower_address: ap_id <> "/followers",
|
||||||
following_address: ap_id <> "/following",
|
following_address: ap_id <> "/following"
|
||||||
following: [ap_id]
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
%{
|
%{
|
||||||
ap_id: User.ap_id(user),
|
ap_id: User.ap_id(user),
|
||||||
follower_address: User.ap_followers(user),
|
follower_address: User.ap_followers(user),
|
||||||
following_address: User.ap_following(user),
|
following_address: User.ap_following(user)
|
||||||
following: [User.ap_id(user)]
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,9 @@ def run(["bump_all_conversations"]) do
|
||||||
def run(["update_users_following_followers_counts"]) do
|
def run(["update_users_following_followers_counts"]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
users = Repo.all(User)
|
User
|
||||||
Enum.each(users, &User.remove_duplicated_following/1)
|
|> Repo.all()
|
||||||
Enum.each(users, &User.update_follower_count/1)
|
|> Enum.each(&User.update_follower_count/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(["prune_objects" | args]) do
|
def run(["prune_objects" | args]) do
|
||||||
|
|
|
@ -163,7 +163,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
if Enum.empty?(user.following) do
|
if Enum.empty?(User.get_friends(user)) do
|
||||||
shell_info("Successfully unsubscribed all followers from #{user.nickname}")
|
shell_info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
@ -97,7 +97,7 @@ def handle_command(state, "home") do
|
||||||
|> Map.put("user", user)
|
|> Map.put("user", user)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
[user.ap_id | user.following]
|
[user.ap_id | Pleroma.User.following(user)]
|
||||||
|> ActivityPub.fetch_activities(params)
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|
||||||
Enum.each(activities, fn activity ->
|
Enum.each(activities, fn activity ->
|
||||||
|
|
|
@ -67,7 +67,13 @@ def create_or_bump_for(activity, opts \\ []) do
|
||||||
|
|
||||||
participations =
|
participations =
|
||||||
Enum.map(users, fn user ->
|
Enum.map(users, fn user ->
|
||||||
User.increment_unread_conversation_count(conversation, user)
|
invisible_conversation = Enum.any?(users, &User.blocks?(user, &1))
|
||||||
|
|
||||||
|
unless invisible_conversation do
|
||||||
|
User.increment_unread_conversation_count(conversation, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)
|
||||||
|
|
||||||
{:ok, participation} =
|
{:ok, participation} =
|
||||||
Participation.create_for_user_and_conversation(user, conversation, opts)
|
Participation.create_for_user_and_conversation(user, conversation, opts)
|
||||||
|
|
|
@ -32,11 +32,20 @@ def creation_cng(struct, params) do
|
||||||
|
|
||||||
def create_for_user_and_conversation(user, conversation, opts \\ []) do
|
def create_for_user_and_conversation(user, conversation, opts \\ []) do
|
||||||
read = !!opts[:read]
|
read = !!opts[:read]
|
||||||
|
invisible_conversation = !!opts[:invisible_conversation]
|
||||||
|
|
||||||
|
update_on_conflict =
|
||||||
|
if(invisible_conversation, do: [], else: [read: read])
|
||||||
|
|> Keyword.put(:updated_at, NaiveDateTime.utc_now())
|
||||||
|
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
|
|> creation_cng(%{
|
||||||
|
user_id: user.id,
|
||||||
|
conversation_id: conversation.id,
|
||||||
|
read: invisible_conversation || read
|
||||||
|
})
|
||||||
|> Repo.insert(
|
|> Repo.insert(
|
||||||
on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
|
on_conflict: [set: update_on_conflict],
|
||||||
returning: true,
|
returning: true,
|
||||||
conflict_target: [:user_id, :conversation_id]
|
conflict_target: [:user_id, :conversation_id]
|
||||||
)
|
)
|
||||||
|
@ -69,7 +78,26 @@ def mark_as_read(participation) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_all_as_read(user) do
|
def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do
|
||||||
|
target_conversation_ids =
|
||||||
|
__MODULE__
|
||||||
|
|> where([p], p.user_id == ^target_user.id)
|
||||||
|
|> select([p], p.conversation_id)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
__MODULE__
|
||||||
|
|> where([p], p.user_id == ^user.id)
|
||||||
|
|> where([p], p.conversation_id in ^target_conversation_ids)
|
||||||
|
|> update([p], set: [read: true])
|
||||||
|
|> Repo.update_all([])
|
||||||
|
|
||||||
|
{:ok, user} = User.set_unread_conversation_count(user)
|
||||||
|
{:ok, user, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_all_as_read(%User{} = user, %User{}), do: {:ok, user, []}
|
||||||
|
|
||||||
|
def mark_all_as_read(%User{} = user) do
|
||||||
{_, participations} =
|
{_, participations} =
|
||||||
__MODULE__
|
__MODULE__
|
||||||
|> where([p], p.user_id == ^user.id)
|
|> where([p], p.user_id == ^user.id)
|
||||||
|
@ -78,8 +106,8 @@ def mark_all_as_read(user) do
|
||||||
|> select([p], p)
|
|> select([p], p)
|
||||||
|> Repo.update_all([])
|
|> Repo.update_all([])
|
||||||
|
|
||||||
User.set_unread_conversation_count(user)
|
{:ok, user} = User.set_unread_conversation_count(user)
|
||||||
{:ok, participations}
|
{:ok, user, participations}
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_as_unread(participation) do
|
def mark_as_unread(participation) do
|
||||||
|
|
110
lib/pleroma/following_relationship.ex
Normal file
110
lib/pleroma/following_relationship.ex
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.FollowingRelationship do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias FlakeId.Ecto.CompatType
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
schema "following_relationships" do
|
||||||
|
field(:state, :string, default: "accept")
|
||||||
|
|
||||||
|
belongs_to(:follower, User, type: CompatType)
|
||||||
|
belongs_to(:following, User, type: CompatType)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%__MODULE__{} = following_relationship, attrs) do
|
||||||
|
following_relationship
|
||||||
|
|> cast(attrs, [:state])
|
||||||
|
|> put_assoc(:follower, attrs.follower)
|
||||||
|
|> put_assoc(:following, attrs.following)
|
||||||
|
|> validate_required([:state, :follower, :following])
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(%User{} = follower, %User{} = following) do
|
||||||
|
__MODULE__
|
||||||
|
|> where(follower_id: ^follower.id, following_id: ^following.id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(follower, following, "reject"), do: unfollow(follower, following)
|
||||||
|
|
||||||
|
def update(%User{} = follower, %User{} = following, state) do
|
||||||
|
case get(follower, following) do
|
||||||
|
nil ->
|
||||||
|
follow(follower, following, state)
|
||||||
|
|
||||||
|
following_relationship ->
|
||||||
|
following_relationship
|
||||||
|
|> cast(%{state: state}, [:state])
|
||||||
|
|> validate_required([:state])
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow(%User{} = follower, %User{} = following, state \\ "accept") do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(%{follower: follower, following: following, state: state})
|
||||||
|
|> Repo.insert(on_conflict: :nothing)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow(%User{} = follower, %User{} = following) do
|
||||||
|
case get(follower, following) do
|
||||||
|
nil -> {:ok, nil}
|
||||||
|
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def follower_count(%User{} = user) do
|
||||||
|
%{followers: user, deactivated: false}
|
||||||
|
|> User.Query.build()
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def following_count(%User{id: nil}), do: 0
|
||||||
|
|
||||||
|
def following_count(%User{} = user) do
|
||||||
|
%{friends: user, deactivated: false}
|
||||||
|
|> User.Query.build()
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_follow_requests(%User{id: id}) do
|
||||||
|
__MODULE__
|
||||||
|
|> join(:inner, [r], f in assoc(r, :follower))
|
||||||
|
|> where([r], r.state == "pending")
|
||||||
|
|> where([r], r.following_id == ^id)
|
||||||
|
|> select([r, f], f)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def following?(%User{id: follower_id}, %User{id: followed_id}) do
|
||||||
|
__MODULE__
|
||||||
|
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
|
||||||
|
|> Repo.exists?()
|
||||||
|
end
|
||||||
|
|
||||||
|
def following(%User{} = user) do
|
||||||
|
following =
|
||||||
|
__MODULE__
|
||||||
|
|> join(:inner, [r], u in User, on: r.following_id == u.id)
|
||||||
|
|> where([r], r.follower_id == ^user.id)
|
||||||
|
|> where([r], r.state == "accept")
|
||||||
|
|> select([r, u], u.follower_address)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
if not user.local or user.nickname in [nil, "internal.fetch"] do
|
||||||
|
following
|
||||||
|
else
|
||||||
|
[user.follower_address | following]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -90,6 +90,9 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
{:fetch_object, %Object{} = object} ->
|
{:fetch_object, %Object{} = object} ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
|
||||||
|
{:fetch, {:error, error}} ->
|
||||||
|
{:error, error}
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
e
|
e
|
||||||
end
|
end
|
||||||
|
@ -110,6 +113,9 @@ def fetch_object_from_id!(id, options \\ []) do
|
||||||
with {:ok, object} <- fetch_object_from_id(id, options) do
|
with {:ok, object} <- fetch_object_from_id(id, options) do
|
||||||
object
|
object
|
||||||
else
|
else
|
||||||
|
{:error, %Tesla.Mock.Error{}} ->
|
||||||
|
nil
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("Error while fetching #{id}: #{inspect(e)}")
|
Logger.error("Error while fetching #{id}: #{inspect(e)}")
|
||||||
nil
|
nil
|
||||||
|
@ -170,6 +176,9 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
{:scheme, _} ->
|
{:scheme, _} ->
|
||||||
{:error, "Unsupported URI scheme"}
|
{:error, "Unsupported URI scheme"}
|
||||||
|
|
||||||
|
{:error, e} ->
|
||||||
|
{:error, e}
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Delivery
|
alias Pleroma.Delivery
|
||||||
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.Keys
|
alias Pleroma.Keys
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -50,7 +51,6 @@ defmodule Pleroma.User do
|
||||||
field(:password, :string, virtual: true)
|
field(:password, :string, virtual: true)
|
||||||
field(:password_confirmation, :string, virtual: true)
|
field(:password_confirmation, :string, virtual: true)
|
||||||
field(:keys, :string)
|
field(:keys, :string)
|
||||||
field(:following, {:array, :string}, default: [])
|
|
||||||
field(:ap_id, :string)
|
field(:ap_id, :string)
|
||||||
field(:avatar, :map)
|
field(:avatar, :map)
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
|
@ -216,61 +216,7 @@ def restrict_deactivated(query) do
|
||||||
from(u in query, where: u.deactivated != ^true)
|
from(u in query, where: u.deactivated != ^true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def following_count(%User{following: []}), do: 0
|
defdelegate following_count(user), to: FollowingRelationship
|
||||||
|
|
||||||
def following_count(%User{} = user) do
|
|
||||||
user
|
|
||||||
|> get_friends_query()
|
|
||||||
|> Repo.aggregate(:count, :id)
|
|
||||||
end
|
|
||||||
|
|
||||||
@info_fields [
|
|
||||||
:banner,
|
|
||||||
:background,
|
|
||||||
:source_data,
|
|
||||||
:note_count,
|
|
||||||
:follower_count,
|
|
||||||
:following_count,
|
|
||||||
:locked,
|
|
||||||
:confirmation_pending,
|
|
||||||
:password_reset_pending,
|
|
||||||
:confirmation_token,
|
|
||||||
:default_scope,
|
|
||||||
:blocks,
|
|
||||||
:domain_blocks,
|
|
||||||
:mutes,
|
|
||||||
:muted_reblogs,
|
|
||||||
:muted_notifications,
|
|
||||||
:subscribers,
|
|
||||||
:deactivated,
|
|
||||||
:no_rich_text,
|
|
||||||
:ap_enabled,
|
|
||||||
:is_moderator,
|
|
||||||
:is_admin,
|
|
||||||
:show_role,
|
|
||||||
:settings,
|
|
||||||
:magic_key,
|
|
||||||
:uri,
|
|
||||||
:hide_followers_count,
|
|
||||||
:hide_follows_count,
|
|
||||||
:hide_followers,
|
|
||||||
:hide_follows,
|
|
||||||
:hide_favorites,
|
|
||||||
:unread_conversation_count,
|
|
||||||
:pinned_activities,
|
|
||||||
:email_notifications,
|
|
||||||
:mascot,
|
|
||||||
:emoji,
|
|
||||||
:pleroma_settings_store,
|
|
||||||
:fields,
|
|
||||||
:raw_fields,
|
|
||||||
:discoverable,
|
|
||||||
:invisible,
|
|
||||||
:skip_thread_containment,
|
|
||||||
:notification_settings
|
|
||||||
]
|
|
||||||
|
|
||||||
def info_fields, do: @info_fields
|
|
||||||
|
|
||||||
defp truncate_fields_param(params) do
|
defp truncate_fields_param(params) do
|
||||||
if Map.has_key?(params, :fields) do
|
if Map.has_key?(params, :fields) do
|
||||||
|
@ -357,7 +303,6 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:bio,
|
:bio,
|
||||||
:name,
|
:name,
|
||||||
:avatar,
|
:avatar,
|
||||||
:following,
|
|
||||||
:locked,
|
:locked,
|
||||||
:no_rich_text,
|
:no_rich_text,
|
||||||
:default_scope,
|
:default_scope,
|
||||||
|
@ -502,7 +447,6 @@ defp put_following_and_follower_address(changeset) do
|
||||||
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> put_change(:following, [followers])
|
|
||||||
|> put_change(:follower_address, followers)
|
|> put_change(:follower_address, followers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -556,8 +500,8 @@ def needs_update?(%User{local: false} = user) do
|
||||||
def needs_update?(_), do: true
|
def needs_update?(_), do: true
|
||||||
|
|
||||||
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
|
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
|
||||||
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
|
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
|
||||||
{:ok, follower}
|
follow(follower, followed, "pending")
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
|
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
|
||||||
|
@ -575,37 +519,22 @@ 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
|
||||||
followed_addresses =
|
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.map(fn %{follower_address: fa} -> fa end)
|
end)
|
||||||
|
|
||||||
q =
|
Enum.each(followeds, &follow(follower, &1, "accept"))
|
||||||
from(u in User,
|
|
||||||
where: u.id == ^follower.id,
|
|
||||||
update: [
|
|
||||||
set: [
|
|
||||||
following:
|
|
||||||
fragment(
|
|
||||||
"array(select distinct unnest (array_cat(?, ?)))",
|
|
||||||
u.following,
|
|
||||||
^followed_addresses
|
|
||||||
)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
select: u
|
|
||||||
)
|
|
||||||
|
|
||||||
{1, [follower]} = Repo.update_all(q, [])
|
|
||||||
|
|
||||||
Enum.each(followeds, &update_follower_count/1)
|
Enum.each(followeds, &update_follower_count/1)
|
||||||
|
|
||||||
set_cache(follower)
|
set_cache(follower)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow(%User{} = follower, %User{} = followed) do
|
defdelegate following(user), to: FollowingRelationship
|
||||||
|
|
||||||
|
def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
|
||||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||||
ap_followers = followed.follower_address
|
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
followed.deactivated ->
|
followed.deactivated ->
|
||||||
|
@ -615,14 +544,7 @@ def follow(%User{} = follower, %User{} = followed) do
|
||||||
{:error, "Could not follow user: #{followed.nickname} blocked you."}
|
{:error, "Could not follow user: #{followed.nickname} blocked you."}
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
q =
|
FollowingRelationship.follow(follower, followed, state)
|
||||||
from(u in User,
|
|
||||||
where: u.id == ^follower.id,
|
|
||||||
update: [push: [following: ^ap_followers]],
|
|
||||||
select: u
|
|
||||||
)
|
|
||||||
|
|
||||||
{1, [follower]} = Repo.update_all(q, [])
|
|
||||||
|
|
||||||
follower = maybe_update_following_count(follower)
|
follower = maybe_update_following_count(follower)
|
||||||
|
|
||||||
|
@ -633,17 +555,8 @@ def follow(%User{} = follower, %User{} = followed) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow(%User{} = follower, %User{} = followed) do
|
def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
ap_followers = followed.follower_address
|
|
||||||
|
|
||||||
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
||||||
q =
|
FollowingRelationship.unfollow(follower, followed)
|
||||||
from(u in User,
|
|
||||||
where: u.id == ^follower.id,
|
|
||||||
update: [pull: [following: ^ap_followers]],
|
|
||||||
select: u
|
|
||||||
)
|
|
||||||
|
|
||||||
{1, [follower]} = Repo.update_all(q, [])
|
|
||||||
|
|
||||||
follower = maybe_update_following_count(follower)
|
follower = maybe_update_following_count(follower)
|
||||||
|
|
||||||
|
@ -657,10 +570,7 @@ def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec following?(User.t(), User.t()) :: boolean
|
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||||
def following?(%User{} = follower, %User{} = followed) do
|
|
||||||
Enum.member?(follower.following, followed.follower_address)
|
|
||||||
end
|
|
||||||
|
|
||||||
def locked?(%User{} = user) do
|
def locked?(%User{} = user) do
|
||||||
user.locked || false
|
user.locked || false
|
||||||
|
@ -882,16 +792,7 @@ def get_friends_ids(user, page \\ nil) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
|
defdelegate get_follow_requests(user), to: FollowingRelationship
|
||||||
def get_follow_requests(%User{} = user) do
|
|
||||||
user
|
|
||||||
|> Activity.follow_requests_for_actor()
|
|
||||||
|> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|
|
||||||
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|
|
||||||
|> group_by([a, u], u.id)
|
|
||||||
|> select([a, u], u)
|
|
||||||
|> Repo.all()
|
|
||||||
end
|
|
||||||
|
|
||||||
def increase_note_count(%User{} = user) do
|
def increase_note_count(%User{} = user) do
|
||||||
User
|
User
|
||||||
|
@ -1019,7 +920,7 @@ def set_unread_conversation_count(%User{local: true} = user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_unread_conversation_count(_), do: :noop
|
def set_unread_conversation_count(user), do: {:ok, user}
|
||||||
|
|
||||||
def increment_unread_conversation_count(conversation, %User{local: true} = user) do
|
def increment_unread_conversation_count(conversation, %User{local: true} = user) do
|
||||||
unread_query =
|
unread_query =
|
||||||
|
@ -1041,19 +942,7 @@ def increment_unread_conversation_count(conversation, %User{local: true} = user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def increment_unread_conversation_count(_, _), do: :noop
|
def increment_unread_conversation_count(_, user), do: {:ok, user}
|
||||||
|
|
||||||
def remove_duplicated_following(%User{following: following} = user) do
|
|
||||||
uniq_following = Enum.uniq(following)
|
|
||||||
|
|
||||||
if length(following) == length(uniq_following) do
|
|
||||||
{:ok, user}
|
|
||||||
else
|
|
||||||
user
|
|
||||||
|> update_changeset(%{following: uniq_following})
|
|
||||||
|> update_and_set_cache()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||||
|
@ -1125,7 +1014,7 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
|
||||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = update_follower_count(blocker)
|
{:ok, blocker} = update_follower_count(blocker)
|
||||||
|
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||||
add_to_block(blocker, ap_id)
|
add_to_block(blocker, ap_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ defmodule Pleroma.User.Query do
|
||||||
"""
|
"""
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
|
import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
|
||||||
|
|
||||||
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@type criteria ::
|
@type criteria ::
|
||||||
|
@ -139,18 +141,40 @@ defp compose_query({:deactivated, true}, query) do
|
||||||
|> where([u], not is_nil(u.nickname))
|
|> where([u], not is_nil(u.nickname))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do
|
defp compose_query({:followers, %User{id: id}}, query) do
|
||||||
where(query, [u], fragment("? <@ ?", ^[follower_address], u.following))
|
query
|
||||||
|> where([u], u.id != ^id)
|
|> where([u], u.id != ^id)
|
||||||
|
|> join(:inner, [u], r in FollowingRelationship,
|
||||||
|
as: :relationships,
|
||||||
|
on: r.following_id == ^id and r.follower_id == u.id
|
||||||
|
)
|
||||||
|
|> where([relationships: r], r.state == "accept")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:friends, %User{id: id, following: following}}, query) do
|
defp compose_query({:friends, %User{id: id}}, query) do
|
||||||
where(query, [u], u.follower_address in ^following)
|
query
|
||||||
|> where([u], u.id != ^id)
|
|> where([u], u.id != ^id)
|
||||||
|
|> join(:inner, [u], r in FollowingRelationship,
|
||||||
|
as: :relationships,
|
||||||
|
on: r.following_id == u.id and r.follower_id == ^id
|
||||||
|
)
|
||||||
|
|> where([relationships: r], r.state == "accept")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:recipients_from_activity, to}, query) do
|
defp compose_query({:recipients_from_activity, to}, query) do
|
||||||
where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
|
query
|
||||||
|
|> join(:left, [u], r in FollowingRelationship,
|
||||||
|
as: :relationships,
|
||||||
|
on: r.follower_id == u.id
|
||||||
|
)
|
||||||
|
|> join(:left, [relationships: r], f in User,
|
||||||
|
as: :following,
|
||||||
|
on: f.id == r.following_id
|
||||||
|
)
|
||||||
|
|> where(
|
||||||
|
[u, following: f, relationships: r],
|
||||||
|
u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:order_by, key}, query) do
|
defp compose_query({:order_by, key}, query) do
|
||||||
|
|
|
@ -518,7 +518,9 @@ defp fetch_activities_for_context_query(context, opts) do
|
||||||
public = [Pleroma.Constants.as_public()]
|
public = [Pleroma.Constants.as_public()]
|
||||||
|
|
||||||
recipients =
|
recipients =
|
||||||
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
if opts["user"],
|
||||||
|
do: [opts["user"].ap_id | User.following(opts["user"])] ++ public,
|
||||||
|
else: public
|
||||||
|
|
||||||
from(activity in Activity)
|
from(activity in Activity)
|
||||||
|> maybe_preload_objects(opts)
|
|> maybe_preload_objects(opts)
|
||||||
|
@ -712,7 +714,7 @@ defp user_activities_recipients(%{"godmode" => true}) do
|
||||||
|
|
||||||
defp user_activities_recipients(%{"reading_user" => reading_user}) do
|
defp user_activities_recipients(%{"reading_user" => reading_user}) do
|
||||||
if reading_user do
|
if reading_user do
|
||||||
[Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
|
[Pleroma.Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]
|
||||||
else
|
else
|
||||||
[Pleroma.Constants.as_public()]
|
[Pleroma.Constants.as_public()]
|
||||||
end
|
end
|
||||||
|
|
|
@ -319,12 +319,12 @@ def read_inbox(
|
||||||
when page? in [true, "true"] do
|
when page? in [true, "true"] do
|
||||||
activities =
|
activities =
|
||||||
if params["max_id"] do
|
if params["max_id"] do
|
||||||
ActivityPub.fetch_activities([user.ap_id | user.following], %{
|
ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{
|
||||||
"max_id" => params["max_id"],
|
"max_id" => params["max_id"],
|
||||||
"limit" => 10
|
"limit" => 10
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
|
ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{"limit" => 10})
|
||||||
end
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -57,9 +57,10 @@ def publish(_), do: {:error, "Not implemented"}
|
||||||
|
|
||||||
@spec list() :: {:ok, [String.t()]} | {:error, any()}
|
@spec list() :: {:ok, [String.t()]} | {:error, any()}
|
||||||
def list do
|
def list do
|
||||||
with %User{following: following} = _user <- get_actor() do
|
with %User{} = user <- get_actor() do
|
||||||
list =
|
list =
|
||||||
following
|
user
|
||||||
|
|> User.following()
|
||||||
|> Enum.map(fn entry -> URI.parse(entry).host end)
|
|> Enum.map(fn entry -> URI.parse(entry).host end)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
A module to handle coding from internal to wire ActivityPub and back.
|
A module to handle coding from internal to wire ActivityPub and back.
|
||||||
"""
|
"""
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -474,7 +475,8 @@ def handle_incoming(
|
||||||
{_, false} <- {:user_locked, User.locked?(followed)},
|
{_, false} <- {:user_locked, User.locked?(followed)},
|
||||||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
||||||
{_, {:ok, _}} <-
|
{_, {:ok, _}} <-
|
||||||
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")} do
|
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
|
||||||
|
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
|
||||||
ActivityPub.accept(%{
|
ActivityPub.accept(%{
|
||||||
to: [follower.ap_id],
|
to: [follower.ap_id],
|
||||||
actor: followed,
|
actor: followed,
|
||||||
|
@ -484,6 +486,7 @@ def handle_incoming(
|
||||||
else
|
else
|
||||||
{:user_blocked, true} ->
|
{:user_blocked, true} ->
|
||||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||||
|
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
|
||||||
|
|
||||||
ActivityPub.reject(%{
|
ActivityPub.reject(%{
|
||||||
to: [follower.ap_id],
|
to: [follower.ap_id],
|
||||||
|
@ -494,6 +497,7 @@ def handle_incoming(
|
||||||
|
|
||||||
{:follow, {:error, _}} ->
|
{:follow, {:error, _}} ->
|
||||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||||
|
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
|
||||||
|
|
||||||
ActivityPub.reject(%{
|
ActivityPub.reject(%{
|
||||||
to: [follower.ap_id],
|
to: [follower.ap_id],
|
||||||
|
@ -503,6 +507,7 @@ def handle_incoming(
|
||||||
})
|
})
|
||||||
|
|
||||||
{:user_locked, true} ->
|
{:user_locked, true} ->
|
||||||
|
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
|
||||||
:noop
|
:noop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -522,7 +527,7 @@ def handle_incoming(
|
||||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||||
{:ok, _follower} = User.follow(follower, followed) do
|
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
|
||||||
ActivityPub.accept(%{
|
ActivityPub.accept(%{
|
||||||
to: follow_activity.data["to"],
|
to: follow_activity.data["to"],
|
||||||
type: "Accept",
|
type: "Accept",
|
||||||
|
@ -545,6 +550,7 @@ def handle_incoming(
|
||||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
||||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||||
|
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
|
||||||
{:ok, activity} <-
|
{:ok, activity} <-
|
||||||
ActivityPub.reject(%{
|
ActivityPub.reject(%{
|
||||||
to: follow_activity.data["to"],
|
to: follow_activity.data["to"],
|
||||||
|
@ -554,8 +560,6 @@ def handle_incoming(
|
||||||
local: false,
|
local: false,
|
||||||
activity_id: id
|
activity_id: id
|
||||||
}) do
|
}) do
|
||||||
User.unfollow(follower, followed)
|
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
_e -> :error
|
_e -> :error
|
||||||
|
@ -1061,43 +1065,22 @@ def perform(:user_upgrade, user) do
|
||||||
# we pass a fake user so that the followers collection is stripped away
|
# we pass a fake user so that the followers collection is stripped away
|
||||||
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
||||||
|
|
||||||
q =
|
from(
|
||||||
from(
|
a in Activity,
|
||||||
u in User,
|
where: ^old_follower_address in a.recipients,
|
||||||
where: ^old_follower_address in u.following,
|
update: [
|
||||||
update: [
|
set: [
|
||||||
set: [
|
recipients:
|
||||||
following:
|
fragment(
|
||||||
fragment(
|
"array_replace(?,?,?)",
|
||||||
"array_replace(?,?,?)",
|
a.recipients,
|
||||||
u.following,
|
^old_follower_address,
|
||||||
^old_follower_address,
|
^user.follower_address
|
||||||
^user.follower_address
|
)
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
)
|
]
|
||||||
|
)
|
||||||
Repo.update_all(q, [])
|
|> Repo.update_all([])
|
||||||
|
|
||||||
q =
|
|
||||||
from(
|
|
||||||
a in Activity,
|
|
||||||
where: ^old_follower_address in a.recipients,
|
|
||||||
update: [
|
|
||||||
set: [
|
|
||||||
recipients:
|
|
||||||
fragment(
|
|
||||||
"array_replace(?,?,?)",
|
|
||||||
a.recipients,
|
|
||||||
^old_follower_address,
|
|
||||||
^user.follower_address
|
|
||||||
)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
Repo.update_all(q, [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_user_from_ap_id(ap_id) do
|
def upgrade_user_from_ap_id(ap_id) do
|
||||||
|
|
|
@ -59,7 +59,7 @@ def visible_for_user?(activity, nil) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_for_user?(activity, user) do
|
def visible_for_user?(activity, user) do
|
||||||
x = [user.ap_id | user.following]
|
x = [user.ap_id | User.following(user)]
|
||||||
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
|
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
|
||||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.ActivityExpiration
|
alias Pleroma.ActivityExpiration
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.FollowingRelationship
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -40,6 +41,7 @@ def accept_follow_request(follower, followed) do
|
||||||
with {:ok, follower} <- User.follow(follower, followed),
|
with {:ok, follower} <- User.follow(follower, followed),
|
||||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||||
|
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
|
||||||
{:ok, _activity} <-
|
{:ok, _activity} <-
|
||||||
ActivityPub.accept(%{
|
ActivityPub.accept(%{
|
||||||
to: [follower.ap_id],
|
to: [follower.ap_id],
|
||||||
|
@ -54,6 +56,7 @@ def accept_follow_request(follower, followed) do
|
||||||
def reject_follow_request(follower, followed) do
|
def reject_follow_request(follower, followed) do
|
||||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
||||||
|
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
|
||||||
{:ok, _activity} <-
|
{:ok, _activity} <-
|
||||||
ActivityPub.reject(%{
|
ActivityPub.reject(%{
|
||||||
to: [follower.ap_id],
|
to: [follower.ap_id],
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
||||||
|
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
|
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
|
||||||
|
@ -28,7 +29,7 @@ def home(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("muting_user", user)
|
|> Map.put("muting_user", user)
|
||||||
|> Map.put("user", user)
|
|> Map.put("user", user)
|
||||||
|
|
||||||
recipients = [user.ap_id | user.following]
|
recipients = [user.ap_id | User.following(user)]
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
recipients
|
recipients
|
||||||
|
@ -128,9 +129,12 @@ def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|
||||||
|
|
||||||
# we must filter the following list for the user to avoid leaking statuses the user
|
# we must filter the following list for the user to avoid leaking statuses the user
|
||||||
# does not actually have permission to see (for more info, peruse security issue #270).
|
# does not actually have permission to see (for more info, peruse security issue #270).
|
||||||
|
|
||||||
|
user_following = User.following(user)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
following
|
following
|
||||||
|> Enum.filter(fn x -> x in user.following end)
|
|> Enum.filter(fn x -> x in user_following end)
|
||||||
|> ActivityPub.fetch_activities_bounded(following, params)
|
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
||||||
|
|
||||||
recipients =
|
recipients =
|
||||||
if for_user do
|
if for_user do
|
||||||
[Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
|
[Pleroma.Constants.as_public()] ++ [for_user.ap_id | User.following(for_user)]
|
||||||
else
|
else
|
||||||
[Pleroma.Constants.as_public()]
|
[Pleroma.Constants.as_public()]
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,7 +80,7 @@ def update_conversation(
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_conversations(%{assigns: %{user: user}} = conn, _params) do
|
def read_conversations(%{assigns: %{user: user}} = conn, _params) do
|
||||||
with {:ok, participations} <- Participation.mark_all_as_read(user) do
|
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(participations)
|
|> add_link_headers(participations)
|
||||||
|> put_view(ConversationView)
|
|> put_view(ConversationView)
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateFollowingRelationships do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:following_relationships) do
|
||||||
|
add(:follower_id, references(:users, type: :uuid, on_delete: :delete_all), null: false)
|
||||||
|
add(:following_id, references(:users, type: :uuid, on_delete: :delete_all), null: false)
|
||||||
|
add(:state, :string, null: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(index(:following_relationships, :follower_id))
|
||||||
|
create_if_not_exists(unique_index(:following_relationships, [:follower_id, :following_id]))
|
||||||
|
|
||||||
|
execute(update_thread_visibility(), restore_thread_visibility())
|
||||||
|
end
|
||||||
|
|
||||||
|
# The only difference between the original version: `actor_user` replaced with `actor_user_following`
|
||||||
|
def update_thread_visibility do
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
|
||||||
|
DECLARE
|
||||||
|
public varchar := 'https://www.w3.org/ns/activitystreams#Public';
|
||||||
|
child objects%ROWTYPE;
|
||||||
|
activity activities%ROWTYPE;
|
||||||
|
author_fa varchar;
|
||||||
|
valid_recipients varchar[];
|
||||||
|
actor_user_following varchar[];
|
||||||
|
BEGIN
|
||||||
|
--- Fetch actor following
|
||||||
|
SELECT array_agg(following.follower_address) INTO actor_user_following FROM following_relationships
|
||||||
|
JOIN users ON users.id = following_relationships.follower_id
|
||||||
|
JOIN users AS following ON following.id = following_relationships.following_id
|
||||||
|
WHERE users.ap_id = actor;
|
||||||
|
|
||||||
|
--- Fetch our initial activity.
|
||||||
|
SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
|
||||||
|
|
||||||
|
LOOP
|
||||||
|
--- Ensure that we have an activity before continuing.
|
||||||
|
--- If we don't, the thread is not satisfiable.
|
||||||
|
IF activity IS NULL THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- We only care about Create activities.
|
||||||
|
IF activity.data->>'type' != 'Create' THEN
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- Normalize the child object into child.
|
||||||
|
SELECT * INTO child FROM objects
|
||||||
|
INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||||
|
WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
|
||||||
|
|
||||||
|
--- Fetch the author's AS2 following collection.
|
||||||
|
SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
|
||||||
|
|
||||||
|
--- Prepare valid recipients array.
|
||||||
|
valid_recipients := ARRAY[actor, public];
|
||||||
|
IF ARRAY[author_fa] && actor_user_following THEN
|
||||||
|
valid_recipients := valid_recipients || author_fa;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- Check visibility.
|
||||||
|
IF NOT valid_recipients && activity.recipients THEN
|
||||||
|
--- activity not visible, break out of the loop
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- If there's a parent, load it and do this all over again.
|
||||||
|
IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
|
||||||
|
SELECT * INTO activity FROM activities
|
||||||
|
INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||||
|
WHERE child.data->>'inReplyTo' = objects.data->>'id';
|
||||||
|
ELSE
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
# priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
|
||||||
|
def restore_thread_visibility do
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
|
||||||
|
DECLARE
|
||||||
|
public varchar := 'https://www.w3.org/ns/activitystreams#Public';
|
||||||
|
child objects%ROWTYPE;
|
||||||
|
activity activities%ROWTYPE;
|
||||||
|
actor_user users%ROWTYPE;
|
||||||
|
author_fa varchar;
|
||||||
|
valid_recipients varchar[];
|
||||||
|
BEGIN
|
||||||
|
--- Fetch our actor.
|
||||||
|
SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
|
||||||
|
|
||||||
|
--- Fetch our initial activity.
|
||||||
|
SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
|
||||||
|
|
||||||
|
LOOP
|
||||||
|
--- Ensure that we have an activity before continuing.
|
||||||
|
--- If we don't, the thread is not satisfiable.
|
||||||
|
IF activity IS NULL THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- We only care about Create activities.
|
||||||
|
IF activity.data->>'type' != 'Create' THEN
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- Normalize the child object into child.
|
||||||
|
SELECT * INTO child FROM objects
|
||||||
|
INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||||
|
WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
|
||||||
|
|
||||||
|
--- Fetch the author's AS2 following collection.
|
||||||
|
SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
|
||||||
|
|
||||||
|
--- Prepare valid recipients array.
|
||||||
|
valid_recipients := ARRAY[actor, public];
|
||||||
|
IF ARRAY[author_fa] && actor_user.following THEN
|
||||||
|
valid_recipients := valid_recipients || author_fa;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- Check visibility.
|
||||||
|
IF NOT valid_recipients && activity.recipients THEN
|
||||||
|
--- activity not visible, break out of the loop
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--- If there's a parent, load it and do this all over again.
|
||||||
|
IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
|
||||||
|
SELECT * INTO activity FROM activities
|
||||||
|
INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
|
||||||
|
WHERE child.data->>'inReplyTo' = objects.data->>'id';
|
||||||
|
ELSE
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,89 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.MigrateFollowingRelationships do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
execute(import_following_from_users(), "")
|
||||||
|
execute(import_following_from_activities(), restore_following_column())
|
||||||
|
end
|
||||||
|
|
||||||
|
defp import_following_from_users do
|
||||||
|
"""
|
||||||
|
INSERT INTO following_relationships (follower_id, following_id, state, inserted_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
relations.follower_id,
|
||||||
|
following.id,
|
||||||
|
'accept',
|
||||||
|
now(),
|
||||||
|
now()
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
users.id AS follower_id,
|
||||||
|
unnest(users.following) AS following_ap_id
|
||||||
|
FROM
|
||||||
|
users
|
||||||
|
WHERE
|
||||||
|
users.following != '{}'
|
||||||
|
AND users.local = false OR users.local = true AND users.email IS NOT NULL -- Exclude `internal/fetch` and `relay`
|
||||||
|
) AS relations
|
||||||
|
JOIN users AS "following" ON "following".follower_address = relations.following_ap_id
|
||||||
|
|
||||||
|
WHERE relations.follower_id != following.id
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp import_following_from_activities do
|
||||||
|
"""
|
||||||
|
INSERT INTO
|
||||||
|
following_relationships (
|
||||||
|
follower_id,
|
||||||
|
following_id,
|
||||||
|
state,
|
||||||
|
inserted_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
followers.id,
|
||||||
|
following.id,
|
||||||
|
activities.data ->> 'state',
|
||||||
|
(activities.data ->> 'published') :: timestamp,
|
||||||
|
now()
|
||||||
|
FROM
|
||||||
|
activities
|
||||||
|
JOIN users AS followers ON (activities.actor = followers.ap_id)
|
||||||
|
JOIN users AS following ON (activities.data ->> 'object' = following.ap_id)
|
||||||
|
WHERE
|
||||||
|
activities.data ->> 'type' = 'Follow'
|
||||||
|
AND activities.data ->> 'state' IN ('accept', 'pending', 'reject')
|
||||||
|
ORDER BY activities.updated_at DESC
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restore_following_column do
|
||||||
|
"""
|
||||||
|
UPDATE
|
||||||
|
users
|
||||||
|
SET
|
||||||
|
following = following_query.following_array,
|
||||||
|
updated_at = now()
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
follower.id AS follower_id,
|
||||||
|
CASE follower.local
|
||||||
|
WHEN TRUE THEN
|
||||||
|
array_prepend(follower.follower_address, array_agg(following.follower_address))
|
||||||
|
ELSE
|
||||||
|
array_agg(following.follower_address)
|
||||||
|
END AS following_array
|
||||||
|
FROM
|
||||||
|
following_relationships
|
||||||
|
JOIN users AS follower ON follower.id = following_relationships.follower_id
|
||||||
|
JOIN users AS following ON following.id = following_relationships.following_id
|
||||||
|
GROUP BY
|
||||||
|
follower.id) AS following_query
|
||||||
|
WHERE
|
||||||
|
following_query.follower_id = users.id
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
16
priv/repo/migrations/20191008132427_drop_users_following.exs
Normal file
16
priv/repo/migrations/20191008132427_drop_users_following.exs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.DropUsersFollowing do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# had to disable these to be able to restore `following` index concurrently
|
||||||
|
# https://hexdocs.pm/ecto_sql/Ecto.Migration.html#index/3-adding-dropping-indexes-concurrently
|
||||||
|
@disable_ddl_transaction true
|
||||||
|
@disable_migration_lock true
|
||||||
|
|
||||||
|
def change do
|
||||||
|
drop(index(:users, [:following], concurrently: true, using: :gin))
|
||||||
|
|
||||||
|
alter table(:users) do
|
||||||
|
remove(:following, {:array, :string}, default: [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,53 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddUsersInfoColumns do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@jsonb_array_default "'[]'::jsonb"
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add_if_not_exists(:banner, :map, default: %{})
|
||||||
|
add_if_not_exists(:background, :map, default: %{})
|
||||||
|
add_if_not_exists(:source_data, :map, default: %{})
|
||||||
|
add_if_not_exists(:note_count, :integer, default: 0)
|
||||||
|
add_if_not_exists(:follower_count, :integer, default: 0)
|
||||||
|
add_if_not_exists(:following_count, :integer, default: nil)
|
||||||
|
add_if_not_exists(:locked, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:confirmation_pending, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:password_reset_pending, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:confirmation_token, :text, default: nil)
|
||||||
|
add_if_not_exists(:default_scope, :string, default: "public")
|
||||||
|
add_if_not_exists(:blocks, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:domain_blocks, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:mutes, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:muted_reblogs, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:muted_notifications, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:subscribers, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:deactivated, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:no_rich_text, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:ap_enabled, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:is_moderator, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:is_admin, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:show_role, :boolean, default: true, null: false)
|
||||||
|
add_if_not_exists(:settings, :map, default: nil)
|
||||||
|
add_if_not_exists(:magic_key, :text, default: nil)
|
||||||
|
add_if_not_exists(:uri, :text, default: nil)
|
||||||
|
add_if_not_exists(:hide_followers_count, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:hide_follows_count, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:hide_followers, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:hide_follows, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:hide_favorites, :boolean, default: true, null: false)
|
||||||
|
add_if_not_exists(:unread_conversation_count, :integer, default: 0)
|
||||||
|
add_if_not_exists(:pinned_activities, {:array, :text}, default: [])
|
||||||
|
add_if_not_exists(:email_notifications, :map, default: %{"digest" => false})
|
||||||
|
add_if_not_exists(:mascot, :map, default: nil)
|
||||||
|
add_if_not_exists(:emoji, :map, default: fragment(@jsonb_array_default))
|
||||||
|
add_if_not_exists(:pleroma_settings_store, :map, default: %{})
|
||||||
|
add_if_not_exists(:fields, :map, default: fragment(@jsonb_array_default))
|
||||||
|
add_if_not_exists(:raw_fields, :map, default: fragment(@jsonb_array_default))
|
||||||
|
add_if_not_exists(:discoverable, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:invisible, :boolean, default: false, null: false)
|
||||||
|
add_if_not_exists(:notification_settings, :map, default: %{})
|
||||||
|
add_if_not_exists(:skip_thread_containment, :boolean, default: false, null: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -95,79 +95,37 @@ defmodule Pleroma.Repo.Migrations.CopyUsersInfoFieldsToUsers do
|
||||||
]
|
]
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
alter table(:users) do
|
|
||||||
add(:banner, :map, default: %{})
|
|
||||||
add(:background, :map, default: %{})
|
|
||||||
add(:source_data, :map, default: %{})
|
|
||||||
add(:note_count, :integer, default: 0)
|
|
||||||
add(:follower_count, :integer, default: 0)
|
|
||||||
add(:following_count, :integer, default: nil)
|
|
||||||
add(:locked, :boolean, default: false, null: false)
|
|
||||||
add(:confirmation_pending, :boolean, default: false, null: false)
|
|
||||||
add(:password_reset_pending, :boolean, default: false, null: false)
|
|
||||||
add(:confirmation_token, :text, default: nil)
|
|
||||||
add(:default_scope, :string, default: "public")
|
|
||||||
add(:blocks, {:array, :text}, default: [])
|
|
||||||
add(:domain_blocks, {:array, :text}, default: [])
|
|
||||||
add(:mutes, {:array, :text}, default: [])
|
|
||||||
add(:muted_reblogs, {:array, :text}, default: [])
|
|
||||||
add(:muted_notifications, {:array, :text}, default: [])
|
|
||||||
add(:subscribers, {:array, :text}, default: [])
|
|
||||||
add(:deactivated, :boolean, default: false, null: false)
|
|
||||||
add(:no_rich_text, :boolean, default: false, null: false)
|
|
||||||
add(:ap_enabled, :boolean, default: false, null: false)
|
|
||||||
add(:is_moderator, :boolean, default: false, null: false)
|
|
||||||
add(:is_admin, :boolean, default: false, null: false)
|
|
||||||
add(:show_role, :boolean, default: true, null: false)
|
|
||||||
add(:settings, :map, default: nil)
|
|
||||||
add(:magic_key, :text, default: nil)
|
|
||||||
add(:uri, :text, default: nil)
|
|
||||||
add(:hide_followers_count, :boolean, default: false, null: false)
|
|
||||||
add(:hide_follows_count, :boolean, default: false, null: false)
|
|
||||||
add(:hide_followers, :boolean, default: false, null: false)
|
|
||||||
add(:hide_follows, :boolean, default: false, null: false)
|
|
||||||
add(:hide_favorites, :boolean, default: true, null: false)
|
|
||||||
add(:unread_conversation_count, :integer, default: 0)
|
|
||||||
add(:pinned_activities, {:array, :text}, default: [])
|
|
||||||
add(:email_notifications, :map, default: %{"digest" => false})
|
|
||||||
add(:mascot, :map, default: nil)
|
|
||||||
add(:emoji, :map, default: fragment(@jsonb_array_default))
|
|
||||||
add(:pleroma_settings_store, :map, default: %{})
|
|
||||||
add(:fields, :map, default: fragment(@jsonb_array_default))
|
|
||||||
add(:raw_fields, :map, default: fragment(@jsonb_array_default))
|
|
||||||
add(:discoverable, :boolean, default: false, null: false)
|
|
||||||
add(:invisible, :boolean, default: false, null: false)
|
|
||||||
add(:notification_settings, :map, default: %{})
|
|
||||||
add(:skip_thread_containment, :boolean, default: false, null: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
if direction() == :up do
|
if direction() == :up do
|
||||||
for f <- @info_fields do
|
sets =
|
||||||
set_field = "update users set #{f} ="
|
for f <- @info_fields do
|
||||||
|
set_field = "#{f} ="
|
||||||
|
|
||||||
# Coercion of null::jsonb to NULL
|
# Coercion of null::jsonb to NULL
|
||||||
jsonb = "case when info->>'#{f}' IS NULL then null else info->'#{f}' end"
|
jsonb = "case when info->>'#{f}' IS NULL then null else info->'#{f}' end"
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
f in @jsonb_fields ->
|
f in @jsonb_fields ->
|
||||||
execute("#{set_field} #{jsonb}")
|
"#{set_field} #{jsonb}"
|
||||||
|
|
||||||
f in @array_jsonb_fields ->
|
f in @array_jsonb_fields ->
|
||||||
execute("#{set_field} coalesce(#{jsonb}, #{@jsonb_array_default})")
|
"#{set_field} coalesce(#{jsonb}, #{@jsonb_array_default})"
|
||||||
|
|
||||||
f in @int_fields ->
|
f in @int_fields ->
|
||||||
execute("#{set_field} (info->>'#{f}')::int")
|
"#{set_field} (info->>'#{f}')::int"
|
||||||
|
|
||||||
f in @boolean_fields ->
|
f in @boolean_fields ->
|
||||||
execute("#{set_field} coalesce((info->>'#{f}')::boolean, false)")
|
"#{set_field} coalesce((info->>'#{f}')::boolean, false)"
|
||||||
|
|
||||||
f in @array_text_fields ->
|
f in @array_text_fields ->
|
||||||
execute("#{set_field} ARRAY(SELECT jsonb_array_elements_text(#{jsonb}))")
|
"#{set_field} ARRAY(SELECT jsonb_array_elements_text(#{jsonb}))"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
execute("#{set_field} info->>'#{f}'")
|
"#{set_field} info->>'#{f}'"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|> Enum.join(", ")
|
||||||
|
|
||||||
|
execute("update users set " <> sets)
|
||||||
|
|
||||||
for index_name <- [
|
for index_name <- [
|
||||||
:users_deactivated_index,
|
:users_deactivated_index,
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForActivities do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE activities
|
||||||
|
ALTER COLUMN data SET NOT NULL,
|
||||||
|
ALTER COLUMN local SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE activities
|
||||||
|
ALTER COLUMN data DROP NOT NULL,
|
||||||
|
ALTER COLUMN local DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForActivityExpirations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE activity_expirations
|
||||||
|
ALTER COLUMN activity_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE activity_expirations
|
||||||
|
ALTER COLUMN activity_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForApps do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE apps
|
||||||
|
ALTER COLUMN client_name SET NOT NULL,
|
||||||
|
ALTER COLUMN redirect_uris SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE apps
|
||||||
|
ALTER COLUMN client_name DROP NOT NULL,
|
||||||
|
ALTER COLUMN redirect_uris DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForBookmarks do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE bookmarks
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN activity_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE bookmarks
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN activity_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForConfig do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE config
|
||||||
|
ALTER COLUMN key SET NOT NULL,
|
||||||
|
ALTER COLUMN value SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE config
|
||||||
|
ALTER COLUMN key DROP NOT NULL,
|
||||||
|
ALTER COLUMN value DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForConversationParticipationRecipientShips do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE conversation_participation_recipient_ships
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN participation_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE conversation_participation_recipient_ships
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN participation_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForConversationParticipations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE conversation_participations
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN conversation_id SET NOT NULL,
|
||||||
|
ALTER COLUMN read SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE conversation_participations
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN conversation_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN read DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForFilters do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE filters
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN filter_id SET NOT NULL,
|
||||||
|
ALTER COLUMN whole_word SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE filters
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN filter_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN whole_word DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForInstances do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE instances
|
||||||
|
ALTER COLUMN host SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE instances
|
||||||
|
ALTER COLUMN host DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForLists do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE lists
|
||||||
|
ALTER COLUMN user_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE lists
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForMarkers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE markers
|
||||||
|
ALTER COLUMN user_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE markers
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForModerationLog do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE moderation_log
|
||||||
|
ALTER COLUMN data SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE moderation_log
|
||||||
|
ALTER COLUMN data DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForNotifications do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE notifications
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN seen SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE notifications
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN seen DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForOauthAuthorizations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE oauth_authorizations
|
||||||
|
ALTER COLUMN app_id SET NOT NULL,
|
||||||
|
ALTER COLUMN token SET NOT NULL,
|
||||||
|
ALTER COLUMN used SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE oauth_authorizations
|
||||||
|
ALTER COLUMN app_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN token DROP NOT NULL,
|
||||||
|
ALTER COLUMN used DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForOauthTokens do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE oauth_tokens
|
||||||
|
ALTER COLUMN app_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE oauth_tokens
|
||||||
|
ALTER COLUMN app_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE objects
|
||||||
|
ALTER COLUMN data SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE objects
|
||||||
|
ALTER COLUMN data DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForPasswordResetTokens do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE password_reset_tokens
|
||||||
|
ALTER COLUMN token SET NOT NULL,
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN used SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE password_reset_tokens
|
||||||
|
ALTER COLUMN token DROP NOT NULL,
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN used DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForPushSubscriptions do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE push_subscriptions
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN token_id SET NOT NULL,
|
||||||
|
ALTER COLUMN endpoint SET NOT NULL,
|
||||||
|
ALTER COLUMN key_p256dh SET NOT NULL,
|
||||||
|
ALTER COLUMN key_auth SET NOT NULL,
|
||||||
|
ALTER COLUMN data SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE push_subscriptions
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN token_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN endpoint DROP NOT NULL,
|
||||||
|
ALTER COLUMN key_p256dh DROP NOT NULL,
|
||||||
|
ALTER COLUMN key_auth DROP NOT NULL,
|
||||||
|
ALTER COLUMN data DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForRegistrations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE registrations
|
||||||
|
ALTER COLUMN provider SET NOT NULL,
|
||||||
|
ALTER COLUMN uid SET NOT NULL,
|
||||||
|
ALTER COLUMN info SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE registrations
|
||||||
|
ALTER COLUMN provider DROP NOT NULL,
|
||||||
|
ALTER COLUMN uid DROP NOT NULL,
|
||||||
|
ALTER COLUMN info DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForScheduledActivities do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE scheduled_activities
|
||||||
|
ALTER COLUMN user_id SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE scheduled_activities
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForThreadMutes do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE thread_mutes
|
||||||
|
ALTER COLUMN user_id SET NOT NULL,
|
||||||
|
ALTER COLUMN context SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE thread_mutes
|
||||||
|
ALTER COLUMN user_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN context DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForUserInviteTokens do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("ALTER TABLE user_invite_tokens
|
||||||
|
ALTER COLUMN used SET NOT NULL,
|
||||||
|
ALTER COLUMN uses SET NOT NULL,
|
||||||
|
ALTER COLUMN invite_type SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE user_invite_tokens
|
||||||
|
ALTER COLUMN used DROP NOT NULL,
|
||||||
|
ALTER COLUMN uses DROP NOT NULL,
|
||||||
|
ALTER COLUMN invite_type DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,44 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.SetNotNullForUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
# modify/3 function will require index recreation, so using execute/1 instead
|
||||||
|
|
||||||
|
def up do
|
||||||
|
# irreversible
|
||||||
|
execute("UPDATE users SET follower_count = 0 WHERE follower_count IS NULL")
|
||||||
|
|
||||||
|
execute("ALTER TABLE users
|
||||||
|
ALTER COLUMN local SET NOT NULL,
|
||||||
|
ALTER COLUMN source_data SET NOT NULL,
|
||||||
|
ALTER COLUMN note_count SET NOT NULL,
|
||||||
|
ALTER COLUMN follower_count SET NOT NULL,
|
||||||
|
ALTER COLUMN blocks SET NOT NULL,
|
||||||
|
ALTER COLUMN domain_blocks SET NOT NULL,
|
||||||
|
ALTER COLUMN mutes SET NOT NULL,
|
||||||
|
ALTER COLUMN muted_reblogs SET NOT NULL,
|
||||||
|
ALTER COLUMN muted_notifications SET NOT NULL,
|
||||||
|
ALTER COLUMN subscribers SET NOT NULL,
|
||||||
|
ALTER COLUMN pinned_activities SET NOT NULL,
|
||||||
|
ALTER COLUMN emoji SET NOT NULL,
|
||||||
|
ALTER COLUMN fields SET NOT NULL,
|
||||||
|
ALTER COLUMN raw_fields SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("ALTER TABLE users
|
||||||
|
ALTER COLUMN local DROP NOT NULL,
|
||||||
|
ALTER COLUMN source_data DROP NOT NULL,
|
||||||
|
ALTER COLUMN note_count DROP NOT NULL,
|
||||||
|
ALTER COLUMN follower_count DROP NOT NULL,
|
||||||
|
ALTER COLUMN blocks DROP NOT NULL,
|
||||||
|
ALTER COLUMN domain_blocks DROP NOT NULL,
|
||||||
|
ALTER COLUMN mutes DROP NOT NULL,
|
||||||
|
ALTER COLUMN muted_reblogs DROP NOT NULL,
|
||||||
|
ALTER COLUMN muted_notifications DROP NOT NULL,
|
||||||
|
ALTER COLUMN subscribers DROP NOT NULL,
|
||||||
|
ALTER COLUMN pinned_activities DROP NOT NULL,
|
||||||
|
ALTER COLUMN emoji DROP NOT NULL,
|
||||||
|
ALTER COLUMN fields DROP NOT NULL,
|
||||||
|
ALTER COLUMN raw_fields DROP NOT NULL")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.MigrateMissingFollowingRelationships do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
execute(import_pending_follows_from_activities(), "")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp import_pending_follows_from_activities do
|
||||||
|
"""
|
||||||
|
INSERT INTO
|
||||||
|
following_relationships (
|
||||||
|
follower_id,
|
||||||
|
following_id,
|
||||||
|
state,
|
||||||
|
inserted_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
followers.id,
|
||||||
|
following.id,
|
||||||
|
activities.data ->> 'state',
|
||||||
|
(activities.data ->> 'published') :: timestamp,
|
||||||
|
now()
|
||||||
|
FROM
|
||||||
|
activities
|
||||||
|
JOIN users AS followers ON (activities.actor = followers.ap_id)
|
||||||
|
JOIN users AS following ON (activities.data ->> 'object' = following.ap_id)
|
||||||
|
WHERE
|
||||||
|
activities.data ->> 'type' = 'Follow'
|
||||||
|
AND activities.data ->> 'state' = 'pending'
|
||||||
|
ORDER BY activities.updated_at DESC
|
||||||
|
ON CONFLICT DO NOTHING
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
|
@ -140,11 +140,15 @@ else
|
||||||
FULL_ARGS="$*"
|
FULL_ARGS="$*"
|
||||||
|
|
||||||
ACTION="$1"
|
ACTION="$1"
|
||||||
shift
|
if [ $# -gt 0 ]; then
|
||||||
echo "$1" | grep "^-" >/dev/null
|
shift
|
||||||
|
fi
|
||||||
|
echo "$1" | grep "^-" >/dev/null
|
||||||
if [ $? -eq 1 ]; then
|
if [ $? -eq 1 ]; then
|
||||||
SUBACTION="$1"
|
SUBACTION="$1"
|
||||||
shift
|
if [ $# -gt 0 ]; then
|
||||||
|
shift
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ACTION" = "update" ]; then
|
if [ "$ACTION" = "update" ]; then
|
||||||
|
|
|
@ -140,7 +140,7 @@ test "it marks all the user's participations as read" do
|
||||||
participation2 = insert(:participation, %{read: false, user: user})
|
participation2 = insert(:participation, %{read: false, user: user})
|
||||||
participation3 = insert(:participation, %{read: false, user: other_user})
|
participation3 = insert(:participation, %{read: false, user: other_user})
|
||||||
|
|
||||||
{:ok, [%{read: true}, %{read: true}]} = Participation.mark_all_as_read(user)
|
{:ok, _, [%{read: true}, %{read: true}]} = Participation.mark_all_as_read(user)
|
||||||
|
|
||||||
assert Participation.get(participation1.id).read == true
|
assert Participation.get(participation1.id).read == true
|
||||||
assert Participation.get(participation2.id).read == true
|
assert Participation.get(participation2.id).read == true
|
||||||
|
@ -216,4 +216,134 @@ test "it sets recipients, always keeping the owner of the participation even whe
|
||||||
assert user in participation.recipients
|
assert user in participation.recipients
|
||||||
assert other_user in participation.recipients
|
assert other_user in participation.recipients
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "blocking" do
|
||||||
|
test "when the user blocks a recipient, the existing conversations with them are marked as read" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _direct1} =
|
||||||
|
CommonAPI.post(third_user, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _direct2} =
|
||||||
|
CommonAPI.post(third_user, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}, @#{blocked.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _direct3} =
|
||||||
|
CommonAPI.post(blocked, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _direct4} =
|
||||||
|
CommonAPI.post(blocked, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}, @#{third_user.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
|
|
||||||
|
# The conversations with the blocked user are marked as read
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the new conversation with the blocked user is not marked as unread " do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
|
|
||||||
|
# When the blocked user is the author
|
||||||
|
{:ok, _direct1} =
|
||||||
|
CommonAPI.post(blocked, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [%{read: true}] = Participation.for_user(blocker)
|
||||||
|
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
|
||||||
|
|
||||||
|
# When the blocked user is a recipient
|
||||||
|
{:ok, _direct2} =
|
||||||
|
CommonAPI.post(third_user, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}, @#{blocked.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
|
||||||
|
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
|
||||||
|
|
||||||
|
assert [%{read: false}, _] = Participation.for_user(blocked)
|
||||||
|
assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the conversation with the blocked user is not marked as unread on a reply" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _direct1} =
|
||||||
|
CommonAPI.post(blocker, %{
|
||||||
|
"status" => "Hi @#{third_user.nickname}, @#{blocked.nickname}",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
|
assert [%{read: true}] = Participation.for_user(blocker)
|
||||||
|
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
|
||||||
|
|
||||||
|
assert [blocked_participation] = Participation.for_user(blocked)
|
||||||
|
|
||||||
|
# When it's a reply from the blocked user
|
||||||
|
{:ok, _direct2} =
|
||||||
|
CommonAPI.post(blocked, %{
|
||||||
|
"status" => "reply",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_conversation_id" => blocked_participation.id
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [%{read: true}] = Participation.for_user(blocker)
|
||||||
|
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
|
||||||
|
|
||||||
|
assert [third_user_participation] = Participation.for_user(third_user)
|
||||||
|
|
||||||
|
# When it's a reply from the third user
|
||||||
|
{:ok, _direct3} =
|
||||||
|
CommonAPI.post(third_user, %{
|
||||||
|
"status" => "reply",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_conversation_id" => third_user_participation.id
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [%{read: true}] = Participation.for_user(blocker)
|
||||||
|
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 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
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,8 +39,7 @@ def user_factory do
|
||||||
user
|
user
|
||||||
| ap_id: User.ap_id(user),
|
| ap_id: User.ap_id(user),
|
||||||
follower_address: User.ap_followers(user),
|
follower_address: User.ap_followers(user),
|
||||||
following_address: User.ap_following(user),
|
following_address: User.ap_following(user)
|
||||||
following: [User.ap_id(user)]
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -72,24 +72,25 @@ test "it prunes old objects from the database" do
|
||||||
describe "running update_users_following_followers_counts" do
|
describe "running update_users_following_followers_counts" do
|
||||||
test "following and followers count are updated" do
|
test "following and followers count are updated" do
|
||||||
[user, user2] = insert_pair(:user)
|
[user, user2] = insert_pair(:user)
|
||||||
{:ok, %User{following: following} = user} = User.follow(user, user2)
|
{:ok, %User{} = user} = User.follow(user, user2)
|
||||||
|
|
||||||
|
following = User.following(user)
|
||||||
|
|
||||||
assert length(following) == 2
|
assert length(following) == 2
|
||||||
assert user.follower_count == 0
|
assert user.follower_count == 0
|
||||||
|
|
||||||
{:ok, user} =
|
{:ok, user} =
|
||||||
user
|
user
|
||||||
|> Ecto.Changeset.change(%{following: following ++ following, follower_count: 3})
|
|> Ecto.Changeset.change(%{follower_count: 3})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|
||||||
assert length(user.following) == 4
|
|
||||||
assert user.follower_count == 3
|
assert user.follower_count == 3
|
||||||
|
|
||||||
assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
|
assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
|
||||||
|
|
||||||
user = User.get_by_id(user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert length(user.following) == 2
|
assert length(User.following(user)) == 2
|
||||||
assert user.follower_count == 0
|
assert user.follower_count == 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,7 @@ test "relay is unfollowed" do
|
||||||
target_user = User.get_cached_by_ap_id(target_instance)
|
target_user = User.get_cached_by_ap_id(target_instance)
|
||||||
follow_activity = Utils.fetch_latest_follow(local_user, target_user)
|
follow_activity = Utils.fetch_latest_follow(local_user, target_user)
|
||||||
User.follow(local_user, target_user)
|
User.follow(local_user, target_user)
|
||||||
assert "#{target_instance}/followers" in refresh_record(local_user).following
|
assert "#{target_instance}/followers" in User.following(local_user)
|
||||||
Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])
|
Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])
|
||||||
|
|
||||||
cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
|
cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
|
||||||
|
@ -68,7 +68,7 @@ test "relay is unfollowed" do
|
||||||
assert undo_activity.data["type"] == "Undo"
|
assert undo_activity.data["type"] == "Undo"
|
||||||
assert undo_activity.data["actor"] == local_user.ap_id
|
assert undo_activity.data["actor"] == local_user.ap_id
|
||||||
assert undo_activity.data["object"] == cancelled_activity.data
|
assert undo_activity.data["object"] == cancelled_activity.data
|
||||||
refute "#{target_instance}/followers" in refresh_record(local_user).following
|
refute "#{target_instance}/followers" in User.following(local_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -78,20 +78,18 @@ test "Prints relay subscription list" do
|
||||||
|
|
||||||
refute_receive {:mix_shell, :info, _}
|
refute_receive {:mix_shell, :info, _}
|
||||||
|
|
||||||
Pleroma.Web.ActivityPub.Relay.get_actor()
|
relay_user = Relay.get_actor()
|
||||||
|> Ecto.Changeset.change(
|
|
||||||
following: [
|
["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
|
||||||
"http://test-app.com/user/test1",
|
|> Enum.each(fn ap_id ->
|
||||||
"http://test-app.com/user/test1",
|
{:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
|
||||||
"http://test-app-42.com/user/test1"
|
User.follow(relay_user, user)
|
||||||
]
|
end)
|
||||||
)
|
|
||||||
|> Pleroma.User.update_and_set_cache()
|
|
||||||
|
|
||||||
:ok = Mix.Tasks.Pleroma.Relay.run(["list"])
|
:ok = Mix.Tasks.Pleroma.Relay.run(["list"])
|
||||||
|
|
||||||
assert_receive {:mix_shell, :info, ["test-app.com"]}
|
assert_receive {:mix_shell, :info, ["mstdn.io"]}
|
||||||
assert_receive {:mix_shell, :info, ["test-app-42.com"]}
|
assert_receive {:mix_shell, :info, ["mastodon.example.org"]}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -139,7 +139,8 @@ test "no user to toggle" do
|
||||||
describe "running unsubscribe" do
|
describe "running unsubscribe" do
|
||||||
test "user is unsubscribed" do
|
test "user is unsubscribed" do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
user = insert(:user)
|
||||||
|
User.follow(user, followed, "accept")
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
|
Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
|
||||||
|
|
||||||
|
@ -154,7 +155,7 @@ test "user is unsubscribed" do
|
||||||
assert message =~ "Successfully unsubscribed"
|
assert message =~ "Successfully unsubscribed"
|
||||||
|
|
||||||
user = User.get_cached_by_nickname(user.nickname)
|
user = User.get_cached_by_nickname(user.nickname)
|
||||||
assert Enum.empty?(user.following)
|
assert Enum.empty?(User.get_friends(user))
|
||||||
assert user.deactivated
|
assert user.deactivated
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -88,10 +88,9 @@ test "doesn't return already accepted or duplicate follow requests" do
|
||||||
CommonAPI.follow(pending_follower, locked)
|
CommonAPI.follow(pending_follower, locked)
|
||||||
CommonAPI.follow(pending_follower, locked)
|
CommonAPI.follow(pending_follower, locked)
|
||||||
CommonAPI.follow(accepted_follower, locked)
|
CommonAPI.follow(accepted_follower, locked)
|
||||||
User.follow(accepted_follower, locked)
|
Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept")
|
||||||
|
|
||||||
assert [activity] = User.get_follow_requests(locked)
|
assert [^pending_follower] = User.get_follow_requests(locked)
|
||||||
assert activity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "clears follow requests when requester is blocked" do
|
test "clears follow requests when requester is blocked" do
|
||||||
|
@ -136,10 +135,10 @@ test "follow_all follows mutliple users without duplicating" do
|
||||||
followed_two = insert(:user)
|
followed_two = insert(:user)
|
||||||
|
|
||||||
{:ok, user} = User.follow_all(user, [followed_zero, followed_one])
|
{:ok, user} = User.follow_all(user, [followed_zero, followed_one])
|
||||||
assert length(user.following) == 3
|
assert length(User.following(user)) == 3
|
||||||
|
|
||||||
{:ok, user} = User.follow_all(user, [followed_one, followed_two])
|
{:ok, user} = User.follow_all(user, [followed_one, followed_two])
|
||||||
assert length(user.following) == 4
|
assert length(User.following(user)) == 4
|
||||||
end
|
end
|
||||||
|
|
||||||
test "follow takes a user and another user" do
|
test "follow takes a user and another user" do
|
||||||
|
@ -153,7 +152,7 @@ test "follow takes a user and another user" do
|
||||||
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.ap_followers(followed) in user.following
|
assert User.ap_followers(followed) in User.following(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can't follow a deactivated users" do
|
test "can't follow a deactivated users" do
|
||||||
|
@ -218,26 +217,29 @@ test "unfollow with syncronizes external user" do
|
||||||
nickname: "fuser2",
|
nickname: "fuser2",
|
||||||
ap_id: "http://localhost:4001/users/fuser2",
|
ap_id: "http://localhost:4001/users/fuser2",
|
||||||
follower_address: "http://localhost:4001/users/fuser2/followers",
|
follower_address: "http://localhost:4001/users/fuser2/followers",
|
||||||
following_address: "http://localhost:4001/users/fuser2/following",
|
following_address: "http://localhost:4001/users/fuser2/following"
|
||||||
following: [User.ap_followers(followed)]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, followed, "accept")
|
||||||
|
|
||||||
{:ok, user, _activity} = User.unfollow(user, followed)
|
{:ok, user, _activity} = User.unfollow(user, followed)
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
assert user.following == []
|
assert User.following(user) == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unfollow takes a user and another user" do
|
test "unfollow takes a user and another user" do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, followed, "accept")
|
||||||
|
|
||||||
|
assert User.following(user) == [user.follower_address, followed.follower_address]
|
||||||
|
|
||||||
{:ok, user, _activity} = User.unfollow(user, followed)
|
{:ok, user, _activity} = User.unfollow(user, followed)
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
assert User.following(user) == [user.follower_address]
|
||||||
|
|
||||||
assert user.following == []
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unfollow doesn't unfollow yourself" do
|
test "unfollow doesn't unfollow yourself" do
|
||||||
|
@ -245,14 +247,14 @@ test "unfollow doesn't unfollow yourself" do
|
||||||
|
|
||||||
{:error, _} = User.unfollow(user, user)
|
{:error, _} = User.unfollow(user, user)
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
assert User.following(user) == [user.follower_address]
|
||||||
assert user.following == [user.ap_id]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "test if a user is following another user" do
|
test "test if a user is following another user" do
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
user = insert(:user, %{following: [User.ap_followers(followed)]})
|
user = insert(:user)
|
||||||
|
User.follow(user, followed, "accept")
|
||||||
|
|
||||||
assert User.following?(user, followed)
|
assert User.following?(user, followed)
|
||||||
refute User.following?(followed, user)
|
refute User.following?(followed, user)
|
||||||
|
@ -335,7 +337,7 @@ test "it restricts certain nicknames" do
|
||||||
refute changeset.valid?
|
refute changeset.valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it sets the password_hash, ap_id and following fields" do
|
test "it sets the password_hash and ap_id" do
|
||||||
changeset = User.register_changeset(%User{}, @full_user_data)
|
changeset = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
|
@ -343,10 +345,6 @@ test "it sets the password_hash, ap_id and following fields" do
|
||||||
assert is_binary(changeset.changes[:password_hash])
|
assert is_binary(changeset.changes[:password_hash])
|
||||||
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
||||||
|
|
||||||
assert changeset.changes[:following] == [
|
|
||||||
User.ap_followers(%User{nickname: @full_user_data.nickname})
|
|
||||||
]
|
|
||||||
|
|
||||||
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -650,37 +648,6 @@ test "it sets the follower_count property" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "remove duplicates from following list" do
|
|
||||||
test "it removes duplicates" do
|
|
||||||
user = insert(:user)
|
|
||||||
follower = insert(:user)
|
|
||||||
|
|
||||||
{:ok, %User{following: following} = follower} = User.follow(follower, user)
|
|
||||||
assert length(following) == 2
|
|
||||||
|
|
||||||
{:ok, follower} =
|
|
||||||
follower
|
|
||||||
|> User.update_changeset(%{following: following ++ following})
|
|
||||||
|> Repo.update()
|
|
||||||
|
|
||||||
assert length(follower.following) == 4
|
|
||||||
|
|
||||||
{:ok, follower} = User.remove_duplicated_following(follower)
|
|
||||||
assert length(follower.following) == 2
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it does nothing when following is uniq" do
|
|
||||||
user = insert(:user)
|
|
||||||
follower = insert(:user)
|
|
||||||
|
|
||||||
{:ok, follower} = User.follow(follower, user)
|
|
||||||
assert length(follower.following) == 2
|
|
||||||
|
|
||||||
{:ok, follower} = User.remove_duplicated_following(follower)
|
|
||||||
assert length(follower.following) == 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "follow_import" do
|
describe "follow_import" do
|
||||||
test "it imports user followings from list" do
|
test "it imports user followings from list" do
|
||||||
[user1, user2, user3] = insert_list(3, :user)
|
[user1, user2, user3] = insert_list(3, :user)
|
||||||
|
@ -989,7 +956,9 @@ test "hide a user's statuses from timelines and notifications" do
|
||||||
assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
|
assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
|
||||||
|
|
||||||
assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] ==
|
assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] ==
|
||||||
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{
|
||||||
|
"user" => user2
|
||||||
|
})
|
||||||
|
|
||||||
{:ok, _user} = User.deactivate(user)
|
{:ok, _user} = User.deactivate(user)
|
||||||
|
|
||||||
|
@ -997,7 +966,9 @@ test "hide a user's statuses from timelines and notifications" do
|
||||||
assert [] == Pleroma.Notification.for_user(user2)
|
assert [] == Pleroma.Notification.for_user(user2)
|
||||||
|
|
||||||
assert [] ==
|
assert [] ==
|
||||||
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|
ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{
|
||||||
|
"user" => user2
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -693,7 +693,7 @@ test "does include announces on request" do
|
||||||
|
|
||||||
{:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
|
{:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
|
||||||
|
|
||||||
[announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
|
[announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
|
||||||
|
|
||||||
assert announce_activity.id == announce.id
|
assert announce_activity.id == announce.id
|
||||||
end
|
end
|
||||||
|
@ -1219,7 +1219,7 @@ test "it filters broken threads" do
|
||||||
})
|
})
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities([user1.ap_id | user1.following])
|
ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
|
||||||
|> Enum.map(fn a -> a.id end)
|
|> Enum.map(fn a -> a.id end)
|
||||||
|
|
||||||
private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
|
private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
|
||||||
|
@ -1229,7 +1229,7 @@ test "it filters broken threads" do
|
||||||
assert length(activities) == 3
|
assert length(activities) == 3
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
|
ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})
|
||||||
|> Enum.map(fn a -> a.id end)
|
|> Enum.map(fn a -> a.id end)
|
||||||
|
|
||||||
assert [public_activity.id, private_activity_1.id] == activities
|
assert [public_activity.id, private_activity_1.id] == activities
|
||||||
|
|
|
@ -56,14 +56,14 @@ test "returns activity" do
|
||||||
service_actor = Relay.get_actor()
|
service_actor = Relay.get_actor()
|
||||||
ActivityPub.follow(service_actor, user)
|
ActivityPub.follow(service_actor, user)
|
||||||
Pleroma.User.follow(service_actor, user)
|
Pleroma.User.follow(service_actor, user)
|
||||||
assert "#{user.ap_id}/followers" in refresh_record(service_actor).following
|
assert "#{user.ap_id}/followers" in User.following(service_actor)
|
||||||
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
|
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
|
||||||
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||||
assert user.ap_id in activity.recipients
|
assert user.ap_id in activity.recipients
|
||||||
assert activity.data["type"] == "Undo"
|
assert activity.data["type"] == "Undo"
|
||||||
assert activity.data["actor"] == service_actor.ap_id
|
assert activity.data["actor"] == service_actor.ap_id
|
||||||
assert activity.data["to"] == [user.ap_id]
|
assert activity.data["to"] == [user.ap_id]
|
||||||
refute "#{user.ap_id}/followers" in refresh_record(service_actor).following
|
refute "#{user.ap_id}/followers" in User.following(service_actor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -720,7 +720,7 @@ test "it fails for incoming deletes with spoofed origin" do
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
:error = Transmogrifier.handle_incoming(data)
|
:error = Transmogrifier.handle_incoming(data)
|
||||||
end) =~
|
end) =~
|
||||||
"[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}"
|
"[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}"
|
||||||
|
|
||||||
assert Activity.get_by_id(activity.id)
|
assert Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
@ -804,6 +804,25 @@ test "it works for incomming unfollows with an existing follow" do
|
||||||
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
|
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works for incoming follows to locked account" do
|
||||||
|
pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
|
||||||
|
user = insert(:user, locked: true)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-follow-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", user.ap_id)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["type"] == "Follow"
|
||||||
|
assert data["object"] == user.ap_id
|
||||||
|
assert data["state"] == "pending"
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
assert [^pending_follower] = User.get_follow_requests(user)
|
||||||
|
end
|
||||||
|
|
||||||
test "it works for incoming blocks" do
|
test "it works for incoming blocks" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
@ -1334,7 +1353,8 @@ test "it upgrades a user to activitypub" do
|
||||||
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
|
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
|
||||||
})
|
})
|
||||||
|
|
||||||
user_two = insert(:user, %{following: [user.follower_address]})
|
user_two = insert(:user)
|
||||||
|
Pleroma.FollowingRelationship.follow(user_two, user, "accept")
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
|
||||||
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
||||||
|
@ -1381,8 +1401,8 @@ test "it upgrades a user to activitypub" do
|
||||||
refute user.follower_address in unrelated_activity.recipients
|
refute user.follower_address in unrelated_activity.recipients
|
||||||
|
|
||||||
user_two = User.get_cached_by_id(user_two.id)
|
user_two = User.get_cached_by_id(user_two.id)
|
||||||
assert user.follower_address in user_two.following
|
assert User.following?(user_two, user)
|
||||||
refute "..." in user_two.following
|
refute "..." in User.following(user_two)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,8 @@ test "returns false when invalid recipients", %{user: user} do
|
||||||
|
|
||||||
test "returns true if user following to author" do
|
test "returns true if user following to author" do
|
||||||
author = insert(:user)
|
author = insert(:user)
|
||||||
user = insert(:user, following: [author.ap_id])
|
user = insert(:user)
|
||||||
|
Pleroma.User.follow(user, author)
|
||||||
|
|
||||||
activity =
|
activity =
|
||||||
insert(:note_activity,
|
insert(:note_activity,
|
||||||
|
|
|
@ -2572,22 +2572,20 @@ test "POST /relay", %{admin: admin} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /relay", %{admin: admin} do
|
test "GET /relay", %{admin: admin} do
|
||||||
Pleroma.Web.ActivityPub.Relay.get_actor()
|
relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
|
||||||
|> Ecto.Changeset.change(
|
|
||||||
following: [
|
["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
|
||||||
"http://test-app.com/user/test1",
|
|> Enum.each(fn ap_id ->
|
||||||
"http://test-app.com/user/test1",
|
{:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
|
||||||
"http://test-app-42.com/user/test1"
|
User.follow(relay_user, user)
|
||||||
]
|
end)
|
||||||
)
|
|
||||||
|> Pleroma.User.update_and_set_cache()
|
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|> assign(:user, admin)
|
|> assign(:user, admin)
|
||||||
|> get("/api/pleroma/admin/relay")
|
|> get("/api/pleroma/admin/relay")
|
||||||
|
|
||||||
assert json_response(conn, 200)["relays"] -- ["test-app.com", "test-app-42.com"] == []
|
assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "DELETE /relay", %{admin: admin} do
|
test "DELETE /relay", %{admin: admin} do
|
||||||
|
|
|
@ -471,7 +471,7 @@ test "following without reblogs" do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|> assign(:user, follower)
|
|> assign(:user, User.get_cached_by_id(follower.id))
|
||||||
|> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
|
|> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
|
||||||
|
|
||||||
assert %{"showing_reblogs" => true} = json_response(conn, 200)
|
assert %{"showing_reblogs" => true} = json_response(conn, 200)
|
||||||
|
|
|
@ -16,9 +16,7 @@ test "/api/v1/follow_requests works" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
{:ok, other_user} = User.follow(other_user, user, "pending")
|
||||||
user = User.get_cached_by_id(user.id)
|
|
||||||
other_user = User.get_cached_by_id(other_user.id)
|
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
@ -36,6 +34,7 @@ test "/api/v1/follow_requests/:id/authorize works" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
{:ok, other_user} = User.follow(other_user, user, "pending")
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
other_user = User.get_cached_by_id(other_user.id)
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
|
@ -169,7 +169,8 @@ test "it sends to public" do
|
||||||
test "it doesn't send to user if recipients invalid and thread containment is enabled" do
|
test "it doesn't send to user if recipients invalid and thread containment is enabled" do
|
||||||
Pleroma.Config.put([:instance, :skip_thread_containment], false)
|
Pleroma.Config.put([:instance, :skip_thread_containment], false)
|
||||||
author = insert(:user)
|
author = insert(:user)
|
||||||
user = insert(:user, following: [author.ap_id])
|
user = insert(:user)
|
||||||
|
User.follow(user, author, "accept")
|
||||||
|
|
||||||
activity =
|
activity =
|
||||||
insert(:note_activity,
|
insert(:note_activity,
|
||||||
|
@ -191,7 +192,8 @@ test "it doesn't send to user if recipients invalid and thread containment is en
|
||||||
test "it sends message if recipients invalid and thread containment is disabled" do
|
test "it sends message if recipients invalid and thread containment is disabled" do
|
||||||
Pleroma.Config.put([:instance, :skip_thread_containment], true)
|
Pleroma.Config.put([:instance, :skip_thread_containment], true)
|
||||||
author = insert(:user)
|
author = insert(:user)
|
||||||
user = insert(:user, following: [author.ap_id])
|
user = insert(:user)
|
||||||
|
User.follow(user, author, "accept")
|
||||||
|
|
||||||
activity =
|
activity =
|
||||||
insert(:note_activity,
|
insert(:note_activity,
|
||||||
|
@ -213,7 +215,8 @@ test "it sends message if recipients invalid and thread containment is disabled"
|
||||||
test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do
|
test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do
|
||||||
Pleroma.Config.put([:instance, :skip_thread_containment], false)
|
Pleroma.Config.put([:instance, :skip_thread_containment], false)
|
||||||
author = insert(:user)
|
author = insert(:user)
|
||||||
user = insert(:user, following: [author.ap_id], skip_thread_containment: true)
|
user = insert(:user, skip_thread_containment: true)
|
||||||
|
User.follow(user, author, "accept")
|
||||||
|
|
||||||
activity =
|
activity =
|
||||||
insert(:note_activity,
|
insert(:note_activity,
|
||||||
|
|
|
@ -366,7 +366,7 @@ test "follows user", %{conn: conn} do
|
||||||
|> response(200)
|
|> response(200)
|
||||||
|
|
||||||
assert response =~ "Account followed!"
|
assert response =~ "Account followed!"
|
||||||
assert user2.follower_address in refresh_record(user).following
|
assert user2.follower_address in User.following(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns error when user is deactivated", %{conn: conn} do
|
test "returns error when user is deactivated", %{conn: conn} do
|
||||||
|
@ -438,7 +438,7 @@ test "follows", %{conn: conn} do
|
||||||
|> response(200)
|
|> response(200)
|
||||||
|
|
||||||
assert response =~ "Account followed!"
|
assert response =~ "Account followed!"
|
||||||
assert user2.follower_address in refresh_record(user).following
|
assert user2.follower_address in User.following(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns error when followee not found", %{conn: conn} do
|
test "returns error when followee not found", %{conn: conn} do
|
||||||
|
|
Loading…
Reference in a new issue