diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue index 972a55dfa..efbc62f84 100644 --- a/js/src/components/NavBar.vue +++ b/js/src/components/NavBar.vue @@ -40,6 +40,10 @@ My account + + Create group + + Log out @@ -70,6 +74,7 @@ import { IConfig } from '@/types/config.model'; import { ICurrentUser } from '@/types/current-user.model'; import Logo from '@/components/Logo.vue'; import SearchField from '@/components/SearchField.vue'; +import { ActorRouteName } from '@/router/actor'; @Component({ apollo: { @@ -95,6 +100,8 @@ export default class NavBar extends Vue { currentUser!: ICurrentUser; showNavbar: boolean = false; + ActorRouteName = ActorRouteName; + @Watch('currentUser') async onCurrentUserChanged() { // Refresh logged person object diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts index 61543f916..7dc92ca20 100644 --- a/js/src/graphql/actor.ts +++ b/js/src/graphql/actor.ts @@ -174,3 +174,34 @@ query($name:String!) { } } `; + +export const CREATE_GROUP = gql` + mutation CreateGroup( + $creatorActorId: Int!, + $preferredUsername: String!, + $name: String!, + $summary: String, + $avatar: PictureInput, + $banner: PictureInput + ) { + createGroup( + creatorActorId: $creatorActorId, + preferredUsername: $preferredUsername, + name: $name, + summary: $summary, + banner: $banner, + avatar: $avatar + ) { + id, + preferredUsername, + name, + summary, + avatar { + url + }, + banner { + url + } + } + } +`; diff --git a/js/src/types/actor/group.model.ts b/js/src/types/actor/group.model.ts index d7dcd0566..c93c4c638 100644 --- a/js/src/types/actor/group.model.ts +++ b/js/src/types/actor/group.model.ts @@ -19,4 +19,14 @@ export interface IMember { export class Group extends Actor implements IGroup { members: IMember[] = []; + + constructor(hash: IGroup | {} = {}) { + super(hash); + + this.patch(hash); + } + + patch (hash: any) { + Object.assign(this, hash); + } } diff --git a/js/src/views/Group/Create.vue b/js/src/views/Group/Create.vue index 8440f91ae..6d7727168 100644 --- a/js/src/views/Group/Create.vue +++ b/js/src/views/Group/Create.vue @@ -1,87 +1,124 @@ + + diff --git a/js/src/vue-apollo.ts b/js/src/vue-apollo.ts index d39a67361..91e513fea 100644 --- a/js/src/vue-apollo.ts +++ b/js/src/vue-apollo.ts @@ -107,7 +107,7 @@ const apolloClient = new ApolloClient({ cache, link, connectToDevTools: true, - resolvers: buildCurrentUserResolver(cache) + resolvers: buildCurrentUserResolver(cache), }); export const apolloProvider = new VueApollo({ diff --git a/lib/mobilizon_web/api/groups.ex b/lib/mobilizon_web/api/groups.ex index 8c6e8f64b..5033792b3 100644 --- a/lib/mobilizon_web/api/groups.ex +++ b/lib/mobilizon_web/api/groups.ex @@ -3,7 +3,7 @@ defmodule MobilizonWeb.API.Groups do API for Events """ alias Mobilizon.Actors - alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils alias MobilizonWeb.API.Utils @@ -11,24 +11,26 @@ defmodule MobilizonWeb.API.Groups do @doc """ Create a group """ - @spec create_group(map()) :: {:ok, Activity.t(), Group.t()} | any() + @spec create_group(User.t(), map()) :: {:ok, Activity.t(), Group.t()} | any() def create_group( + user, %{ preferred_username: title, - description: description, - admin_actor_username: admin_actor_username + summary: summary, + creator_actor_id: creator_actor_id, + avatar: avatar, + banner: banner } = args ) do - with {:bad_actor, %Actor{url: url} = actor} <- - {:bad_actor, Actors.get_local_actor_by_name(admin_actor_username)}, + with {:is_owned, true, actor} <- User.owns_actor(user, creator_actor_id), {:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)}, title <- String.trim(title), visibility <- Map.get(args, :visibility, :public), {content_html, tags, to, cc} <- - Utils.prepare_content(actor, description, visibility, [], nil), + Utils.prepare_content(actor, summary, visibility, [], nil), group <- ActivityPubUtils.make_group_data( - url, + actor.url, to, title, content_html, @@ -43,10 +45,10 @@ defmodule MobilizonWeb.API.Groups do }) else {:existing_group, _} -> - {:error, :existing_group_name} + {:error, "A group with this name already exists"} - {:bad_actor, _} -> - {:error, :bad_admin_actor} + {:is_owned, _} -> + {:error, "Actor id is not owned by authenticated user"} end end end diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex index 96b4214eb..902d77640 100644 --- a/lib/mobilizon_web/resolvers/group.ex +++ b/lib/mobilizon_web/resolvers/group.ex @@ -27,8 +27,11 @@ defmodule MobilizonWeb.Resolvers.Group do Lists all groups """ def list_groups(_parent, %{page: page, limit: limit}, _resolution) do - {:ok, - Actors.list_groups(page, limit) |> Enum.map(fn actor -> Person.proxify_pictures(actor) end)} + { + :ok, + Actors.list_groups(page, limit) + |> Enum.map(fn actor -> Person.proxify_pictures(actor) end) + } end @doc """ @@ -39,7 +42,7 @@ defmodule MobilizonWeb.Resolvers.Group do args, %{ context: %{ - current_user: _user + current_user: user } } ) do @@ -52,26 +55,22 @@ defmodule MobilizonWeb.Resolvers.Group do }, %Actor{} = group } <- - MobilizonWeb.API.Groups.create_group(args) do + MobilizonWeb.API.Groups.create_group( + user, + %{ + preferred_username: args.preferred_username, + creator_actor_id: args.creator_actor_id, + name: Map.get(args, "name", args.preferred_username), + summary: args.summary, + avatar: Map.get(args, "avatar"), + banner: Map.get(args, "banner") + } + ) do { :ok, group } end - - # with %Actor{id: actor_id} <- Actors.get_local_actor_by_name(actor_username), - # {:user_actor, true} <- - # {:user_actor, actor_id in Enum.map(Actors.get_actors_for_user(user), & &1.id)}, - # {:ok, %Actor{} = group} <- Actors.create_group(%{preferred_username: preferred_username}) do - # {:ok, group} - # else - # {:error, %Ecto.Changeset{errors: [url: {"has already been taken", []}]}} -> - # {:error, :group_name_not_available} - - # err -> - # Logger.error(inspect(err)) - # err - # end end def create_group(_parent, _args, _resolution) do @@ -138,12 +137,18 @@ defmodule MobilizonWeb.Resolvers.Group do actor_id: actor.id, role: role }) do - {:ok, - %{ - parent: group |> Person.proxify_pictures(), - actor: actor |> Person.proxify_pictures(), - role: role - }} + { + :ok, + %{ + parent: + group + |> Person.proxify_pictures(), + actor: + actor + |> Person.proxify_pictures(), + role: role + } + } else {:is_owned, false} -> {:error, "Actor id is not owned by authenticated user"} diff --git a/lib/mobilizon_web/schema/actors/group.ex b/lib/mobilizon_web/schema/actors/group.ex index c3c65b407..30226039d 100644 --- a/lib/mobilizon_web/schema/actors/group.ex +++ b/lib/mobilizon_web/schema/actors/group.ex @@ -95,13 +95,14 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do @desc "Create a group" field :create_group, :group do arg(:preferred_username, non_null(:string), description: "The name for the group") - arg(:name, :string, description: "The displayed name for the group") - arg(:description, :string, description: "The summary for the group", default_value: "") - arg(:admin_actor_username, :string, - description: "The actor's username which will be the admin (otherwise user's default one)" + arg(:creator_actor_id, non_null(:integer), + description: "The identity that creates the group" ) + arg(:name, :string, description: "The displayed name for the group") + arg(:summary, :string, description: "The summary for the group", default_value: "") + arg(:avatar, :picture_input, description: "The avatar for the group, either as an object or directly the ID of an existing Picture" diff --git a/lib/mobilizon_web/schema/event.ex b/lib/mobilizon_web/schema/event.ex index 96f8bea30..25a6ffcac 100644 --- a/lib/mobilizon_web/schema/event.ex +++ b/lib/mobilizon_web/schema/event.ex @@ -72,7 +72,7 @@ defmodule MobilizonWeb.Schema.EventType do @desc "The list of visibility options for an event" enum :event_visibility do - value(:public, description: "Publically listed and federated. Can be shared.") + value(:public, description: "Publicly listed and federated. Can be shared.") value(:unlisted, description: "Visible only to people with the link - or invited") value(:private, diff --git a/test/mobilizon_web/resolvers/group_resolver_test.exs b/test/mobilizon_web/resolvers/group_resolver_test.exs index 00334af92..7373b2f14 100644 --- a/test/mobilizon_web/resolvers/group_resolver_test.exs +++ b/test/mobilizon_web/resolvers/group_resolver_test.exs @@ -15,12 +15,37 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do end describe "Group Resolver" do - test "create_group/3 creates a group", %{conn: conn, user: user, actor: actor} do + test "create_group/3 should check the user owns the identity", %{conn: conn, user: user} do + another_actor = insert(:actor) + mutation = """ mutation { createGroup( preferred_username: "#{@new_group_params.groupname}", - admin_actor_username: "#{actor.preferred_username}" + creator_actor_id: #{another_actor.id} + ) { + preferred_username, + type + } + } + """ + + res = + conn + |> auth_conn(user) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] == + "Actor id is not owned by authenticated user" + end + + test "create_group/3 creates a group and check a group with this name does not already exist", + %{conn: conn, user: user, actor: actor} do + mutation = """ + mutation { + createGroup( + preferred_username: "#{@new_group_params.groupname}", + creator_actor_id: #{actor.id} ) { preferred_username, type @@ -42,7 +67,7 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do mutation { createGroup( preferred_username: "#{@new_group_params.groupname}", - admin_actor_username: "#{actor.preferred_username}", + creator_actor_id: #{actor.id}, ) { preferred_username, type @@ -55,7 +80,8 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do |> auth_conn(user) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) - assert hd(json_response(res, 200)["errors"])["message"] == "existing_group_name" + assert hd(json_response(res, 200)["errors"])["message"] == + "A group with this name already exists" end test "list_groups/3 returns all public or unlisted groups", context do diff --git a/test/mobilizon_web/resolvers/participant_resolver_test.exs b/test/mobilizon_web/resolvers/participant_resolver_test.exs index 80ad79cad..c2a3bbb48 100644 --- a/test/mobilizon_web/resolvers/participant_resolver_test.exs +++ b/test/mobilizon_web/resolvers/participant_resolver_test.exs @@ -7,7 +7,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do @event %{ description: "some body", title: "some title", - begins_on: DateTime.utc_now() |> DateTime.truncate(:second), + begins_on: + DateTime.utc_now() + |> DateTime.truncate(:second), uuid: "b5126423-f1af-43e4-a923-002a03003ba4", url: "some url", category: "meeting" @@ -171,7 +173,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do assert json_response(res, 200)["data"]["participants"] == [ %{ - "actor" => %{"preferredUsername" => participant2.actor.preferred_username}, + "actor" => %{ + "preferredUsername" => participant2.actor.preferred_username + }, "role" => "creator" } ] @@ -339,7 +343,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do assert json_response(res, 200)["data"]["participants"] == [ %{ - "actor" => %{"preferredUsername" => context.actor.preferred_username}, + "actor" => %{ + "preferredUsername" => context.actor.preferred_username + }, "role" => "creator" } ] @@ -356,14 +362,26 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do context.conn |> get("/api", AbsintheHelpers.query_skeleton(query, "participants")) - assert json_response(res, 200)["data"]["participants"] == [ + sorted_participants = + json_response(res, 200)["data"]["participants"] + |> Enum.sort_by( + &(&1 + |> Map.get("actor") + |> Map.get("preferredUsername")) + ) + + assert sorted_participants == [ %{ - "actor" => %{"preferredUsername" => participant2.actor.preferred_username}, - "role" => "participant" + "actor" => %{ + "preferredUsername" => context.actor.preferred_username + }, + "role" => "creator" }, %{ - "actor" => %{"preferredUsername" => context.actor.preferred_username}, - "role" => "creator" + "actor" => %{ + "preferredUsername" => participant2.actor.preferred_username + }, + "role" => "participant" } ] end