Remove duplicated entries in users' following lists

This commit is contained in:
Sergey Suprunenko 2019-05-16 20:04:08 +00:00 committed by feld
parent 0f8892686a
commit 2c303afc8b
6 changed files with 110 additions and 3 deletions

View file

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support for Mastodon's remote interaction - Support for Mastodon's remote interaction
- Mix Tasks: `mix pleroma.database bump_all_conversations` - Mix Tasks: `mix pleroma.database bump_all_conversations`
- Mix Tasks: `mix pleroma.database remove_embedded_objects` - Mix Tasks: `mix pleroma.database remove_embedded_objects`
- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
- Mix Tasks: `mix pleroma.user toggle_confirmed` - Mix Tasks: `mix pleroma.user toggle_confirmed`
- Federation: Support for reports - Federation: Support for reports
- Configuration: `safe_dm_mentions` option - Configuration: `safe_dm_mentions` option

View file

@ -5,6 +5,8 @@
defmodule Mix.Tasks.Pleroma.Database do defmodule Mix.Tasks.Pleroma.Database do
alias Mix.Tasks.Pleroma.Common alias Mix.Tasks.Pleroma.Common
alias Pleroma.Conversation alias Pleroma.Conversation
alias Pleroma.Repo
alias Pleroma.User
require Logger require Logger
use Mix.Task use Mix.Task
@ -25,6 +27,9 @@ defmodule Mix.Tasks.Pleroma.Database do
mix pleroma.database bump_all_conversations mix pleroma.database bump_all_conversations
## Remove duplicated items from following and update followers count for all users
mix pleroma.database update_users_following_followers_counts
""" """
def run(["remove_embedded_objects" | args]) do def run(["remove_embedded_objects" | args]) do
{options, [], []} = {options, [], []} =
@ -38,7 +43,7 @@ def run(["remove_embedded_objects" | args]) do
Common.start_pleroma() Common.start_pleroma()
Logger.info("Removing embedded objects") Logger.info("Removing embedded objects")
Pleroma.Repo.query!( Repo.query!(
"update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;", "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",
[], [],
timeout: :infinity timeout: :infinity
@ -47,7 +52,7 @@ def run(["remove_embedded_objects" | args]) do
if Keyword.get(options, :vacuum) do if Keyword.get(options, :vacuum) do
Logger.info("Runnning VACUUM FULL") Logger.info("Runnning VACUUM FULL")
Pleroma.Repo.query!( Repo.query!(
"vacuum full;", "vacuum full;",
[], [],
timeout: :infinity timeout: :infinity
@ -59,4 +64,12 @@ def run(["bump_all_conversations"]) do
Common.start_pleroma() Common.start_pleroma()
Conversation.bump_for_all_activities() Conversation.bump_for_all_activities()
end end
def run(["update_users_following_followers_counts"]) do
Common.start_pleroma()
users = Repo.all(User)
Enum.each(users, &User.remove_duplicated_following/1)
Enum.each(users, &User.update_follower_count/1)
end
end end

View file

@ -166,7 +166,7 @@ def remote_user_creation(params) do
def update_changeset(struct, params \\ %{}) do def update_changeset(struct, params \\ %{}) do
struct struct
|> cast(params, [:bio, :name, :avatar]) |> cast(params, [:bio, :name, :avatar, :following])
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000) |> validate_length(:bio, max: 5000)
@ -709,6 +709,18 @@ def update_follower_count(%User{} = user) do
end end
end end
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
criteria = %{ap_id: ap_ids, deactivated: false} criteria = %{ap_id: ap_ids, deactivated: false}

View file

@ -0,0 +1,49 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.DatabaseTest do
alias Pleroma.Repo
alias Pleroma.User
use Pleroma.DataCase
import Pleroma.Factory
setup_all do
Mix.shell(Mix.Shell.Process)
on_exit(fn ->
Mix.shell(Mix.Shell.IO)
end)
:ok
end
describe "running update_users_following_followers_counts" do
test "following and followers count are updated" do
[user, user2] = insert_pair(:user)
{:ok, %User{following: following, info: info} = user} = User.follow(user, user2)
assert length(following) == 2
assert info.follower_count == 0
info_cng = Ecto.Changeset.change(info, %{follower_count: 3})
{:ok, user} =
user
|> Ecto.Changeset.change(%{following: following ++ following})
|> Ecto.Changeset.put_embed(:info, info_cng)
|> Repo.update()
assert length(user.following) == 4
assert user.info.follower_count == 3
assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
user = User.get_by_id(user.id)
assert length(user.following) == 2
assert user.info.follower_count == 0
end
end
end

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.UserTest do defmodule Mix.Tasks.Pleroma.UserTest do
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
use Pleroma.DataCase use Pleroma.DataCase

View file

@ -626,6 +626,37 @@ test "it sets the info->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)