forked from potsda.mn/mobilizon
Merge branch 'update-member' into 'master'
Update member See merge request framasoft/mobilizon!549
This commit is contained in:
commit
a59e273722
|
@ -81,6 +81,15 @@ export const GROUP_MEMBERS = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_MEMBER = gql`
|
||||||
|
mutation UpdateMember($memberId: ID!, $role: MemberRoleEnum!) {
|
||||||
|
updateMember(memberId: $memberId, role: $role) {
|
||||||
|
id
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const REMOVE_MEMBER = gql`
|
export const REMOVE_MEMBER = gql`
|
||||||
mutation RemoveMember($groupId: ID!, $memberId: ID!) {
|
mutation RemoveMember($groupId: ID!, $memberId: ID!) {
|
||||||
removeMember(groupId: $groupId, memberId: $memberId) {
|
removeMember(groupId: $groupId, memberId: $memberId) {
|
||||||
|
|
|
@ -764,5 +764,7 @@
|
||||||
"Update": "Update",
|
"Update": "Update",
|
||||||
"Search…": "Search…",
|
"Search…": "Search…",
|
||||||
"Edited {ago}": "Edited {ago}",
|
"Edited {ago}": "Edited {ago}",
|
||||||
"[This comment has been deleted by it's author]": "[This comment has been deleted by it's author]"
|
"[This comment has been deleted by it's author]": "[This comment has been deleted by it's author]",
|
||||||
|
"Promote": "Promote",
|
||||||
|
"Demote": "Demote"
|
||||||
}
|
}
|
||||||
|
|
|
@ -765,5 +765,7 @@
|
||||||
"Update": "Éditer",
|
"Update": "Éditer",
|
||||||
"Search…": "Rechercher…",
|
"Search…": "Rechercher…",
|
||||||
"Edited {ago}": "Édité {ago}",
|
"Edited {ago}": "Édité {ago}",
|
||||||
"[This comment has been deleted by it's author]": "[Ce commentaire a été supprimé par son auteur]"
|
"[This comment has been deleted by it's author]": "[Ce commentaire a été supprimé par son auteur]",
|
||||||
|
"Promote": "Promouvoir",
|
||||||
|
"Demote": "Rétrograder"
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,12 +134,24 @@
|
||||||
</span>
|
</span>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
<b-table-column field="actions" :label="$t('Actions')" v-slot="props">
|
<b-table-column field="actions" :label="$t('Actions')" v-slot="props">
|
||||||
<b-button
|
<div class="buttons">
|
||||||
v-if="props.row.role === MemberRole.MEMBER"
|
<b-button
|
||||||
@click="removeMember(props.row.id)"
|
v-if="props.row.role === MemberRole.MEMBER"
|
||||||
type="is-danger"
|
@click="promoteMember(props.row.id)"
|
||||||
>{{ $t("Remove") }}</b-button
|
>{{ $t("Promote") }}</b-button
|
||||||
>
|
>
|
||||||
|
<b-button
|
||||||
|
v-if="props.row.role === MemberRole.ADMINISTRATOR"
|
||||||
|
@click="demoteMember(props.row.id)"
|
||||||
|
>{{ $t("Demote") }}</b-button
|
||||||
|
>
|
||||||
|
<b-button
|
||||||
|
v-if="props.row.role === MemberRole.MEMBER"
|
||||||
|
@click="removeMember(props.row.id)"
|
||||||
|
type="is-danger"
|
||||||
|
>{{ $t("Remove") }}</b-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
<template slot="empty">
|
<template slot="empty">
|
||||||
<section class="section">
|
<section class="section">
|
||||||
|
@ -156,7 +168,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue, Watch } from "vue-property-decorator";
|
import { Component, Vue, Watch } from "vue-property-decorator";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import { INVITE_MEMBER, GROUP_MEMBERS, REMOVE_MEMBER } from "../../graphql/member";
|
import { INVITE_MEMBER, GROUP_MEMBERS, REMOVE_MEMBER, UPDATE_MEMBER } from "../../graphql/member";
|
||||||
import { IGroup, usernameWithDomain } from "../../types/actor";
|
import { IGroup, usernameWithDomain } from "../../types/actor";
|
||||||
import { IMember, MemberRole } from "../../types/actor/group.model";
|
import { IMember, MemberRole } from "../../types/actor/group.model";
|
||||||
|
|
||||||
|
@ -297,5 +309,23 @@ export default class GroupMembers extends Vue {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
promoteMember(memberId: string) {
|
||||||
|
return this.updateMember(memberId, MemberRole.ADMINISTRATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
demoteMember(memberId: string) {
|
||||||
|
return this.updateMember(memberId, MemberRole.MEMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateMember(memberId: string, role: MemberRole) {
|
||||||
|
await this.$apollo.mutate<{ updateMember: IMember }>({
|
||||||
|
mutation: UPDATE_MEMBER,
|
||||||
|
variables: {
|
||||||
|
memberId,
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -87,6 +87,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||||
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
|
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
|
||||||
{:existing, nil} <-
|
{:existing, nil} <-
|
||||||
{:existing, Actors.get_actor_by_url_2(url)},
|
{:existing, Actors.get_actor_by_url_2(url)},
|
||||||
|
{:existing, nil} <- {:existing, Actors.get_member_by_url(url)},
|
||||||
:ok <- Logger.info("Data for URL not found anywhere, going to fetch it"),
|
:ok <- Logger.info("Data for URL not found anywhere, going to fetch it"),
|
||||||
{:ok, _activity, entity} <- Fetcher.fetch_and_create(url, options) do
|
{:ok, _activity, entity} <- Fetcher.fetch_and_create(url, options) do
|
||||||
Logger.debug("Going to preload the new entity")
|
Logger.debug("Going to preload the new entity")
|
||||||
|
@ -359,16 +360,12 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||||
end
|
end
|
||||||
|
|
||||||
def join_group(
|
def join_group(
|
||||||
%{parent_id: parent_id, actor_id: actor_id, role: role},
|
%{parent_id: _parent_id, actor_id: _actor_id, role: _role} = args,
|
||||||
local \\ true,
|
local \\ true,
|
||||||
additional \\ %{}
|
additional \\ %{}
|
||||||
) do
|
) do
|
||||||
with {:ok, %Member{} = member} <-
|
with {:ok, %Member{} = member} <-
|
||||||
Mobilizon.Actors.create_member(%{
|
Mobilizon.Actors.create_member(args),
|
||||||
parent_id: parent_id,
|
|
||||||
actor_id: actor_id,
|
|
||||||
role: role
|
|
||||||
}),
|
|
||||||
activity_data when is_map(activity_data) <-
|
activity_data when is_map(activity_data) <-
|
||||||
Convertible.model_to_as(member),
|
Convertible.model_to_as(member),
|
||||||
{:ok, activity} <- create_activity(Map.merge(activity_data, additional), local),
|
{:ok, activity} <- create_activity(Map.merge(activity_data, additional), local),
|
||||||
|
|
|
@ -33,8 +33,6 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
||||||
@spec fetch_and_create(String.t(), Keyword.t()) :: {:ok, map(), struct()}
|
@spec fetch_and_create(String.t(), Keyword.t()) :: {:ok, map(), struct()}
|
||||||
def fetch_and_create(url, options \\ []) do
|
def fetch_and_create(url, options \\ []) do
|
||||||
with {:ok, data} when is_map(data) <- fetch(url, options),
|
with {:ok, data} when is_map(data) <- fetch(url, options),
|
||||||
:ok <- Logger.debug("inspect body from fetch_object_from_url #{url}"),
|
|
||||||
:ok <- Logger.debug(inspect(data)),
|
|
||||||
{:origin_check, true} <- {:origin_check, origin_check?(url, data)},
|
{:origin_check, true} <- {:origin_check, origin_check?(url, data)},
|
||||||
params <- %{
|
params <- %{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
|
@ -55,8 +53,6 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
||||||
@spec fetch_and_update(String.t(), Keyword.t()) :: {:ok, map(), struct()}
|
@spec fetch_and_update(String.t(), Keyword.t()) :: {:ok, map(), struct()}
|
||||||
def fetch_and_update(url, options \\ []) do
|
def fetch_and_update(url, options \\ []) do
|
||||||
with {:ok, data} when is_map(data) <- fetch(url, options),
|
with {:ok, data} when is_map(data) <- fetch(url, options),
|
||||||
:ok <- Logger.debug("inspect body from fetch_object_from_url #{url}"),
|
|
||||||
:ok <- Logger.debug(inspect(data)),
|
|
||||||
{:origin_check, true} <- {:origin_check, origin_check?(url, data)},
|
{:origin_check, true} <- {:origin_check, origin_check?(url, data)},
|
||||||
params <- %{
|
params <- %{
|
||||||
"type" => "Update",
|
"type" => "Update",
|
||||||
|
|
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
|
||||||
|
|
||||||
# TODO: Move me in a more appropriate place
|
# TODO: Move me in a more appropriate place
|
||||||
alias Mobilizon.{Actors, Discussions, Events, Resources}
|
alias Mobilizon.{Actors, Discussions, Events, Resources}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Resources.Resource
|
alias Mobilizon.Resources.Resource
|
||||||
|
@ -25,6 +25,8 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
|
||||||
|
|
||||||
def maybe_preload(%Actor{url: url}), do: {:ok, Actors.get_actor_by_url!(url, true)}
|
def maybe_preload(%Actor{url: url}), do: {:ok, Actors.get_actor_by_url!(url, true)}
|
||||||
|
|
||||||
|
def maybe_preload(%Member{} = member), do: {:ok, member}
|
||||||
|
|
||||||
def maybe_preload(%Tombstone{uri: _uri} = tombstone), do: {:ok, tombstone}
|
def maybe_preload(%Tombstone{uri: _uri} = tombstone), do: {:ok, tombstone}
|
||||||
|
|
||||||
def maybe_preload(other), do: {:error, other}
|
def maybe_preload(other), do: {:error, other}
|
||||||
|
|
|
@ -415,6 +415,27 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{"type" => "Update", "object" => %{"type" => "Member"} = object, "actor" => _actor} =
|
||||||
|
update_data
|
||||||
|
) do
|
||||||
|
Logger.info("Handle incoming to update a member")
|
||||||
|
|
||||||
|
with actor <- Utils.get_actor(update_data),
|
||||||
|
{:ok, %Actor{url: actor_url, suspended: false} = actor} <-
|
||||||
|
ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||||
|
{:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)},
|
||||||
|
object_data <- Converter.Member.as_to_model_data(object),
|
||||||
|
{:ok, old_entity} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
|
||||||
|
{:ok, %Activity{} = activity, new_entity} <-
|
||||||
|
ActivityPub.update(old_entity, object_data, false, %{moderator: actor}) do
|
||||||
|
{:ok, activity, new_entity}
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_incoming(%{
|
def handle_incoming(%{
|
||||||
"type" => "Update",
|
"type" => "Update",
|
||||||
"object" => %{"type" => "Tombstone"} = object,
|
"object" => %{"type" => "Tombstone"} = object,
|
||||||
|
|
|
@ -5,6 +5,7 @@ alias Mobilizon.Federation.ActivityPub.Types.{
|
||||||
Entity,
|
Entity,
|
||||||
Events,
|
Events,
|
||||||
Managable,
|
Managable,
|
||||||
|
Members,
|
||||||
Ownable,
|
Ownable,
|
||||||
Posts,
|
Posts,
|
||||||
Resources,
|
Resources,
|
||||||
|
@ -13,7 +14,7 @@ alias Mobilizon.Federation.ActivityPub.Types.{
|
||||||
Tombstones
|
Tombstones
|
||||||
}
|
}
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Posts.Post
|
alias Mobilizon.Posts.Post
|
||||||
|
@ -149,3 +150,8 @@ defimpl Ownable, for: Tombstone do
|
||||||
defdelegate group_actor(entity), to: Tombstones
|
defdelegate group_actor(entity), to: Tombstones
|
||||||
defdelegate actor(entity), to: Tombstones
|
defdelegate actor(entity), to: Tombstones
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defimpl Managable, for: Member do
|
||||||
|
defdelegate update(entity, attrs, additionnal), to: Members
|
||||||
|
defdelegate delete(entity, actor, local), to: Members
|
||||||
|
end
|
||||||
|
|
54
lib/federation/activity_pub/types/members.ex
Normal file
54
lib/federation/activity_pub/types/members.ex
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
defmodule Mobilizon.Federation.ActivityPub.Types.Members do
|
||||||
|
@moduledoc false
|
||||||
|
alias Mobilizon.Actors
|
||||||
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
|
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||||
|
require Logger
|
||||||
|
import Mobilizon.Federation.ActivityPub.Utils, only: [make_update_data: 2]
|
||||||
|
|
||||||
|
def update(
|
||||||
|
%Member{parent: %Actor{id: group_id}, id: member_id, role: current_role} = member,
|
||||||
|
%{role: updated_role} = args,
|
||||||
|
%{moderator: %Actor{url: moderator_url, id: moderator_id}} = additional
|
||||||
|
) do
|
||||||
|
with additional <- Map.delete(additional, :moderator),
|
||||||
|
{:has_rights_to_update_role, {:ok, %Member{role: moderator_role}}}
|
||||||
|
when moderator_role in [:moderator, :administrator, :creator] <-
|
||||||
|
{:has_rights_to_update_role, Actors.get_member(moderator_id, group_id)},
|
||||||
|
{:is_only_admin, false} <-
|
||||||
|
{:is_only_admin, check_admins_left(member_id, group_id, current_role, updated_role)},
|
||||||
|
{:ok, %Member{} = member} <-
|
||||||
|
Actors.update_member(member, args),
|
||||||
|
{:ok, true} <- Cachex.del(:activity_pub, "member_#{member_id}"),
|
||||||
|
member_as_data <-
|
||||||
|
Convertible.model_to_as(member),
|
||||||
|
audience <- %{
|
||||||
|
"to" => [member.parent.members_url, member.actor.url],
|
||||||
|
"cc" => [member.parent.url],
|
||||||
|
"actor" => moderator_url,
|
||||||
|
"attributedTo" => [member.parent.url]
|
||||||
|
} do
|
||||||
|
update_data = make_update_data(member_as_data, Map.merge(audience, additional))
|
||||||
|
|
||||||
|
{:ok, member, update_data}
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.debug(inspect(err))
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Delete member is not used, see ActivityPub.leave/4 and ActivityPub.remove/5 instead
|
||||||
|
def delete(_, _, _), do: :error
|
||||||
|
|
||||||
|
def actor(%Member{actor_id: actor_id}),
|
||||||
|
do: Actors.get_actor(actor_id)
|
||||||
|
|
||||||
|
def group_actor(%Member{parent_id: parent_id}),
|
||||||
|
do: Actors.get_actor(parent_id)
|
||||||
|
|
||||||
|
defp check_admins_left(member_id, group_id, current_role, updated_role) do
|
||||||
|
Actors.is_only_administrator?(member_id, group_id) && current_role == :administrator &&
|
||||||
|
updated_role != :administrator
|
||||||
|
end
|
||||||
|
end
|
|
@ -29,7 +29,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Member do
|
||||||
"id" => member.url,
|
"id" => member.url,
|
||||||
"actor" => member.actor.url,
|
"actor" => member.actor.url,
|
||||||
"object" => member.parent.url,
|
"object" => member.parent.url,
|
||||||
"role" => member.role
|
"role" => to_string(member.role)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -121,12 +121,36 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_member(_parent, %{member_id: member_id, group_id: group_id}, %{
|
def update_member(_parent, %{member_id: member_id, role: role}, %{
|
||||||
context: %{current_user: %User{} = user}
|
context: %{current_user: %User{} = user}
|
||||||
}) do
|
}) do
|
||||||
with %Actor{} = moderator <- Users.get_actor_for_user(user),
|
with %Actor{} = moderator <- Users.get_actor_for_user(user),
|
||||||
|
%Member{} = member <- Actors.get_member(member_id),
|
||||||
|
{:ok, _activity, %Member{} = member} <-
|
||||||
|
ActivityPub.update(member, %{role: role}, true, %{moderator: moderator}) do
|
||||||
|
{:ok, member}
|
||||||
|
else
|
||||||
|
{:has_rights_to_update_role, {:error, :member_not_found}} ->
|
||||||
|
{:error, "You are not a moderator or admin for this group"}
|
||||||
|
|
||||||
|
{:is_only_admin, true} ->
|
||||||
|
{:error,
|
||||||
|
"You can't set yourself to a lower member role for this group because you are the only administrator"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_member(_parent, _args, _resolution),
|
||||||
|
do: {:error, "You must be logged-in to update a member"}
|
||||||
|
|
||||||
|
def remove_member(_parent, %{member_id: member_id, group_id: group_id}, %{
|
||||||
|
context: %{current_user: %User{} = user}
|
||||||
|
}) do
|
||||||
|
with %Actor{id: moderator_id} = moderator <- Users.get_actor_for_user(user),
|
||||||
%Member{} = member <- Actors.get_member(member_id),
|
%Member{} = member <- Actors.get_member(member_id),
|
||||||
%Actor{type: :Group} = group <- Actors.get_actor(group_id),
|
%Actor{type: :Group} = group <- Actors.get_actor(group_id),
|
||||||
|
{:has_rights_to_invite, {:ok, %Member{role: role}}}
|
||||||
|
when role in [:moderator, :administrator, :creator] <-
|
||||||
|
{:has_rights_to_invite, Actors.get_member(moderator_id, group_id)},
|
||||||
{:ok, _activity, %Member{}} <- ActivityPub.remove(member, group, moderator, true) do
|
{:ok, _activity, %Member{}} <- ActivityPub.remove(member, group, moderator, true) do
|
||||||
{:ok, member}
|
{:ok, member}
|
||||||
end
|
end
|
||||||
|
|
|
@ -72,6 +72,13 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
|
||||||
resolve(&Member.reject_invitation/3)
|
resolve(&Member.reject_invitation/3)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
field :update_member, :member do
|
||||||
|
arg(:member_id, non_null(:id))
|
||||||
|
arg(:role, non_null(:member_role_enum))
|
||||||
|
|
||||||
|
resolve(&Member.update_member/3)
|
||||||
|
end
|
||||||
|
|
||||||
@desc "Remove a member from a group"
|
@desc "Remove a member from a group"
|
||||||
field :remove_member, :member do
|
field :remove_member, :member do
|
||||||
arg(:group_id, non_null(:id))
|
arg(:group_id, non_null(:id))
|
||||||
|
|
19
lib/web/cache/activity_pub.ex
vendored
19
lib/web/cache/activity_pub.ex
vendored
|
@ -4,7 +4,7 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources, Todos, Tombstone}
|
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources, Todos, Tombstone}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Federation.ActivityPub.Relay
|
alias Mobilizon.Federation.ActivityPub.Relay
|
||||||
|
@ -174,6 +174,23 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a member by its UUID, with all associations loaded.
|
||||||
|
"""
|
||||||
|
@spec get_member_by_uuid_with_preload(String.t()) ::
|
||||||
|
{:commit, Todo.t()} | {:ignore, nil}
|
||||||
|
def get_member_by_uuid_with_preload(uuid) do
|
||||||
|
Cachex.fetch(@cache, "member_" <> uuid, fn "member_" <> uuid ->
|
||||||
|
case Actors.get_member(uuid) do
|
||||||
|
%Member{} = member ->
|
||||||
|
{:commit, member}
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
{:ignore, nil}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a relay.
|
Gets a relay.
|
||||||
"""
|
"""
|
||||||
|
|
1
lib/web/cache/cache.ex
vendored
1
lib/web/cache/cache.ex
vendored
|
@ -24,6 +24,7 @@ defmodule Mobilizon.Web.Cache do
|
||||||
defdelegate get_resource_by_uuid_with_preload(uuid), to: ActivityPub
|
defdelegate get_resource_by_uuid_with_preload(uuid), to: ActivityPub
|
||||||
defdelegate get_todo_list_by_uuid_with_preload(uuid), to: ActivityPub
|
defdelegate get_todo_list_by_uuid_with_preload(uuid), to: ActivityPub
|
||||||
defdelegate get_todo_by_uuid_with_preload(uuid), to: ActivityPub
|
defdelegate get_todo_by_uuid_with_preload(uuid), to: ActivityPub
|
||||||
|
defdelegate get_member_by_uuid_with_preload(uuid), to: ActivityPub
|
||||||
defdelegate get_post_by_slug_with_preload(slug), to: ActivityPub
|
defdelegate get_post_by_slug_with_preload(slug), to: ActivityPub
|
||||||
defdelegate get_discussion_by_slug_with_preload(slug), to: ActivityPub
|
defdelegate get_discussion_by_slug_with_preload(slug), to: ActivityPub
|
||||||
defdelegate get_relay, to: ActivityPub
|
defdelegate get_relay, to: ActivityPub
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||||
use Mobilizon.Web, :controller
|
use Mobilizon.Web, :controller
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Config}
|
alias Mobilizon.{Actors, Config}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Federation.ActivityPub.Federator
|
alias Mobilizon.Federation.ActivityPub.Federator
|
||||||
|
@ -66,6 +66,41 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||||
actor_collection(conn, "discussions", args)
|
actor_collection(conn, "discussions", args)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ok_statuses [:ok, :commit]
|
||||||
|
@spec member(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||||
|
def member(conn, %{"uuid" => uuid}) do
|
||||||
|
with {status, %Member{parent: %Actor{} = group, actor: %Actor{domain: nil} = _actor} = member}
|
||||||
|
when status in @ok_statuses <-
|
||||||
|
Cache.get_member_by_uuid_with_preload(uuid),
|
||||||
|
actor <- Map.get(conn.assigns, :actor),
|
||||||
|
true <- actor_applicant_group_member?(group, actor) do
|
||||||
|
json(
|
||||||
|
conn,
|
||||||
|
ActorView.render("member.json", %{
|
||||||
|
member: member,
|
||||||
|
actor_applicant: actor
|
||||||
|
})
|
||||||
|
)
|
||||||
|
else
|
||||||
|
{status, %Member{actor: %Actor{url: domain}, parent: %Actor{} = group, url: url}}
|
||||||
|
when status in @ok_statuses and not is_nil(domain) ->
|
||||||
|
with actor <- Map.get(conn.assigns, :actor),
|
||||||
|
true <- actor_applicant_group_member?(group, actor) do
|
||||||
|
redirect(conn, external: url)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json("Not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json("Not found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def outbox(conn, args) do
|
def outbox(conn, args) do
|
||||||
actor_collection(conn, "outbox", args)
|
actor_collection(conn, "outbox", args)
|
||||||
end
|
end
|
||||||
|
@ -153,4 +188,15 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp actor_applicant_group_member?(%Actor{}, nil), do: false
|
||||||
|
|
||||||
|
defp actor_applicant_group_member?(%Actor{id: group_id}, %Actor{id: actor_applicant_id}),
|
||||||
|
do:
|
||||||
|
Actors.get_member(actor_applicant_id, group_id, [
|
||||||
|
:member,
|
||||||
|
:moderator,
|
||||||
|
:administrator,
|
||||||
|
:creator
|
||||||
|
]) != {:error, :member_not_found}
|
||||||
end
|
end
|
||||||
|
|
|
@ -105,6 +105,7 @@ defmodule Mobilizon.Web.Router do
|
||||||
get("/@:name/following", ActivityPubController, :following)
|
get("/@:name/following", ActivityPubController, :following)
|
||||||
get("/@:name/followers", ActivityPubController, :followers)
|
get("/@:name/followers", ActivityPubController, :followers)
|
||||||
get("/@:name/members", ActivityPubController, :members)
|
get("/@:name/members", ActivityPubController, :members)
|
||||||
|
get("/member/:uuid", ActivityPubController, :member)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", Mobilizon.Web do
|
scope "/", Mobilizon.Web do
|
||||||
|
|
|
@ -23,6 +23,12 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("member.json", %{member: %Member{} = member}) do
|
||||||
|
member
|
||||||
|
|> Convertible.model_to_as()
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Render an actor collection
|
Render an actor collection
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -447,4 +447,153 @@ defmodule Mobilizon.GraphQL.Resolvers.MemberTest do
|
||||||
assert hd(res["errors"])["message"] == "You cannot invite to this group"
|
assert hd(res["errors"])["message"] == "You cannot invite to this group"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Member resolver to update a group member" do
|
||||||
|
@update_member_mutation """
|
||||||
|
mutation UpdateMember($memberId: ID!, $role: MemberRoleEnum!) {
|
||||||
|
updateMember(memberId: $memberId, role: $role) {
|
||||||
|
id
|
||||||
|
role
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
setup %{conn: conn, actor: actor, user: user} do
|
||||||
|
group = insert(:group)
|
||||||
|
target_actor = insert(:actor, user: user)
|
||||||
|
|
||||||
|
{:ok, conn: conn, actor: actor, user: user, group: group, target_actor: target_actor}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_member/3 fails when not connected", %{
|
||||||
|
conn: conn,
|
||||||
|
group: group,
|
||||||
|
target_actor: target_actor
|
||||||
|
} do
|
||||||
|
%Member{id: member_id} =
|
||||||
|
insert(:member, %{actor: target_actor, parent: group, role: :member})
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_member_mutation,
|
||||||
|
variables: %{
|
||||||
|
memberId: member_id,
|
||||||
|
role: "MODERATOR"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "You must be logged-in to update a member"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_member/3 fails when not a member of the group", %{
|
||||||
|
conn: conn,
|
||||||
|
group: group,
|
||||||
|
target_actor: target_actor
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
actor = insert(:actor, user: user)
|
||||||
|
Mobilizon.Users.update_user_default_actor(user.id, actor.id)
|
||||||
|
|
||||||
|
%Member{id: member_id} =
|
||||||
|
insert(:member, %{actor: target_actor, parent: group, role: :member})
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_member_mutation,
|
||||||
|
variables: %{
|
||||||
|
memberId: member_id,
|
||||||
|
role: "MODERATOR"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] == "You are not a moderator or admin for this group"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_member/3 updates the member role", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
actor: actor,
|
||||||
|
group: group,
|
||||||
|
target_actor: target_actor
|
||||||
|
} do
|
||||||
|
Mobilizon.Users.update_user_default_actor(user.id, actor.id)
|
||||||
|
insert(:member, actor: actor, parent: group, role: :administrator)
|
||||||
|
|
||||||
|
%Member{id: member_id} =
|
||||||
|
insert(:member, %{actor: target_actor, parent: group, role: :member})
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_member_mutation,
|
||||||
|
variables: %{
|
||||||
|
memberId: member_id,
|
||||||
|
role: "MODERATOR"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert is_nil(res["errors"])
|
||||||
|
assert res["data"]["updateMember"]["role"] == "MODERATOR"
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_member_mutation,
|
||||||
|
variables: %{
|
||||||
|
memberId: member_id,
|
||||||
|
role: "ADMINISTRATOR"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert is_nil(res["errors"])
|
||||||
|
assert res["data"]["updateMember"]["role"] == "ADMINISTRATOR"
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_member_mutation,
|
||||||
|
variables: %{
|
||||||
|
memberId: member_id,
|
||||||
|
role: "MEMBER"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert is_nil(res["errors"])
|
||||||
|
assert res["data"]["updateMember"]["role"] == "MEMBER"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_member/3 prevents to downgrade the member role if there's no admin left", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
actor: actor,
|
||||||
|
group: group
|
||||||
|
} do
|
||||||
|
Mobilizon.Users.update_user_default_actor(user.id, actor.id)
|
||||||
|
%Member{id: member_id} = insert(:member, actor: actor, parent: group, role: :administrator)
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user)
|
||||||
|
|> AbsintheHelpers.graphql_query(
|
||||||
|
query: @update_member_mutation,
|
||||||
|
variables: %{
|
||||||
|
memberId: member_id,
|
||||||
|
role: "MEMBER"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hd(res["errors"])["message"] ==
|
||||||
|
"You can't set yourself to a lower member role for this group because you are the only administrator"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Member resolver to remove a member from a group" do
|
||||||
|
# TODO write tests for me plz
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -438,4 +438,60 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
|
||||||
assert result["totalItems"] == 2
|
assert result["totalItems"] == 2
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "/member/:uuid" do
|
||||||
|
test "it returns a json representation of the member", %{conn: conn} do
|
||||||
|
group = insert(:group)
|
||||||
|
remote_actor_2 = insert(:actor, domain: "remote3.tld")
|
||||||
|
insert(:member, actor: remote_actor_2, parent: group, role: :member)
|
||||||
|
|
||||||
|
member =
|
||||||
|
insert(:member,
|
||||||
|
parent: group,
|
||||||
|
url: "https://someremote.url/member/here"
|
||||||
|
)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:actor, remote_actor_2)
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get(Routes.activity_pub_url(Endpoint, :member, member.id))
|
||||||
|
|
||||||
|
assert json_response(conn, 200) ==
|
||||||
|
ActorView.render("member.json", %{member: member})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it redirects for remote comments", %{conn: conn} do
|
||||||
|
group = insert(:group, domain: "remote1.tld")
|
||||||
|
remote_actor = insert(:actor, domain: "remote2.tld")
|
||||||
|
remote_actor_2 = insert(:actor, domain: "remote3.tld")
|
||||||
|
insert(:member, actor: remote_actor_2, parent: group, role: :member)
|
||||||
|
|
||||||
|
member =
|
||||||
|
insert(:member,
|
||||||
|
actor: remote_actor,
|
||||||
|
parent: group,
|
||||||
|
url: "https://someremote.url/member/here"
|
||||||
|
)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:actor, remote_actor_2)
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get(Routes.activity_pub_url(Endpoint, :member, member.id))
|
||||||
|
|
||||||
|
assert redirected_to(conn) == "https://someremote.url/member/here"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns 404 if the fetch is not authenticated", %{conn: conn} do
|
||||||
|
member = insert(:member)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get(Routes.activity_pub_url(Endpoint, :member, member.id))
|
||||||
|
|
||||||
|
assert json_response(conn, 404)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue