[#1335] Refactored UserMute and UserBlock into UserRelationship, introduced EctoEnum.

This commit is contained in:
Ivan Tashkinov 2019-11-18 20:38:56 +03:00
parent 01d9c093c3
commit aad6576130
15 changed files with 239 additions and 317 deletions

View File

@ -0,0 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
import EctoEnum
defenum(UserRelationshipTypeEnum, block: 1, mute: 2, reblog_mute: 3, notification_mute: 4)

View File

@ -22,8 +22,7 @@ defmodule Pleroma.User do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.RepoStreamer alias Pleroma.RepoStreamer
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserBlock alias Pleroma.UserRelationship
alias Pleroma.UserMute
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -78,7 +77,6 @@ defmodule Pleroma.User do
field(:confirmation_token, :string, default: nil) field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public") field(:default_scope, :string, default: "public")
field(:domain_blocks, {:array, :string}, default: []) field(:domain_blocks, {:array, :string}, default: [])
field(:mutes, {:array, :string}, default: [])
field(:muted_reblogs, {:array, :string}, default: []) field(:muted_reblogs, {:array, :string}, default: [])
field(:muted_notifications, {:array, :string}, default: []) field(:muted_notifications, {:array, :string}, default: [])
field(:subscribers, {:array, :string}, default: []) field(:subscribers, {:array, :string}, default: [])
@ -121,20 +119,41 @@ defmodule Pleroma.User do
has_many(:registrations, Registration) has_many(:registrations, Registration)
has_many(:deliveries, Delivery) has_many(:deliveries, Delivery)
has_many(:blocker_blocks, UserBlock, foreign_key: :blocker_id) has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
has_many(:blockee_blocks, UserBlock, foreign_key: :blockee_id) has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
has_many(:blocked_users, through: [:blocker_blocks, :blockee])
has_many(:blocker_users, through: [:blockee_blocks, :blocker])
has_many(:muter_mutes, UserMute, foreign_key: :muter_id) has_many(:blocker_blocks, UserRelationship,
has_many(:mutee_mutes, UserMute, foreign_key: :mutee_id) foreign_key: :source_id,
has_many(:muted_users, through: [:muter_mutes, :mutee]) where: [relationship_type: :block]
has_many(:muter_users, through: [:mutee_mutes, :muter]) )
has_many(:blockee_blocks, UserRelationship,
foreign_key: :target_id,
where: [relationship_type: :block]
)
has_many(:blocked_users, through: [:blocker_blocks, :target])
has_many(:blocker_users, through: [:blockee_blocks, :source])
has_many(:muter_mutes, UserRelationship,
foreign_key: :source_id,
where: [relationship_type: :mute]
)
has_many(:mutee_mutes, UserRelationship,
foreign_key: :target_id,
where: [relationship_type: :mute]
)
has_many(:muted_users, through: [:muter_mutes, :target])
has_many(:muter_users, through: [:mutee_mutes, :source])
field(:info, :map, default: %{}) field(:info, :map, default: %{})
# `:blocks` is deprecated (replaced with `blocked_users` relation) # `:blocks` is deprecated (replaced with `blocked_users` relation)
field(:blocks, {:array, :string}, default: []) field(:blocks, {:array, :string}, default: [])
# `:mutes` is deprecated (replaced with `muted_users` relation)
field(:mutes, {:array, :string}, default: [])
timestamps() timestamps()
end end
@ -1054,7 +1073,7 @@ def mutes?(nil, _), do: false
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target) def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
def mutes_user?(%User{} = user, %User{} = target) do def mutes_user?(%User{} = user, %User{} = target) do
UserMute.exists?(user, target) UserRelationship.mute_exists?(user, target)
end end
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
@ -1070,7 +1089,7 @@ def blocks?(%User{} = user, %User{} = target) do
end end
def blocks_user?(%User{} = user, %User{} = target) do def blocks_user?(%User{} = user, %User{} = target) do
UserBlock.exists?(user, target) UserRelationship.block_exists?(user, target)
end end
def blocks_user?(_, _), do: false def blocks_user?(_, _), do: false
@ -1119,33 +1138,20 @@ def blocked_ap_ids(user) do
|> Repo.all() |> Repo.all()
end end
defp related_ap_ids_sql(join_table, source_column, target_column) do @doc """
"(SELECT array_agg(u.ap_id) FROM users as u " <> Returns map of related AP IDs list by relation type.
"INNER JOIN #{join_table} AS join_table " <> E.g. `related_ap_ids(user, [:blocks])` -> `%{blocks: ["https://some.site/users/userapid"]}`
"ON join_table.#{source_column} = $1 " <> """
"WHERE u.id = join_table.#{target_column})" @spec related_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
end def related_ap_ids(%User{} = user, relationship_types) when is_list(relationship_types) do
user
@related_ap_ids_sql_params %{ |> assoc(:outgoing_relationships)
blocked_users: ["user_blocks", "blocker_id", "blockee_id"], |> join(:inner, [user_rel], u in assoc(user_rel, :target))
muted_users: ["user_mutes", "muter_id", "mutee_id"] |> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
} |> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
|> group_by([user_rel, u], user_rel.relationship_type)
def related_ap_ids(user, relations) when is_list(relations) do |> Repo.all()
query = |> Enum.into(%{}, fn [k, v] -> {k, v} end)
relations
|> Enum.map(fn r -> @related_ap_ids_sql_params[r] end)
|> Enum.filter(& &1)
|> Enum.map(fn [join_table, source_column, target_column] ->
related_ap_ids_sql(join_table, source_column, target_column)
end)
|> Enum.join(", ")
with {:ok, %{rows: [ap_ids_arrays]}} <-
Repo.query("SELECT #{query}", [FlakeId.from_string(user.id)]) do
ap_ids_arrays = Enum.map(ap_ids_arrays, &(&1 || []))
{:ok, ap_ids_arrays}
end
end end
@spec subscribers(User.t()) :: [User.t()] @spec subscribers(User.t()) :: [User.t()]
@ -1918,19 +1924,20 @@ def unblock_domain(user, domain_blocked) do
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked)) set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
end end
@spec add_to_block(User.t(), User.t()) :: {:ok, UserBlock.t()} | {:error, Ecto.Changeset.t()} @spec add_to_block(User.t(), User.t()) ::
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
defp add_to_block(%User{} = user, %User{} = blocked) do defp add_to_block(%User{} = user, %User{} = blocked) do
UserBlock.create(user, blocked) UserRelationship.create_block(user, blocked)
end end
@spec add_to_block(User.t(), User.t()) :: @spec add_to_block(User.t(), User.t()) ::
{:ok, UserBlock.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()} {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
defp remove_from_block(%User{} = user, %User{} = blocked) do defp remove_from_block(%User{} = user, %User{} = blocked) do
UserBlock.delete(user, blocked) UserRelationship.delete_block(user, blocked)
end end
defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notifications?) do
with {:ok, user_mute} <- UserMute.create(user, muted_user), with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
{:ok, _user} <- {:ok, _user} <-
set_notification_mutes( set_notification_mutes(
user, user,
@ -1942,7 +1949,7 @@ defp add_to_mutes(%User{} = user, %User{ap_id: ap_id} = muted_user, notification
end end
defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do defp remove_from_mutes(user, %User{ap_id: ap_id} = muted_user) do
with {:ok, user_mute} <- UserMute.delete(user, muted_user), with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
{:ok, _user} <- {:ok, _user} <-
set_notification_mutes( set_notification_mutes(
user, user,

View File

@ -100,11 +100,11 @@ defp base_query(user, true), do: User.get_followers_query(user)
defp filter_blocked_user(query, %User{} = blocker) do defp filter_blocked_user(query, %User{} = blocker) do
query query
|> join(:left, [u], b in Pleroma.UserBlock, |> join(:left, [u], b in Pleroma.UserRelationship,
as: :blocks, as: :blocks,
on: b.blocker_id == ^blocker.id and u.id == b.blockee_id on: b.relationship_type == ^:block and b.source_id == ^blocker.id and u.id == b.target_id
) )
|> where([blocks: b], is_nil(b.blockee_id)) |> where([blocks: b], is_nil(b.target_id))
end end
defp filter_blocked_user(query, _), do: query defp filter_blocked_user(query, _), do: query

View File

@ -1,71 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserBlock do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserBlock
schema "user_blocks" do
belongs_to(:blocker, User, type: FlakeId.Ecto.CompatType)
belongs_to(:blockee, User, type: FlakeId.Ecto.CompatType)
timestamps(updated_at: false)
end
def changeset(%UserBlock{} = user_block, params \\ %{}) do
user_block
|> cast(params, [:blocker_id, :blockee_id])
|> validate_required([:blocker_id, :blockee_id])
|> unique_constraint(:blockee_id, name: :user_blocks_blocker_id_blockee_id_index)
|> validate_not_self_block()
end
def exists?(%User{} = blocker, %User{} = blockee) do
UserBlock
|> where(blocker_id: ^blocker.id, blockee_id: ^blockee.id)
|> Repo.exists?()
end
def create(%User{} = blocker, %User{} = blockee) do
%UserBlock{}
|> changeset(%{blocker_id: blocker.id, blockee_id: blockee.id})
|> Repo.insert(
on_conflict: :replace_all_except_primary_key,
conflict_target: [:blocker_id, :blockee_id]
)
end
def delete(%User{} = blocker, %User{} = blockee) do
attrs = %{blocker_id: blocker.id, blockee_id: blockee.id}
case Repo.get_by(UserBlock, attrs) do
%UserBlock{} = existing_record -> Repo.delete(existing_record)
nil -> {:ok, nil}
end
end
defp validate_not_self_block(%Ecto.Changeset{} = changeset) do
changeset
|> validate_change(:blockee_id, fn _, blockee_id ->
if blockee_id == get_field(changeset, :blocker_id) do
[blockee_id: "can't be equal to blocker_id"]
else
[]
end
end)
|> validate_change(:blocker_id, fn _, blocker_id ->
if blocker_id == get_field(changeset, :blockee_id) do
[blocker_id: "can't be equal to blockee_id"]
else
[]
end
end)
end
end

View File

@ -1,71 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserMute do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserMute
schema "user_mutes" do
belongs_to(:muter, User, type: FlakeId.Ecto.CompatType)
belongs_to(:mutee, User, type: FlakeId.Ecto.CompatType)
timestamps(updated_at: false)
end
def changeset(%UserMute{} = user_mute, params \\ %{}) do
user_mute
|> cast(params, [:muter_id, :mutee_id])
|> validate_required([:muter_id, :mutee_id])
|> unique_constraint(:mutee_id, name: :user_mutes_muter_id_mutee_id_index)
|> validate_not_self_mute()
end
def exists?(%User{} = muter, %User{} = mutee) do
UserMute
|> where(muter_id: ^muter.id, mutee_id: ^mutee.id)
|> Repo.exists?()
end
def create(%User{} = muter, %User{} = mutee) do
%UserMute{}
|> changeset(%{muter_id: muter.id, mutee_id: mutee.id})
|> Repo.insert(
on_conflict: :replace_all_except_primary_key,
conflict_target: [:muter_id, :mutee_id]
)
end
def delete(%User{} = muter, %User{} = mutee) do
attrs = %{muter_id: muter.id, mutee_id: mutee.id}
case Repo.get_by(UserMute, attrs) do
%UserMute{} = existing_record -> Repo.delete(existing_record)
nil -> {:ok, nil}
end
end
defp validate_not_self_mute(%Ecto.Changeset{} = changeset) do
changeset
|> validate_change(:mutee_id, fn _, mutee_id ->
if mutee_id == get_field(changeset, :muter_id) do
[mutee_id: "can't be equal to muter_id"]
else
[]
end
end)
|> validate_change(:muter_id, fn _, muter_id ->
if muter_id == get_field(changeset, :mutee_id) do
[muter_id: "can't be equal to mutee_id"]
else
[]
end
end)
end
end

View File

@ -0,0 +1,90 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserRelationship do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserRelationship
schema "user_relationships" do
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
field(:relationship_type, UserRelationshipTypeEnum)
timestamps(updated_at: false)
end
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
user_relationship
|> cast(params, [:relationship_type, :source_id, :target_id])
|> validate_required([:relationship_type, :source_id, :target_id])
|> unique_constraint(:relationship_type,
name: :user_relationships_source_id_relationship_type_target_id_index
)
|> validate_not_self_relationship()
end
def exists?(relationship_type, %User{} = source, %User{} = target) do
UserRelationship
|> where(relationship_type: ^relationship_type, source_id: ^source.id, target_id: ^target.id)
|> Repo.exists?()
end
def block_exists?(%User{} = blocker, %User{} = blockee), do: exists?(:block, blocker, blockee)
def mute_exists?(%User{} = muter, %User{} = mutee), do: exists?(:mute, muter, mutee)
def create(relationship_type, %User{} = source, %User{} = target) do
%UserRelationship{}
|> changeset(%{
relationship_type: relationship_type,
source_id: source.id,
target_id: target.id
})
|> Repo.insert(
on_conflict: :replace_all_except_primary_key,
conflict_target: [:source_id, :relationship_type, :target_id]
)
end
def create_block(%User{} = blocker, %User{} = blockee), do: create(:block, blocker, blockee)
def create_mute(%User{} = muter, %User{} = mutee), do: create(:mute, muter, mutee)
def delete(relationship_type, %User{} = source, %User{} = target) do
attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id}
case Repo.get_by(UserRelationship, attrs) do
%UserRelationship{} = existing_record -> Repo.delete(existing_record)
nil -> {:ok, nil}
end
end
def delete_block(%User{} = blocker, %User{} = blockee), do: delete(:block, blocker, blockee)
def delete_mute(%User{} = muter, %User{} = mutee), do: delete(:mute, muter, mutee)
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
changeset
|> validate_change(:target_id, fn _, target_id ->
if target_id == get_field(changeset, :source_id) do
[target_id: "can't be equal to source_id"]
else
[]
end
end)
|> validate_change(:source_id, fn _, source_id ->
if source_id == get_field(changeset, :target_id) do
[source_id: "can't be equal to target_id"]
else
[]
end
end)
end
end

View File

@ -129,7 +129,9 @@ defp do_stream(%{topic: topic, item: item}) do
end end
defp should_send?(%User{} = user, %Activity{} = item) do defp should_send?(%User{} = user, %Activity{} = item) do
{:ok, [blocks, mutes]} = User.related_ap_ids(user, [:blocked_users, :muted_users]) related_ap_ids = User.related_ap_ids(user, [:block, :mute])
blocks = related_ap_ids[:block] || []
mutes = related_ap_ids[:mute] || []
reblog_mutes = user.muted_reblogs || [] reblog_mutes = user.muted_reblogs || []
recipient_blocks = MapSet.new(blocks ++ mutes) recipient_blocks = MapSet.new(blocks ++ mutes)
recipients = MapSet.new(item.recipients) recipients = MapSet.new(item.recipients)

View File

@ -100,6 +100,7 @@ defp deps do
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.0"},
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.2"}, {:ecto_sql, "~> 3.2"},
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
{:oban, "~> 0.8.1"}, {:oban, "~> 0.8.1"},

View File

@ -24,6 +24,7 @@
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.2.3", "51274df79862845b388733fddcf6f107d0c8c86e27abe7131fa98f8d30761bda", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto": {:hex, :ecto, "3.2.3", "51274df79862845b388733fddcf6f107d0c8c86e27abe7131fa98f8d30761bda", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},

View File

@ -1,14 +0,0 @@
defmodule Pleroma.Repo.Migrations.CreateUserBlocks do
use Ecto.Migration
def change do
create_if_not_exists table(:user_blocks) do
add(:blocker_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:blockee_id, references(:users, type: :uuid, on_delete: :delete_all))
timestamps(updated_at: false)
end
create_if_not_exists(unique_index(:user_blocks, [:blocker_id, :blockee_id]))
end
end

View File

@ -1,49 +0,0 @@
defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserBlocks do
use Ecto.Migration
alias Ecto.Adapters.SQL
alias Pleroma.Repo
require Logger
def up do
{:ok, %{rows: block_rows}} =
SQL.query(Repo, "SELECT id, blocks FROM users WHERE blocks != '{}'")
blockee_ap_ids =
Enum.flat_map(
block_rows,
fn [_, ap_ids] -> ap_ids end
)
|> Enum.uniq()
# Selecting ids of all blockees at once in order to reduce the number of SELECT queries
{:ok, %{rows: blockee_ap_id_id}} =
SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [blockee_ap_ids])
blockee_id_by_ap_id = Enum.into(blockee_ap_id_id, %{}, fn [k, v] -> {k, v} end)
Enum.each(
block_rows,
fn [blocker_id, blockee_ap_ids] ->
blocker_uuid = Ecto.UUID.cast!(blocker_id)
for blockee_ap_id <- blockee_ap_ids do
blockee_id = blockee_id_by_ap_id[blockee_ap_id]
with {:ok, blockee_uuid} <- blockee_id && Ecto.UUID.cast(blockee_id) do
execute(
"INSERT INTO user_blocks(blocker_id, blockee_id, inserted_at) " <>
"VALUES('#{blocker_uuid}'::uuid, '#{blockee_uuid}'::uuid, now()) " <>
"ON CONFLICT (blocker_id, blockee_id) DO NOTHING"
)
else
_ -> Logger.warn("Missing reference: (#{blocker_uuid}, #{blockee_id})")
end
end
end
)
end
def down, do: :noop
end

View File

@ -1,14 +0,0 @@
defmodule Pleroma.Repo.Migrations.CreateUserMutes do
use Ecto.Migration
def change do
create_if_not_exists table(:user_mutes) do
add(:muter_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:mutee_id, references(:users, type: :uuid, on_delete: :delete_all))
timestamps(updated_at: false)
end
create_if_not_exists(unique_index(:user_mutes, [:muter_id, :mutee_id]))
end
end

View File

@ -1,48 +0,0 @@
defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserMutes do
use Ecto.Migration
alias Ecto.Adapters.SQL
alias Pleroma.Repo
require Logger
def up do
{:ok, %{rows: mute_rows}} = SQL.query(Repo, "SELECT id, mutes FROM users WHERE mutes != '{}'")
mutee_ap_ids =
Enum.flat_map(
mute_rows,
fn [_, ap_ids] -> ap_ids end
)
|> Enum.uniq()
# Selecting ids of all mutees at once in order to reduce the number of SELECT queries
{:ok, %{rows: mutee_ap_id_id}} =
SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [mutee_ap_ids])
mutee_id_by_ap_id = Enum.into(mutee_ap_id_id, %{}, fn [k, v] -> {k, v} end)
Enum.each(
mute_rows,
fn [muter_id, mutee_ap_ids] ->
muter_uuid = Ecto.UUID.cast!(muter_id)
for mutee_ap_id <- mutee_ap_ids do
mutee_id = mutee_id_by_ap_id[mutee_ap_id]
with {:ok, mutee_uuid} <- mutee_id && Ecto.UUID.cast(mutee_id) do
execute(
"INSERT INTO user_mutes(muter_id, mutee_id, inserted_at) " <>
"VALUES('#{muter_uuid}'::uuid, '#{mutee_uuid}'::uuid, now()) " <>
"ON CONFLICT (muter_id, mutee_id) DO NOTHING"
)
else
_ -> Logger.warn("Missing reference: (#{muter_uuid}, #{mutee_id})")
end
end
end
)
end
def down, do: :noop
end

View File

@ -0,0 +1,17 @@
defmodule Pleroma.Repo.Migrations.CreateUserRelationships do
use Ecto.Migration
def change do
create_if_not_exists table(:user_relationships) do
add(:source_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:target_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:relationship_type, :integer, null: false)
timestamps(updated_at: false)
end
create_if_not_exists(
unique_index(:user_relationships, [:source_id, :relationship_type, :target_id])
)
end
end

View File

@ -0,0 +1,64 @@
defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
use Ecto.Migration
alias Ecto.Adapters.SQL
alias Pleroma.Repo
require Logger
def up do
Enum.each(
[blocks: 1, mutes: 2, muted_reblogs: 3, muted_notifications: 4],
fn {field, relationship_type_code} ->
migrate(field, relationship_type_code)
end
)
end
def down, do: :noop
defp migrate(field, relationship_type_code) do
Logger.info("Processing users.#{field}...")
{:ok, %{rows: field_rows}} =
SQL.query(Repo, "SELECT id, #{field} FROM users WHERE #{field} != '{}'")
target_ap_ids =
Enum.flat_map(
field_rows,
fn [_, ap_ids] -> ap_ids end
)
|> Enum.uniq()
# Selecting ids of all targets at once in order to reduce the number of SELECT queries
{:ok, %{rows: target_ap_id_id}} =
SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [target_ap_ids])
target_id_by_ap_id = Enum.into(target_ap_id_id, %{}, fn [k, v] -> {k, v} end)
Enum.each(
field_rows,
fn [source_id, target_ap_ids] ->
source_uuid = Ecto.UUID.cast!(source_id)
for target_ap_id <- target_ap_ids do
target_id = target_id_by_ap_id[target_ap_id]
with {:ok, target_uuid} <- target_id && Ecto.UUID.cast(target_id) do
execute("""
INSERT INTO user_relationships(
source_id, target_id, relationship_type, inserted_at
)
VALUES(
'#{source_uuid}'::uuid, '#{target_uuid}'::uuid, #{relationship_type_code}, now()
)
ON CONFLICT (source_id, relationship_type, target_id) DO NOTHING
""")
else
_ -> Logger.warn("Unresolved #{field} reference: (#{source_uuid}, #{target_id})")
end
end
end
)
end
end