Get membership status only for the current group

Closes #575

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-03-05 11:23:17 +01:00
parent 74923c91d8
commit 03824b898c
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
11 changed files with 161 additions and 67 deletions

View File

@ -446,6 +446,70 @@ export const PERSON_MEMBERSHIPS_WITH_MEMBERS = gql`
} }
`; `;
export const PERSON_MEMBERSHIP_GROUP = gql`
query PersonMembershipGroup($id: ID!, $group: String!) {
person(id: $id) {
id
memberships(group: $group) {
total
elements {
id
role
parent {
id
preferredUsername
name
domain
avatar {
id
url
}
}
invitedBy {
id
preferredUsername
name
}
insertedAt
updatedAt
}
}
}
}
`;
export const GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED = gql`
subscription($actorId: ID!, $group: String!) {
groupMembershipChanged(personId: $actorId, group: $group) {
id
memberships {
total
elements {
id
role
parent {
id
preferredUsername
name
domain
avatar {
id
url
}
}
invitedBy {
id
preferredUsername
name
}
insertedAt
updatedAt
}
}
}
}
`;
export const CREATE_PERSON = gql` export const CREATE_PERSON = gql`
mutation CreatePerson( mutation CreatePerson(
$preferredUsername: String! $preferredUsername: String!

View File

@ -592,38 +592,6 @@ export const EVENT_PERSON_PARTICIPATION_SUBSCRIPTION_CHANGED = gql`
} }
`; `;
export const GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED = gql`
subscription($actorId: ID!) {
groupMembershipChanged(personId: $actorId) {
id
memberships {
total
elements {
id
role
parent {
id
preferredUsername
name
domain
avatar {
id
url
}
}
invitedBy {
id
preferredUsername
name
}
insertedAt
updatedAt
}
}
}
}
`;
export const FETCH_GROUP_EVENTS = gql` export const FETCH_GROUP_EVENTS = gql`
query( query(
$name: String! $name: String!

View File

@ -1,5 +1,8 @@
import { PERSON_MEMBERSHIPS, CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; import {
import { GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED } from "@/graphql/event"; CURRENT_ACTOR_CLIENT,
GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED,
PERSON_MEMBERSHIP_GROUP,
} from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group"; import { FETCH_GROUP } from "@/graphql/group";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import { Group, IActor, IGroup, IPerson } from "@/types/actor"; import { Group, IActor, IGroup, IPerson } from "@/types/actor";
@ -26,11 +29,12 @@ import { Component, Vue } from "vue-property-decorator";
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIPS, query: PERSON_MEMBERSHIP_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {
id: this.currentActor.id, id: this.currentActor.id,
group: this.$route.params.preferredUsername,
}; };
}, },
subscribeToMore: { subscribeToMore: {
@ -38,14 +42,23 @@ import { Component, Vue } from "vue-property-decorator";
variables() { variables() {
return { return {
actorId: this.currentActor.id, actorId: this.currentActor.id,
group: this.$route.params.preferredUsername,
}; };
}, },
skip() { skip() {
return !this.currentActor || !this.currentActor.id; return (
!this.currentActor ||
!this.currentActor.id ||
!this.$route.params.preferredUsername
);
}, },
}, },
skip() { skip() {
return !this.currentActor || !this.currentActor.id; return (
!this.currentActor ||
!this.currentActor.id ||
!this.$route.params.preferredUsername
);
}, },
}, },
currentActor: CURRENT_ACTOR_CLIENT, currentActor: CURRENT_ACTOR_CLIENT,
@ -71,13 +84,7 @@ export default class GroupMixin extends Vue {
hasCurrentActorThisRole(givenRole: string | string[]): boolean { hasCurrentActorThisRole(givenRole: string | string[]): boolean {
const roles = Array.isArray(givenRole) ? givenRole : [givenRole]; const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
return ( return roles.includes(this.person?.memberships?.elements[0].role);
this.person &&
this.person.memberships.elements.some(
({ parent: { id }, role }) =>
id === this.group.id && roles.includes(role)
)
);
} }
handleErrors(errors: any[]): void { handleErrors(errors: any[]): void {

View File

@ -75,8 +75,11 @@ import { IActor, IGroup, IPerson, usernameWithDomain } from "@/types/actor";
import DiscussionListItem from "@/components/Discussion/DiscussionListItem.vue"; import DiscussionListItem from "@/components/Discussion/DiscussionListItem.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { MemberRole } from "@/types/enums"; import { MemberRole } from "@/types/enums";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor"; import {
import { GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED } from "@/graphql/event"; CURRENT_ACTOR_CLIENT,
GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED,
PERSON_MEMBERSHIP_GROUP,
} from "@/graphql/actor";
import { IMember } from "@/types/actor/member.model"; import { IMember } from "@/types/actor/member.model";
import EmptyContent from "@/components/Utils/EmptyContent.vue"; import EmptyContent from "@/components/Utils/EmptyContent.vue";
@ -96,11 +99,12 @@ import EmptyContent from "@/components/Utils/EmptyContent.vue";
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIPS, query: PERSON_MEMBERSHIP_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {
id: this.currentActor.id, id: this.currentActor.id,
group: this.preferredUsername,
}; };
}, },
subscribeToMore: { subscribeToMore: {
@ -108,14 +112,21 @@ import EmptyContent from "@/components/Utils/EmptyContent.vue";
variables() { variables() {
return { return {
actorId: this.currentActor.id, actorId: this.currentActor.id,
group: this.preferredUsername,
}; };
}, },
skip() { skip() {
return !this.currentActor || !this.currentActor.id; return (
!this.currentActor ||
!this.currentActor.id ||
!this.preferredUsername
);
}, },
}, },
skip() { skip() {
return !this.currentActor || !this.currentActor.id; return (
!this.currentActor || !this.currentActor.id || !this.preferredUsername
);
}, },
}, },
currentActor: CURRENT_ACTOR_CLIENT, currentActor: CURRENT_ACTOR_CLIENT,

