mirror of
https://framagit.org/framasoft/mobilizon.git
synced 2024-12-30 11:55:26 +00:00
Add support for GraphQL handling of group follows
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
cf9ba47b69
commit
44e8ac7e9a
7 changed files with 318 additions and 3 deletions
|
@ -6,7 +6,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
import Mobilizon.Users.Guards
|
import Mobilizon.Users.Guards
|
||||||
alias Mobilizon.Config
|
alias Mobilizon.Config
|
||||||
alias Mobilizon.{Actors, Events}
|
alias Mobilizon.{Actors, Events}
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||||
alias Mobilizon.Federation.ActivityPub.Actions
|
alias Mobilizon.Federation.ActivityPub.Actions
|
||||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||||
alias Mobilizon.GraphQL.API
|
alias Mobilizon.GraphQL.API
|
||||||
|
@ -320,6 +320,78 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||||
{:error, dgettext("errors", "You need to be logged-in to leave a group")}
|
{:error, dgettext("errors", "You need to be logged-in to leave a group")}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Follow a group
|
||||||
|
"""
|
||||||
|
@spec follow_group(any(), map(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Follower.t()} | {:error, String.t()}
|
||||||
|
def follow_group(_parent, %{group_id: group_id, notify: _notify}, %{
|
||||||
|
context: %{current_actor: %Actor{} = actor}
|
||||||
|
}) do
|
||||||
|
case Actors.get_actor(group_id) do
|
||||||
|
%Actor{type: :Group} = group ->
|
||||||
|
with {:ok, _activity, %Follower{} = follower} <- Actions.Follow.follow(actor, group) do
|
||||||
|
{:ok, follower}
|
||||||
|
end
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
{:error, dgettext("errors", "Group not found")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_group(_parent, _args, _resolution) do
|
||||||
|
{:error, dgettext("errors", "You need to be logged-in to follow a group")}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Update a group follow
|
||||||
|
"""
|
||||||
|
@spec update_group_follow(any(), map(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Member.t()} | {:error, String.t()}
|
||||||
|
def update_group_follow(_parent, %{follow_id: follow_id, notify: notify}, %{
|
||||||
|
context: %{current_actor: %Actor{} = actor}
|
||||||
|
}) do
|
||||||
|
case Actors.get_follower(follow_id) do
|
||||||
|
%Follower{} = follower ->
|
||||||
|
if follower.actor_id == actor.id do
|
||||||
|
# Update notify
|
||||||
|
Actors.update_follower(follower, %{notify: notify})
|
||||||
|
else
|
||||||
|
{:error, dgettext("errors", "Follow does not match your account")}
|
||||||
|
end
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
{:error, dgettext("errors", "Follow not found")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_group_follow(_parent, _args, _resolution) do
|
||||||
|
{:error, dgettext("errors", "You need to be logged-in to update a group follow")}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Unfollow a group
|
||||||
|
"""
|
||||||
|
@spec unfollow_group(any(), map(), Absinthe.Resolution.t()) ::
|
||||||
|
{:ok, Follower.t()} | {:error, String.t()}
|
||||||
|
def unfollow_group(_parent, %{group_id: group_id}, %{
|
||||||
|
context: %{current_actor: %Actor{} = actor}
|
||||||
|
}) do
|
||||||
|
case Actors.get_actor(group_id) do
|
||||||
|
%Actor{type: :Group} = group ->
|
||||||
|
with {:ok, _activity, %Follower{} = follower} <- Actions.Follow.unfollow(actor, group) do
|
||||||
|
{:ok, follower}
|
||||||
|
end
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
{:error, dgettext("errors", "Group not found")}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow_group(_parent, _args, _resolution) do
|
||||||
|
{:error, dgettext("errors", "You need to be logged-in to unfollow a group")}
|
||||||
|
end
|
||||||
|
|
||||||
@spec find_events_for_group(Actor.t(), map(), Absinthe.Resolution.t()) ::
|
@spec find_events_for_group(Actor.t(), map(), Absinthe.Resolution.t()) ::
|
||||||
{:ok, Page.t(Event.t())}
|
{:ok, Page.t(Event.t())}
|
||||||
def find_events_for_group(
|
def find_events_for_group(
|
||||||
|
|
|
@ -17,6 +17,11 @@ defmodule Mobilizon.GraphQL.Schema.Actors.FollowerType do
|
||||||
description: "Whether the follow has been approved by the target actor"
|
description: "Whether the follow has been approved by the target actor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
field(:notify, :boolean,
|
||||||
|
description:
|
||||||
|
"Whether the follower will be notified by the target actor's activity or not (applicable for profile/group follows)"
|
||||||
|
)
|
||||||
|
|
||||||
field(:inserted_at, :datetime, description: "When the follow was created")
|
field(:inserted_at, :datetime, description: "When the follow was created")
|
||||||
field(:updated_at, :datetime, description: "When the follow was updated")
|
field(:updated_at, :datetime, description: "When the follow was updated")
|
||||||
end
|
end
|
||||||
|
|
|
@ -205,6 +205,12 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
||||||
value(:private, description: "Visible only to people with the link - or invited")
|
value(:private, description: "Visible only to people with the link - or invited")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
object :group_follow do
|
||||||
|
field(:group, :group, description: "The group followed")
|
||||||
|
field(:profile, :group, description: "The group followed")
|
||||||
|
field(:notify, :boolean, description: "Whether to notify profile from group activity")
|
||||||
|
end
|
||||||
|
|
||||||
object :group_queries do
|
object :group_queries do
|
||||||
@desc "Get all groups"
|
@desc "Get all groups"
|
||||||
field :groups, :paginated_group_list do
|
field :groups, :paginated_group_list do
|
||||||
|
@ -310,5 +316,36 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
||||||
|
|
||||||
resolve(&Group.delete_group/3)
|
resolve(&Group.delete_group/3)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@desc "Follow a group"
|
||||||
|
field :follow_group, :follower do
|
||||||
|
arg(:group_id, non_null(:id), description: "The group ID")
|
||||||
|
|
||||||
|
arg(:notify, :boolean,
|
||||||
|
description: "Whether to notify profile from group activity",
|
||||||
|
default_value: true
|
||||||
|
)
|
||||||
|
|
||||||
|
resolve(&Group.follow_group/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Update a group follow"
|
||||||
|
field :update_group_follow, :follower do
|
||||||
|
arg(:follow_id, non_null(:id), description: "The follow ID")
|
||||||
|
|
||||||
|
arg(:notify, :boolean,
|
||||||
|
description: "Whether to notify profile from group activity",
|
||||||
|
default_value: true
|
||||||
|
)
|
||||||
|
|
||||||
|
resolve(&Group.update_group_follow/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Unfollow a group"
|
||||||
|
field :unfollow_group, :follower do
|
||||||
|
arg(:group_id, non_null(:id), description: "The group ID")
|
||||||
|
|
||||||
|
resolve(&Group.unfollow_group/3)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,12 +17,14 @@ defmodule Mobilizon.Actors.Follower do
|
||||||
url: String.t(),
|
url: String.t(),
|
||||||
target_actor: Actor.t(),
|
target_actor: Actor.t(),
|
||||||
actor: Actor.t(),
|
actor: Actor.t(),
|
||||||
|
notify: boolean(),
|
||||||
inserted_at: DateTime.t(),
|
inserted_at: DateTime.t(),
|
||||||
updated_at: DateTime.t()
|
updated_at: DateTime.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
@required_attrs [:url, :approved, :target_actor_id, :actor_id]
|
@required_attrs [:url, :approved, :target_actor_id, :actor_id]
|
||||||
@attrs @required_attrs
|
@optional_attrs [:notify]
|
||||||
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
@timestamps_opts [type: :utc_datetime]
|
@timestamps_opts [type: :utc_datetime]
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@ defmodule Mobilizon.Actors.Follower do
|
||||||
schema "followers" do
|
schema "followers" do
|
||||||
field(:approved, :boolean, default: false)
|
field(:approved, :boolean, default: false)
|
||||||
field(:url, :string)
|
field(:url, :string)
|
||||||
|
field(:notify, :boolean, default: true)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.AddNotifyToFollowers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:followers) do
|
||||||
|
add(:notify, :boolean, default: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -178,7 +178,7 @@ defmodule Mobilizon.Web.Resolvers.FollowerTest do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
describe "update a follower update_follower/3" do
|
describe "approve a follower update_follower/3" do
|
||||||
test "without being logged-in", %{
|
test "without being logged-in", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
group: %Actor{} = group
|
group: %Actor{} = group
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Mobilizon.Web.Resolvers.GroupTest do
|
||||||
|
|
||||||
import Mobilizon.Factory
|
import Mobilizon.Factory
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.{Actor, Follower}
|
||||||
alias Mobilizon.GraphQL.AbsintheHelpers
|
alias Mobilizon.GraphQL.AbsintheHelpers
|
||||||
|
|
||||||
@non_existent_username "nonexistent"
|
@non_existent_username "nonexistent"
|
||||||
|
@ -468,4 +469,192 @@ defmodule Mobilizon.Web.Resolvers.GroupTest do
|
||||||
assert hd(res["errors"])["message"] =~ "not an administrator"
|
assert hd(res["errors"])["message"] =~ "not an administrator"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "follow a group" do
|
||||||
|
@follow_group_mutation """
|
||||||
|
mutation FollowGroup($groupId: ID!, $notify: Boolean) {
|
||||||
|
followGroup(groupId: $groupId, notify: $notify) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
test "when not authenticated", %{conn: conn, user: _user} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @follow_group_mutation,
|
||||||
|
variables: %{groupId: group.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "You need to be logged-in to follow a group"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when group doesn't exist", %{conn: conn, user: user} do
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @follow_group_mutation,
|
||||||
|
variables: %{groupId: "89542"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "Group not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "success", %{conn: conn, user: user} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @follow_group_mutation,
|
||||||
|
variables: %{groupId: group.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res["errors"] == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "unfollow a group" do
|
||||||
|
@unfollow_group_mutation """
|
||||||
|
mutation UnfollowGroup($groupId: ID!) {
|
||||||
|
unfollowGroup(groupId: $groupId) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
test "when not authenticated", %{conn: conn, user: _user} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @unfollow_group_mutation,
|
||||||
|
variables: %{groupId: group.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "You need to be logged-in to unfollow a group"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when group doesn't exist", %{conn: conn, user: user} do
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @unfollow_group_mutation,
|
||||||
|
variables: %{groupId: "89542"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "Group not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when the profile is not following the group", %{conn: conn, user: user} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @unfollow_group_mutation,
|
||||||
|
variables: %{groupId: group.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] =~ "Could not unfollow actor: you are not following"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "success", %{conn: conn, user: user, actor: actor} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
|
||||||
|
Mobilizon.Actors.follow(group, actor)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @unfollow_group_mutation,
|
||||||
|
variables: %{groupId: group.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res["errors"] == nil
|
||||||
|
assert Mobilizon.Actors.get_follower_by_followed_and_following(group, actor) == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update a group follow" do
|
||||||
|
@update_group_follow_mutation """
|
||||||
|
mutation UpdateGroupFollow($followId: ID!, $notify: Boolean) {
|
||||||
|
updateGroupFollow(followId: $followId, notify: $notify) {
|
||||||
|
id
|
||||||
|
notify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
test "when not authenticated", %{conn: conn, user: _user, actor: actor} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
follow = insert(:follower, target_actor: group, actor: actor)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_group_follow_mutation,
|
||||||
|
variables: %{followId: follow.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "You need to be logged-in to update a group follow"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when follow doesn't exist", %{conn: conn, user: user} do
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_group_follow_mutation,
|
||||||
|
variables: %{followId: "d7c83493-e4a0-42a2-a15d-a469e955e80a"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "Follow not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when follow does not match the current actor", %{conn: conn, user: user} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
follow = insert(:follower, target_actor: group)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_group_follow_mutation,
|
||||||
|
variables: %{followId: follow.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "Follow does not match your account"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "success", %{conn: conn, user: user, actor: actor} do
|
||||||
|
%Actor{type: :Group} = group = insert(:group)
|
||||||
|
follow = insert(:follower, target_actor: group, actor: actor)
|
||||||
|
|
||||||
|
assert %Follower{notify: true} =
|
||||||
|
Mobilizon.Actors.get_follower_by_followed_and_following(group, actor)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_group_follow_mutation,
|
||||||
|
variables: %{followId: follow.id, notify: false}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res["errors"] == nil
|
||||||
|
assert res["data"]["updateGroupFollow"]["notify"] == false
|
||||||
|
|
||||||
|
assert %Follower{notify: false} =
|
||||||
|
Mobilizon.Actors.get_follower_by_followed_and_following(group, actor)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue