@@ -11,3 +11,9 @@ defenum(UserRelationshipTypeEnum, | |||
notification_mute: 4, | |||
inverse_subscription: 5 | |||
) | |||
defenum(FollowingRelationshipStateEnum, | |||
follow_pending: 1, | |||
follow_accept: 2, | |||
follow_reject: 3 | |||
) |
@@ -13,7 +13,7 @@ defmodule Pleroma.FollowingRelationship do | |||
alias Pleroma.User | |||
schema "following_relationships" do | |||
field(:state, :string, default: "accept") | |||
field(:state, FollowingRelationshipStateEnum, default: :follow_pending) | |||
belongs_to(:follower, User, type: CompatType) | |||
belongs_to(:following, User, type: CompatType) | |||
@@ -27,6 +27,19 @@ defmodule Pleroma.FollowingRelationship do | |||
|> put_assoc(:follower, attrs.follower) | |||
|> put_assoc(:following, attrs.following) | |||
|> validate_required([:state, :follower, :following]) | |||
|> unique_constraint(:follower_id, | |||
name: :following_relationships_follower_id_following_id_index | |||
) | |||
|> validate_not_self_relationship() | |||
end | |||
def state_to_enum(state) when is_binary(state) do | |||
case state do | |||
"pending" -> :follow_pending | |||
"accept" -> :follow_accept | |||
"reject" -> :follow_reject | |||
_ -> raise "State is not convertible to FollowingRelationshipStateEnum: #{state}" | |||
end | |||
end | |||
def get(%User{} = follower, %User{} = following) do | |||
@@ -35,7 +48,7 @@ defmodule Pleroma.FollowingRelationship do | |||
|> Repo.one() | |||
end | |||
def update(follower, following, "reject"), do: unfollow(follower, following) | |||
def update(follower, following, :follow_reject), do: unfollow(follower, following) | |||
def update(%User{} = follower, %User{} = following, state) do | |||
case get(follower, following) do | |||
@@ -50,7 +63,7 @@ defmodule Pleroma.FollowingRelationship do | |||
end | |||
end | |||
def follow(%User{} = follower, %User{} = following, state \\ "accept") do | |||
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do | |||
%__MODULE__{} | |||
|> changeset(%{follower: follower, following: following, state: state}) | |||
|> Repo.insert(on_conflict: :nothing) | |||
@@ -80,7 +93,7 @@ defmodule Pleroma.FollowingRelationship do | |||
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.state == ^:follow_pending) | |||
|> where([r], r.following_id == ^id) | |||
|> select([r, f], f) | |||
|> Repo.all() | |||
@@ -88,7 +101,7 @@ defmodule Pleroma.FollowingRelationship do | |||
def following?(%User{id: follower_id}, %User{id: followed_id}) do | |||
__MODULE__ | |||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept") | |||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept) | |||
|> Repo.exists?() | |||
end | |||
@@ -97,7 +110,7 @@ defmodule Pleroma.FollowingRelationship do | |||
__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") | |||
|> where([r], r.state == ^:follow_accept) | |||
|> select([r, u], u.follower_address) | |||
|> Repo.all() | |||
@@ -157,4 +170,22 @@ defmodule Pleroma.FollowingRelationship do | |||
fr -> fr.follower_id == follower.id and fr.following_id == following.id | |||
end) | |||
end | |||
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do | |||
changeset | |||
|> validate_change(:following_id, fn _, following_id -> | |||
if following_id == get_field(changeset, :follower_id) do | |||
[target_id: "can't be equal to follower_id"] | |||
else | |||
[] | |||
end | |||
end) | |||
|> validate_change(:follower_id, fn _, follower_id -> | |||
if follower_id == get_field(changeset, :following_id) do | |||
[source_id: "can't be equal to following_id"] | |||
else | |||
[] | |||
end | |||
end) | |||
end | |||
end |
@@ -697,7 +697,7 @@ defmodule Pleroma.User do | |||
@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} = followed) do | |||
follow(follower, followed, "pending") | |||
follow(follower, followed, :follow_pending) | |||
end | |||
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do | |||
@@ -717,14 +717,14 @@ defmodule Pleroma.User do | |||
def follow_all(follower, followeds) do | |||
followeds | |||
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) | |||
|> Enum.each(&follow(follower, &1, "accept")) | |||
|> Enum.each(&follow(follower, &1, :follow_accept)) | |||
set_cache(follower) | |||
end | |||
defdelegate following(user), to: FollowingRelationship | |||
def follow(%User{} = follower, %User{} = followed, state \\ "accept") do | |||
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do | |||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) | |||
cond do | |||
@@ -751,7 +751,7 @@ defmodule Pleroma.User do | |||
def unfollow(%User{} = follower, %User{} = followed) do | |||
case get_follow_state(follower, followed) do | |||
state when state in ["accept", "pending"] -> | |||
state when state in [:follow_pending, :follow_accept] -> | |||
FollowingRelationship.unfollow(follower, followed) | |||
{:ok, followed} = update_follower_count(followed) | |||
@@ -769,6 +769,7 @@ defmodule Pleroma.User do | |||
defdelegate following?(follower, followed), to: FollowingRelationship | |||
@doc "Returns follow state as FollowingRelationshipStateEnum value" | |||
def get_follow_state(%User{} = follower, %User{} = following) do | |||
following_relationship = FollowingRelationship.get(follower, following) | |||
get_follow_state(follower, following, following_relationship) | |||
@@ -782,8 +783,11 @@ defmodule Pleroma.User do | |||
case {following_relationship, following.local} do | |||
{nil, false} -> | |||
case Utils.fetch_latest_follow(follower, following) do | |||
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state | |||
_ -> nil | |||
%Activity{data: %{"state" => state}} when state in ["pending", "accept"] -> | |||
FollowingRelationship.state_to_enum(state) | |||
_ -> | |||
nil | |||
end | |||
{%{state: state}, _} -> | |||
@@ -1282,7 +1286,7 @@ defmodule Pleroma.User do | |||
def blocks?(%User{} = user, %User{} = target) do | |||
blocks_user?(user, target) || | |||
(!User.following?(user, target) && blocks_domain?(user, target)) | |||
(blocks_domain?(user, target) and not User.following?(user, target)) | |||
end | |||
def blocks_user?(%User{} = user, %User{} = target) do | |||
@@ -148,7 +148,7 @@ defmodule Pleroma.User.Query do | |||
as: :relationships, | |||
on: r.following_id == ^id and r.follower_id == u.id | |||
) | |||
|> where([relationships: r], r.state == "accept") | |||
|> where([relationships: r], r.state == ^:follow_accept) | |||
end | |||
defp compose_query({:friends, %User{id: id}}, query) do | |||
@@ -158,7 +158,7 @@ defmodule Pleroma.User.Query do | |||
as: :relationships, | |||
on: r.following_id == u.id and r.follower_id == ^id | |||
) | |||
|> where([relationships: r], r.state == "accept") | |||
|> where([relationships: r], r.state == ^:follow_accept) | |||
end | |||
defp compose_query({:recipients_from_activity, to}, query) do | |||
@@ -173,7 +173,7 @@ defmodule Pleroma.User.Query do | |||
) | |||
|> where( | |||
[u, following: f, relationships: r], | |||
u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept") | |||
u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept) | |||
) | |||
|> distinct(true) | |||
end | |||
@@ -33,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do | |||
@spec subdomain_match?([Regex.t()], String.t()) :: boolean() | |||
def subdomain_match?(domains, host) do | |||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end) | |||
!!Enum.find(domains, fn domain -> Regex.match?(domain, host) end) | |||
end | |||
@callback describe() :: {:ok | :error, Map.t()} | |||
@@ -490,7 +490,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)}, | |||
{_, {:ok, _}} <- | |||
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")}, | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do | |||
{:ok, _relationship} <- | |||
FollowingRelationship.update(follower, followed, :follow_accept) do | |||
ActivityPub.accept(%{ | |||
to: [follower.ap_id], | |||
actor: followed, | |||
@@ -500,7 +501,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
else | |||
{:user_blocked, true} -> | |||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject") | |||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") | |||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject) | |||
ActivityPub.reject(%{ | |||
to: [follower.ap_id], | |||
@@ -511,7 +512,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
{:follow, {:error, _}} -> | |||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject") | |||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") | |||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject) | |||
ActivityPub.reject(%{ | |||
to: [follower.ap_id], | |||
@@ -521,7 +522,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
}) | |||
{:user_locked, true} -> | |||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending") | |||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending) | |||
:noop | |||
end | |||
@@ -541,7 +542,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed), | |||
{: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"]), | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do | |||
ActivityPub.accept(%{ | |||
to: follow_activity.data["to"], | |||
type: "Accept", | |||
@@ -564,7 +565,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed), | |||
{: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"]), | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), | |||
{:ok, activity} <- | |||
ActivityPub.reject(%{ | |||
to: follow_activity.data["to"], | |||
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.CommonAPI do | |||
with {:ok, follower} <- User.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, _relationship} <- FollowingRelationship.update(follower, followed, "accept"), | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept), | |||
{:ok, _activity} <- | |||
ActivityPub.accept(%{ | |||
to: [follower.ap_id], | |||
@@ -57,7 +57,7 @@ defmodule Pleroma.Web.CommonAPI do | |||
def reject_follow_request(follower, followed) do | |||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), | |||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), | |||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject), | |||
{:ok, _activity} <- | |||
ActivityPub.reject(%{ | |||
to: [follower.ap_id], | |||
@@ -71,7 +71,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||
followed_by = | |||
if following_relationships do | |||
case FollowingRelationship.find(following_relationships, target, reading_user) do | |||
%{state: "accept"} -> true | |||
%{state: :follow_accept} -> true | |||
_ -> false | |||
end | |||
else | |||
@@ -81,7 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags | |||
%{ | |||
id: to_string(target.id), | |||
following: follow_state == "accept", | |||
following: follow_state == :follow_accept, | |||
followed_by: followed_by, | |||
blocking: | |||
UserRelationship.exists?( | |||
@@ -123,7 +123,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||
reading_user, | |||
&User.subscribed_to?(&2, &1) | |||
), | |||
requested: follow_state == "pending", | |||
requested: follow_state == :follow_pending, | |||
domain_blocking: User.blocks_domain?(reading_user, target), | |||
showing_reblogs: | |||
not UserRelationship.exists?( | |||
@@ -0,0 +1,29 @@ | |||
defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do | |||
use Ecto.Migration | |||
@alter_apps_scopes "ALTER TABLE following_relationships ALTER COLUMN state" | |||
def up do | |||
execute(""" | |||
#{@alter_apps_scopes} TYPE integer USING | |||
CASE | |||
WHEN state = 'pending' THEN 1 | |||
WHEN state = 'accept' THEN 2 | |||
WHEN state = 'reject' THEN 3 | |||
ELSE 0 | |||
END; | |||
""") | |||
end | |||
def down do | |||
execute(""" | |||
#{@alter_apps_scopes} TYPE varchar(255) USING | |||
CASE | |||
WHEN state = 1 THEN 'pending' | |||
WHEN state = 2 THEN 'accept' | |||
WHEN state = 3 THEN 'reject' | |||
ELSE '' | |||
END; | |||
""") | |||
end | |||
end |
@@ -0,0 +1,11 @@ | |||
defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do | |||
use Ecto.Migration | |||
# [:follower_index] index is useless because of [:follower_id, :following_id] index | |||
# [:following_id] index makes sense because of user's followers-targeted queries | |||
def change do | |||
drop_if_exists(index(:following_relationships, [:follower_id])) | |||
create_if_not_exists(drop_if_exists(index(:following_relationships, [:following_id]))) | |||
end | |||
end |
@@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do | |||
test "returns following addresses without internal.fetch" do | |||
user = insert(:user) | |||
fetch_actor = InternalFetchActor.get_actor() | |||
FollowingRelationship.follow(fetch_actor, user, "accept") | |||
FollowingRelationship.follow(fetch_actor, user, :follow_accept) | |||
assert FollowingRelationship.following(fetch_actor) == [user.follower_address] | |||
end | |||
test "returns following addresses without relay" do | |||
user = insert(:user) | |||
relay_actor = Relay.get_actor() | |||
FollowingRelationship.follow(relay_actor, user, "accept") | |||
FollowingRelationship.follow(relay_actor, user, :follow_accept) | |||
assert FollowingRelationship.following(relay_actor) == [user.follower_address] | |||
end | |||
test "returns following addresses without remote user" do | |||
user = insert(:user) | |||
actor = insert(:user, local: false) | |||
FollowingRelationship.follow(actor, user, "accept") | |||
FollowingRelationship.follow(actor, user, :follow_accept) | |||
assert FollowingRelationship.following(actor) == [user.follower_address] | |||
end | |||
test "returns following addresses with local user" do | |||
user = insert(:user) | |||
actor = insert(:user, local: true) | |||
FollowingRelationship.follow(actor, user, "accept") | |||
FollowingRelationship.follow(actor, user, :follow_accept) | |||
assert FollowingRelationship.following(actor) == [ | |||
actor.follower_address, | |||
@@ -140,7 +140,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do | |||
test "user is unsubscribed" do | |||
followed = insert(:user) | |||
user = insert(:user) | |||
User.follow(user, followed, "accept") | |||
User.follow(user, followed, :follow_accept) | |||
Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname]) | |||
@@ -194,7 +194,8 @@ defmodule Pleroma.UserTest do | |||
CommonAPI.follow(pending_follower, locked) | |||
CommonAPI.follow(pending_follower, locked) | |||
CommonAPI.follow(accepted_follower, locked) | |||
Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept") | |||
Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept) | |||
assert [^pending_follower] = User.get_follow_requests(locked) | |||
end | |||
@@ -319,7 +320,7 @@ defmodule Pleroma.UserTest do | |||
following_address: "http://localhost:4001/users/fuser2/following" | |||
}) | |||
{:ok, user} = User.follow(user, followed, "accept") | |||
{:ok, user} = User.follow(user, followed, :follow_accept) | |||
{:ok, user, _activity} = User.unfollow(user, followed) | |||
@@ -332,7 +333,7 @@ defmodule Pleroma.UserTest do | |||
followed = insert(:user) | |||
user = insert(:user) | |||
{:ok, user} = User.follow(user, followed, "accept") | |||
{:ok, user} = User.follow(user, followed, :follow_accept) | |||
assert User.following(user) == [user.follower_address, followed.follower_address] | |||
@@ -353,7 +354,7 @@ defmodule Pleroma.UserTest do | |||
test "test if a user is following another user" do | |||
followed = insert(:user) | |||
user = insert(:user) | |||
User.follow(user, followed, "accept") | |||
User.follow(user, followed, :follow_accept) | |||
assert User.following?(user, followed) | |||
refute User.following?(followed, user) | |||
@@ -1622,7 +1622,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do | |||
}) | |||
user_two = insert(:user) | |||
Pleroma.FollowingRelationship.follow(user_two, user, "accept") | |||
Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept) | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) | |||
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) | |||
@@ -562,7 +562,7 @@ defmodule Pleroma.Web.CommonAPITest do | |||
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = | |||
CommonAPI.follow(follower, followed) | |||
assert User.get_follow_state(follower, followed) == "pending" | |||
assert User.get_follow_state(follower, followed) == :follow_pending | |||
assert {:ok, follower} = CommonAPI.unfollow(follower, followed) | |||
assert User.get_follow_state(follower, followed) == nil | |||
@@ -584,7 +584,7 @@ defmodule Pleroma.Web.CommonAPITest do | |||
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = | |||
CommonAPI.follow(follower, followed) | |||
assert User.get_follow_state(follower, followed) == "pending" | |||
assert User.get_follow_state(follower, followed) == :follow_pending | |||
assert {:ok, follower} = CommonAPI.unfollow(follower, followed) | |||
assert User.get_follow_state(follower, followed) == nil | |||
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do | |||
other_user = insert(:user) | |||
{:ok, _activity} = ActivityPub.follow(other_user, user) | |||
{:ok, other_user} = User.follow(other_user, user, "pending") | |||
{:ok, other_user} = User.follow(other_user, user, :follow_pending) | |||
assert User.following?(other_user, user) == false | |||
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do | |||
other_user = insert(:user) | |||
{:ok, _activity} = ActivityPub.follow(other_user, user) | |||
{:ok, other_user} = User.follow(other_user, user, "pending") | |||
{:ok, other_user} = User.follow(other_user, user, :follow_pending) | |||
user = User.get_cached_by_id(user.id) | |||
other_user = User.get_cached_by_id(other_user.id) | |||
@@ -197,7 +197,7 @@ defmodule Pleroma.Web.StreamerTest do | |||
Pleroma.Config.put([:instance, :skip_thread_containment], false) | |||
author = insert(:user) | |||
user = insert(:user) | |||
User.follow(user, author, "accept") | |||
User.follow(user, author, :follow_accept) | |||
activity = | |||
insert(:note_activity, | |||
@@ -220,7 +220,7 @@ defmodule Pleroma.Web.StreamerTest do | |||
Pleroma.Config.put([:instance, :skip_thread_containment], true) | |||
author = insert(:user) | |||
user = insert(:user) | |||
User.follow(user, author, "accept") | |||
User.follow(user, author, :follow_accept) | |||
activity = | |||
insert(:note_activity, | |||
@@ -243,7 +243,7 @@ defmodule Pleroma.Web.StreamerTest do | |||
Pleroma.Config.put([:instance, :skip_thread_containment], false) | |||
author = insert(:user) | |||
user = insert(:user, skip_thread_containment: true) | |||
User.follow(user, author, "accept") | |||
User.follow(user, author, :follow_accept) | |||
activity = | |||
insert(:note_activity, | |||