View File

@ -628,15 +628,14 @@ export default class Group extends mixins(GroupMixin) {
} }
get groupMember(): IMember | undefined { get groupMember(): IMember | undefined {
if (!this.person || !this.person.id) return undefined; if (this.person?.memberships?.total > 0) {
return this.person.memberships.elements.find( return this.person?.memberships?.elements[0];
({ parent: { id } }) => id === this.group.id }
); return undefined;
} }
get groupMemberships(): (string | undefined)[] { get groupMemberships(): (string | undefined)[] {
if (!this.person || !this.person.id) return []; return this.person?.memberships?.elements
return this.person.memberships.elements
.filter( .filter(
(membership: IMember) => (membership: IMember) =>
![ ![

View File

@ -33,14 +33,13 @@
</nav> </nav>
<section <section
class="container section" class="container section"
v-if="group && isCurrentActorAGroupAdmin" v-if="group && isCurrentActorAGroupAdmin && followers"
> >
<h1>{{ $t("Group Followers") }} ({{ followers.total }})</h1> <h1>{{ $t("Group Followers") }} ({{ followers.total }})</h1>
<b-field :label="$t('Status')" horizontal> <b-field :label="$t('Status')" horizontal>
<b-switch v-model="pending">{{ $t("Pending") }}</b-switch> <b-switch v-model="pending">{{ $t("Pending") }}</b-switch>
</b-field> </b-field>
<b-table <b-table
v-if="followers"
:data="followers.elements" :data="followers.elements"
ref="queueTable" ref="queueTable"
:loading="this.$apollo.loading" :loading="this.$apollo.loading"

View File

@ -934,7 +934,10 @@ defmodule Mobilizon.Federation.ActivityPub do
do: Refresher.fetch_group(member.parent.url, member.actor) do: Refresher.fetch_group(member.parent.url, member.actor)
), ),
Absinthe.Subscription.publish(Endpoint, member.actor, Absinthe.Subscription.publish(Endpoint, member.actor,
group_membership_changed: member.actor.id group_membership_changed: [
Actor.preferred_username_and_domain(member.parent),
member.actor.id
]
), ),
member_as_data <- Convertible.model_to_as(member), member_as_data <- Convertible.model_to_as(member),
audience <- audience <-

View File

@ -126,7 +126,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
}), }),
{:ok, _} <- {:ok, _} <-
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_joined"), Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_joined"),
Absinthe.Subscription.publish(Endpoint, actor, group_membership_changed: actor.id), Absinthe.Subscription.publish(Endpoint, actor,
group_membership_changed: [Actor.preferred_username_and_domain(group), actor.id]
),
join_data <- %{ join_data <- %{
"type" => "Join", "type" => "Join",
"id" => member.url, "id" => member.url,

View File

@ -5,11 +5,17 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.Activity.Member, as: MemberActivity alias Mobilizon.Service.Activity.Member, as: MemberActivity
alias Mobilizon.Web.Endpoint
require Logger require Logger
import Mobilizon.Federation.ActivityPub.Utils, only: [make_update_data: 2] import Mobilizon.Federation.ActivityPub.Utils, only: [make_update_data: 2]
def update( def update(
%Member{parent: %Actor{id: group_id}, id: member_id, role: current_role} = old_member, %Member{
parent: %Actor{id: group_id} = group,
id: member_id,
role: current_role,
actor: %Actor{id: actor_id} = actor
} = old_member,
%{role: updated_role} = args, %{role: updated_role} = args,
%{moderator: %Actor{url: moderator_url, id: moderator_id} = moderator} = additional %{moderator: %Actor{url: moderator_url, id: moderator_id} = moderator} = additional
) do ) do
@ -27,6 +33,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
moderator: moderator, moderator: moderator,
subject: "member_updated" subject: "member_updated"
), ),
Absinthe.Subscription.publish(Endpoint, actor,
group_membership_changed: [Actor.preferred_username_and_domain(group), actor_id]
),
{:ok, true} <- Cachex.del(:activity_pub, "member_#{member_id}"), {:ok, true} <- Cachex.del(:activity_pub, "member_#{member_id}"),
member_as_data <- member_as_data <-
Convertible.model_to_as(member), Convertible.model_to_as(member),

