From 8388240957475c664776e543f4b177d35ea29aa1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 25 Jan 2019 09:23:44 +0100 Subject: [PATCH] Add ability to delete a group --- lib/mobilizon/actors/actors.ex | 17 ++++++++ lib/mobilizon/actors/member.ex | 19 +++++++++ lib/mobilizon/actors/user.ex | 12 ++++++ lib/mobilizon_web/resolvers/group.ex | 39 ++++++++++++++++++- lib/mobilizon_web/schema.ex | 13 +++++++ ...125143718_group_delete_members_cascade.exs | 13 +++++++ .../resolvers/group_resolver_test.exs | 31 +++++++++++++++ test/support/factory.ex | 3 +- 8 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 priv/repo/migrations/20190125143718_group_delete_members_cascade.exs diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index 576d799ec..cb08885c8 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -242,6 +242,13 @@ defmodule Mobilizon.Actors do |> Repo.insert() end + @doc """ + Delete a group + """ + def delete_group!(%Actor{type: :Group} = group) do + Repo.delete!(group) + end + alias Mobilizon.Actors.User @doc """ @@ -551,6 +558,16 @@ defmodule Mobilizon.Actors do end end + @doc """ + Find a group by its actor id + """ + def find_group_by_actor_id(actor_id) do + case Repo.get_by(Actor, id: actor_id, type: :Group) do + nil -> {:error, :group_not_found} + actor -> {:ok, actor} + end + end + @doc """ Authenticate user """ diff --git a/lib/mobilizon/actors/member.ex b/lib/mobilizon/actors/member.ex index 8770df02c..6534a9688 100644 --- a/lib/mobilizon/actors/member.ex +++ b/lib/mobilizon/actors/member.ex @@ -6,6 +6,7 @@ defmodule Mobilizon.Actors.Member do import Ecto.Changeset alias Mobilizon.Actors.Member alias Mobilizon.Actors.Actor + alias Mobilizon.Repo schema "members" do field(:approved, :boolean, default: true) @@ -24,4 +25,22 @@ defmodule Mobilizon.Actors.Member do |> validate_required([:parent_id, :actor_id]) |> unique_constraint(:parent_id, name: :members_actor_parent_unique_index) end + + @doc """ + Gets a single member of an actor (for example a group) + """ + def get_member(actor_id, parent_id) do + case Repo.get_by(Member, actor_id: actor_id, parent_id: parent_id) do + nil -> {:error, :member_not_found} + member -> {:ok, member} + end + end + + def is_administrator(%Member{role: 2} = member) do + {:is_admin, true} + end + + def is_administrator(%Member{}) do + {:is_admin, false} + end end diff --git a/lib/mobilizon/actors/user.ex b/lib/mobilizon/actors/user.ex index 5ba90441a..aede05314 100644 --- a/lib/mobilizon/actors/user.ex +++ b/lib/mobilizon/actors/user.ex @@ -144,4 +144,16 @@ defmodule Mobilizon.Actors.User do def is_confirmed(%User{} = user) do {:ok, user} end + + def owns_actor(%User{default_actor_id: default_actor_id} = user, %Actor{id: actor_id}) + when default_actor_id == actor_id do + {:is_owned, true} + end + + def owns_actor(%User{actors: actors} = user, actor_id) do + case Enum.any?(actors, fn a -> a.id == actor_id end) do + true -> {:is_owned, true} + _ -> {:is_owned, false} + end + end end diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex index fc73fd5a0..d5f9ea31e 100644 --- a/lib/mobilizon_web/resolvers/group.ex +++ b/lib/mobilizon_web/resolvers/group.ex @@ -3,7 +3,7 @@ defmodule MobilizonWeb.Resolvers.Group do Handles the group-related GraphQL calls """ alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor} + alias Mobilizon.Actors.{Actor, User, Member} alias Mobilizon.Service.ActivityPub alias Mobilizon.Activity require Logger @@ -68,4 +68,41 @@ defmodule MobilizonWeb.Resolvers.Group do def create_group(_parent, _args, _resolution) do {:error, "You need to be logged-in to create a group"} end + + @doc """ + Delete an existing group + """ + def delete_group( + _parent, + %{group_id: group_id, actor_id: actor_id}, + %{ + context: %{ + current_user: user + } + } + ) do + with {:ok, %Actor{} = group} <- Actors.find_group_by_actor_id(group_id), + {:is_owned, true} <- User.owns_actor(user, actor_id), + {:ok, %Member{} = member} <- Member.get_member(actor_id, group.id), + {:is_admin, true} <- Member.is_administrator(member), + group <- Actors.delete_group!(group) do + {:ok, %{id: group.id}} + else + {:error, :group_not_found} -> + {:error, "Group with preferred username not found"} + + {:is_owned, false} -> + {:error, "Actor id is not owned by authenticated user"} + + {:error, :member_not_found} -> + {:error, "Actor id is not a member of this group"} + + {:is_admin, false} -> + {:error, "User is not an administrator of the selected group"} + end + end + + def delete_group(_parent, _args, _resolution) do + {:error, "You need to be logged-in to delete a group"} + end end diff --git a/lib/mobilizon_web/schema.ex b/lib/mobilizon_web/schema.ex index 654871355..ff8f094a3 100644 --- a/lib/mobilizon_web/schema.ex +++ b/lib/mobilizon_web/schema.ex @@ -20,6 +20,11 @@ defmodule MobilizonWeb.Schema do alias MobilizonWeb.Resolvers + @desc "A struct containing the id of the deleted object" + object :deleted_object do + field(:id, :integer) + end + @desc "A JWT and the associated user ID" object :login do field(:token, non_null(:string), description: "A JWT Token for this session") @@ -301,6 +306,14 @@ defmodule MobilizonWeb.Schema do resolve(&Resolvers.Group.create_group/3) end + @desc "Delete a group" + field :delete_group, :deleted_object do + arg(:group_id, non_null(:integer)) + arg(:actor_id, non_null(:integer)) + + resolve(&Resolvers.Group.delete_group/3) + end + # @desc "Upload a picture" # field :upload_picture, :picture do # arg(:file, non_null(:upload)) diff --git a/priv/repo/migrations/20190125143718_group_delete_members_cascade.exs b/priv/repo/migrations/20190125143718_group_delete_members_cascade.exs new file mode 100644 index 000000000..ca8a6bc32 --- /dev/null +++ b/priv/repo/migrations/20190125143718_group_delete_members_cascade.exs @@ -0,0 +1,13 @@ +defmodule Mobilizon.Repo.Migrations.GroupDeleteMembersCascade do + use Ecto.Migration + + def change do + drop constraint(:members, "members_account_id_fkey") + drop constraint(:members, "members_parent_id_fkey") + + alter table(:members) do + modify :actor_id, references(:actors, on_delete: :delete_all) + modify :parent_id, references(:actors, on_delete: :delete_all) + end + end +end diff --git a/test/mobilizon_web/resolvers/group_resolver_test.exs b/test/mobilizon_web/resolvers/group_resolver_test.exs index 4522a4afd..64f0cde8f 100644 --- a/test/mobilizon_web/resolvers/group_resolver_test.exs +++ b/test/mobilizon_web/resolvers/group_resolver_test.exs @@ -114,5 +114,36 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do assert hd(json_response(res, 200)["errors"])["message"] == "Group with name #{@non_existent_username} not found" end + + test "delete_group/3 deletes a group", %{conn: conn, user: user, actor: actor} do + group = insert(:group) + insert(:member, parent: group, actor: actor, role: 2) + + mutation = """ + mutation { + deleteGroup( + actor_id: #{actor.id}, + group_id: #{group.id} + ) { + id + } + } + """ + + res = + conn + |> auth_conn(user) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["errors"] == nil + assert json_response(res, 200)["data"]["deleteGroup"]["id"] == group.id + + res = + conn + |> auth_conn(user) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] =~ "not found" + end end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 0fb645bf1..06b3a7e8f 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -148,7 +148,8 @@ defmodule Mobilizon.Factory do def member_factory do %Mobilizon.Actors.Member{ parent: build(:actor), - actor: build(:actor) + actor: build(:actor), + role: 0 } end end