View File

@ -6,9 +6,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
import Mobilizon.Users.Guards import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Events, Users} alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Events.Participant alias Mobilizon.Events.Participant
alias Mobilizon.Storage.Page alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.User alias Mobilizon.Users.User
import Mobilizon.Web.Gettext import Mobilizon.Web.Gettext
@ -306,10 +306,33 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
@doc """ @doc """
Returns the list of events this person is going to Returns the list of events this person is going to
""" """
def person_memberships(%Actor{id: actor_id}, _args, %{context: %{current_user: user}}) do @spec person_memberships(Actor.t(), map(), map()) :: {:ok, Page.t()} | {:error, String.t()}
def person_memberships(%Actor{id: actor_id}, %{group: group}, %{
context: %{current_user: user}
}) do
with {:is_owned, %Actor{id: actor_id}} <- User.owns_actor(user, actor_id),
%Actor{id: group_id} <- Actors.get_actor_by_name(group, :Group),
{:ok, %Member{} = membership} <- Actors.get_member(actor_id, group_id),
memberships <- %Page{
total: 1,
elements: [Repo.preload(membership, [:actor, :parent, :invited_by])]
} do
{:ok, memberships}
else
{:error, :member_not_found} ->
{:ok, %Page{total: 0, elements: []}}
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
end
end
def person_memberships(%Actor{id: actor_id}, %{page: page, limit: limit}, %{
context: %{current_user: user}
}) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
participations <- Actors.list_members_for_actor(actor) do memberships <- Actors.list_members_for_actor(actor, page, limit) do
{:ok, participations} {:ok, memberships}
else else
{:is_owned, nil} -> {:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")} {:error, dgettext("errors", "Profile is not owned by authenticated user")}

View File

@ -70,7 +70,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
field(:participations, :paginated_participant_list, field(:participations, :paginated_participant_list,
description: "The list of events this person goes to" description: "The list of events this person goes to"
) do ) do
arg(:event_id, :id) arg(:event_id, :id, description: "Filter by event ID")
arg(:page, :integer, arg(:page, :integer,
default_value: 1, default_value: 1,
@ -86,6 +86,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
field(:memberships, :paginated_member_list, field(:memberships, :paginated_member_list,
description: "The list of group this person is member of" description: "The list of group this person is member of"
) do ) do
arg(:group, :string, description: "Filter by group federated username")
arg(:page, :integer,
default_value: 1,
description: "The page in the paginated memberships list"
)
arg(:limit, :integer, default_value: 10, description: "The limit of memberships per page")
resolve(&Person.person_memberships/3) resolve(&Person.person_memberships/3)
end end
end end
@ -225,9 +233,10 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
@desc "Notify when a person's membership's status changed for a group" @desc "Notify when a person's membership's status changed for a group"
field :group_membership_changed, :person do field :group_membership_changed, :person do
arg(:person_id, non_null(:id), description: "The person's ID") arg(:person_id, non_null(:id), description: "The person's ID")
arg(:group, non_null(:string), description: "The group's federated username")
config(fn args, _ -> config(fn args, _ ->
{:ok, topic: args.person_id} {:ok, topic: [args.group, args.person_id]}
end) end)
end end
end end