refactor(backend): properly model group-membership (#9124)

This commit is contained in:
Ulf Gebhardt 2026-01-30 04:56:03 +01:00 committed by GitHub
parent bea7c275e8
commit d96cb32f11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 521 additions and 207 deletions

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const ChangeGroupMemberRole = gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const GroupMembers = gql`
query GroupMembers($id: ID!) {
GroupMembers(id: $id) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const JoinGroup = gql`
mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const LeaveGroup = gql`
mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const RemoveUserFromGroup = gql`
mutation ($groupId: ID!, $userId: ID!) {
RemoveUserFromGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`

View File

@ -891,8 +891,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
JoinGroup: {
id: 'owner-of-closed-group',
myRoleInGroup: 'usual',
user: {
id: 'owner-of-closed-group',
},
membership: {
role: 'usual',
},
},
},
errors: undefined,
@ -914,8 +918,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
JoinGroup: {
id: 'current-user',
myRoleInGroup: 'owner',
user: {
id: 'current-user',
},
membership: {
role: 'owner',
},
},
},
errors: undefined,
@ -939,8 +947,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
JoinGroup: {
id: 'current-user',
myRoleInGroup: 'pending',
user: {
id: 'current-user',
},
membership: {
role: 'pending',
},
},
},
errors: undefined,
@ -962,8 +974,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
JoinGroup: {
id: 'owner-of-closed-group',
myRoleInGroup: 'owner',
user: {
id: 'owner-of-closed-group',
},
membership: {
role: 'owner',
},
},
},
errors: undefined,
@ -1001,8 +1017,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
JoinGroup: {
id: 'owner-of-hidden-group',
myRoleInGroup: 'owner',
user: {
id: 'owner-of-hidden-group',
},
membership: {
role: 'owner',
},
},
},
errors: undefined,
@ -1208,16 +1228,28 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
]),
},
@ -1241,16 +1273,28 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
]),
},
@ -1274,16 +1318,28 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
]),
},
@ -1317,16 +1373,28 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'pending',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
]),
},
@ -1350,16 +1418,28 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'pending',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
]),
},
@ -1415,20 +1495,36 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'pending-user',
myRoleInGroup: 'pending',
user: expect.objectContaining({
id: 'pending-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}),
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'admin',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'admin',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
]),
},
@ -1452,20 +1548,36 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'pending-user',
myRoleInGroup: 'pending',
user: expect.objectContaining({
id: 'pending-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}),
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'admin',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'admin',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
]),
},
@ -1489,20 +1601,36 @@ describe('in mode', () => {
data: {
GroupMembers: expect.arrayContaining([
expect.objectContaining({
id: 'pending-user',
myRoleInGroup: 'pending',
user: expect.objectContaining({
id: 'pending-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}),
expect.objectContaining({
id: 'current-user',
myRoleInGroup: 'usual',
user: expect.objectContaining({
id: 'current-user',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}),
expect.objectContaining({
id: 'owner-of-closed-group',
myRoleInGroup: 'admin',
user: expect.objectContaining({
id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'admin',
}),
}),
expect.objectContaining({
id: 'owner-of-hidden-group',
myRoleInGroup: 'owner',
user: expect.objectContaining({
id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}),
]),
},
@ -1600,8 +1728,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'usual-member-user',
myRoleInGroup: 'usual',
user: {
id: 'usual-member-user',
},
membership: {
role: 'usual',
},
},
},
errors: undefined,
@ -1638,8 +1770,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'admin-member-user',
myRoleInGroup: 'admin',
user: {
id: 'admin-member-user',
},
membership: {
role: 'admin',
},
},
},
errors: undefined,
@ -1673,8 +1809,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'second-owner-member-user',
myRoleInGroup: 'owner',
user: {
id: 'second-owner-member-user',
},
membership: {
role: 'owner',
},
},
},
errors: undefined,
@ -1759,8 +1899,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'owner-member-user',
myRoleInGroup: 'owner',
user: {
id: 'owner-member-user',
},
membership: {
role: 'owner',
},
},
},
errors: undefined,
@ -1869,8 +2013,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'admin-member-user',
myRoleInGroup: 'owner',
user: {
id: 'admin-member-user',
},
membership: {
role: 'owner',
},
},
},
errors: undefined,
@ -2047,8 +2195,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'usual-member-user',
myRoleInGroup: 'admin',
user: {
id: 'usual-member-user',
},
membership: {
role: 'admin',
},
},
},
errors: undefined,
@ -2073,8 +2225,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'usual-member-user',
myRoleInGroup: 'usual',
user: {
id: 'usual-member-user',
},
membership: {
role: 'usual',
},
},
},
errors: undefined,
@ -2234,8 +2390,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'pending-member-user',
myRoleInGroup: 'usual',
user: {
id: 'pending-member-user',
},
membership: {
role: 'usual',
},
},
},
errors: undefined,
@ -2260,8 +2420,12 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
ChangeGroupMemberRole: {
id: 'pending-member-user',
myRoleInGroup: 'pending',
user: {
id: 'pending-member-user',
},
membership: {
role: 'pending',
},
},
},
errors: undefined,
@ -2413,7 +2577,7 @@ describe('in mode', () => {
},
})
return result.data?.GroupMembers
? !!result.data.GroupMembers.find((member) => member.id === userId)
? !!result.data.GroupMembers.find((member) => member.user.id === userId)
: null
}
@ -2440,8 +2604,10 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
LeaveGroup: {
id: 'pending-member-user',
myRoleInGroup: null,
user: {
id: 'pending-member-user',
},
membership: null,
},
},
errors: undefined,
@ -2467,8 +2633,10 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
LeaveGroup: {
id: 'usual-member-user',
myRoleInGroup: null,
user: {
id: 'usual-member-user',
},
membership: null,
},
},
errors: undefined,
@ -2494,8 +2662,10 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
LeaveGroup: {
id: 'admin-member-user',
myRoleInGroup: null,
user: {
id: 'admin-member-user',
},
membership: null,
},
},
errors: undefined,
@ -3021,8 +3191,10 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
RemoveUserFromGroup: expect.objectContaining({
id: 'usual-member-user',
myRoleInGroup: null,
user: expect.objectContaining({
id: 'usual-member-user',
}),
membership: null,
}),
},
errors: undefined,
@ -3093,8 +3265,10 @@ describe('in mode', () => {
).resolves.toMatchObject({
data: {
RemoveUserFromGroup: expect.objectContaining({
id: 'usual-member-user',
myRoleInGroup: null,
user: {
id: 'usual-member-user',
},
membership: null,
}),
},
errors: undefined,

View File

@ -63,7 +63,7 @@ export default {
const readTxResultPromise = session.readTransaction(async (txc) => {
const groupMemberCypher = `
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
RETURN user {.*, myRoleInGroup: membership.role}
RETURN user {.*}, membership {.*}
SKIP toInteger($offset) LIMIT toInteger($first)
`
const transactionResponse = await txc.run(groupMemberCypher, {
@ -71,7 +71,9 @@ export default {
first,
offset,
})
return transactionResponse.records.map((record) => record.get('user'))
return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
})
try {
return await readTxResultPromise
@ -273,8 +275,8 @@ export default {
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const joinGroupCypher = `
MATCH (member:User {id: $userId}), (group:Group {id: $groupId})
MERGE (member)-[membership:MEMBER_OF]->(group)
MATCH (user:User {id: $userId}), (group:Group {id: $groupId})
MERGE (user)-[membership:MEMBER_OF]->(group)
ON CREATE SET
membership.createdAt = toString(datetime()),
membership.updatedAt = null,
@ -283,14 +285,15 @@ export default {
THEN 'usual'
ELSE 'pending'
END
RETURN member {.*, myRoleInGroup: membership.role}
RETURN user {.*}, membership {.*}
`
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
const [member] = transactionResponse.records.map((record) => record.get('member'))
return member
return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
})
try {
return await writeTxResultPromise
return (await writeTxResultPromise)[0]
} catch (error) {
throw new Error(error)
} finally {
@ -337,7 +340,7 @@ export default {
membership.updatedAt = toString(datetime()),
membership.role = $roleInGroup
${postRestrictionCypher}
RETURN member {.*, myRoleInGroup: membership.role}
RETURN member {.*} as user, membership {.*}
`
const transactionResponse = await transaction.run(joinGroupCypher, {
@ -345,7 +348,9 @@ export default {
userId,
roleInGroup,
})
const [member] = transactionResponse.records.map((record) => record.get('member'))
const [member] = transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return member
})
try {
@ -528,14 +533,16 @@ const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId)
WITH user, collect(p) AS posts
FOREACH (post IN posts |
MERGE (user)-[:CANNOT_SEE]->(post))
RETURN user {.*, myRoleInGroup: NULL}
RETURN user {.*}, NULL as membership
`
const transactionResponse = await transaction.run(removeUserFromGroupCypher, {
groupId,
userId,
})
const [user] = await transactionResponse.records.map((record) => record.get('user'))
const [user] = await transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return user
})
}

View File

@ -1089,16 +1089,24 @@ describe('redeemInviteCode', () => {
data: {
GroupMembers: expect.arrayContaining([
{
id: 'inviting-user',
myRoleInGroup: 'owner',
name: 'Inviting User',
slug: 'inviting-user',
user: {
id: 'inviting-user',
name: 'Inviting User',
slug: 'inviting-user',
},
membership: {
role: 'owner',
},
},
{
id: 'other-user',
myRoleInGroup: 'pending',
name: 'Other User',
slug: 'other-user',
user: {
id: 'other-user',
name: 'Other User',
slug: 'other-user',
},
membership: {
role: 'pending',
},
},
]),
},

View File

@ -55,6 +55,11 @@ type Group {
currentlyPinnedPostsCount: Int! @neo4j_ignore
}
type GroupMember {
user: User
membership: MEMBER_OF
}
input _GroupFilter {
AND: [_GroupFilter!]
OR: [_GroupFilter!]
@ -82,7 +87,7 @@ type Query {
# orderBy: [_GroupOrdering] # not implemented yet
# filter: _GroupFilter # not implemented yet
GroupMembers(id: ID!, first: Int, offset: Int): [User]
GroupMembers(id: ID!, first: Int, offset: Int): [GroupMember]
# orderBy: [_UserOrdering] # not implemented yet
# filter: _UserFilter # not implemented yet
@ -128,13 +133,13 @@ type Mutation {
# DeleteGroup(id: ID!): Group
JoinGroup(groupId: ID!, userId: ID!): User
JoinGroup(groupId: ID!, userId: ID!): GroupMember
LeaveGroup(groupId: ID!, userId: ID!): User
LeaveGroup(groupId: ID!, userId: ID!): GroupMember
ChangeGroupMemberRole(groupId: ID!, userId: ID!, roleInGroup: GroupMemberRole!): User
ChangeGroupMemberRole(groupId: ID!, userId: ID!, roleInGroup: GroupMemberRole!): GroupMember
RemoveUserFromGroup(groupId: ID!, userId: ID!): User
RemoveUserFromGroup(groupId: ID!, userId: ID!): GroupMember
muteGroup(groupId: ID!): Group
unmuteGroup(groupId: ID!): Group

View File

@ -153,8 +153,6 @@ type User {
emotions: [EMOTED]
activeCategories: [String] @neo4j_ignore
myRoleInGroup: GroupMemberRole
}
input _UserFilter {

View File

@ -52,10 +52,14 @@ describe('CtaJoinLeaveGroup.vue', () => {
mocks.$apollo.mutate = jest.fn().mockResolvedValue({
data: {
JoinGroup: {
id: 'g-123',
slug: 'group-123',
name: 'Group 123',
myRoleInGroup: 'usual',
user: {
id: 'g-123',
slug: 'group-123',
name: 'Group 123',
},
membership: {
role: 'usual',
},
},
},
})
@ -66,10 +70,14 @@ describe('CtaJoinLeaveGroup.vue', () => {
expect(wrapper.emitted().update).toEqual([
[
{
id: 'g-123',
slug: 'group-123',
name: 'Group 123',
myRoleInGroup: 'usual',
user: {
id: 'g-123',
slug: 'group-123',
name: 'Group 123',
},
membership: {
role: 'usual',
},
},
],
])

View File

@ -8,14 +8,22 @@ const propsData = {
groupId: 'group-id',
groupMembers: [
{
slug: 'owner',
id: 'owner',
myRoleInGroup: 'owner',
user: {
slug: 'owner',
id: 'owner',
},
membership: {
role: 'owner',
},
},
{
slug: 'user',
id: 'user',
myRoleInGroup: 'usual',
user: {
slug: 'user',
id: 'user',
},
membership: {
role: 'usual',
},
},
],
}
@ -30,9 +38,13 @@ const apolloMock = jest
.mockResolvedValue({
data: {
ChangeGroupMemberRole: {
slug: 'user',
id: 'user',
myRoleInGroup: 'admin',
user: {
slug: 'user',
id: 'user',
},
membership: {
role: 'admin',
},
},
},
})
@ -117,9 +129,11 @@ describe('GroupMember', () => {
apolloMock.mockRejectedValueOnce({ message: 'Oh no!!' }).mockResolvedValue({
data: {
RemoveUserFromGroup: {
slug: 'user',
id: 'user',
myRoleInGroup: null,
user: {
slug: 'user',
id: 'user',
},
membership: null,
},
},
})

View File

@ -7,21 +7,21 @@
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
params: { id: scope.row.user.id, slug: scope.row.user.slug },
}"
>
<profile-avatar :profile="scope.row" size="small" />
<profile-avatar :profile="scope.row.user" size="small" />
</nuxt-link>
</template>
<template #name="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
params: { id: scope.row.user.id, slug: scope.row.user.slug },
}"
>
<ds-text>
<b>{{ scope.row.name | truncate(20) }}</b>
<b>{{ scope.row.user.name | truncate(20) }}</b>
</ds-text>
</nuxt-link>
</template>
@ -29,37 +29,37 @@
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
params: { id: scope.row.user.id, slug: scope.row.user.slug },
}"
>
<ds-text>
<b>{{ `@${scope.row.slug}` | truncate(20) }}</b>
<b>{{ `@${scope.row.user.slug}` | truncate(20) }}</b>
</ds-text>
</nuxt-link>
</template>
<template #roleInGroup="scope">
<select
v-if="scope.row.myRoleInGroup !== 'owner'"
v-if="scope.row.membership.role !== 'owner'"
:options="['pending', 'usual', 'admin', 'owner']"
:value="`${scope.row.myRoleInGroup}`"
@change="changeMemberRole(scope.row.id, $event)"
:value="`${scope.row.membership.role}`"
@change="changeMemberRole(scope.row.user.id, $event)"
>
<option v-for="role in ['pending', 'usual', 'admin', 'owner']" :key="role" :value="role">
{{ $t(`group.roles.${role}`) }}
</option>
</select>
<ds-chip v-else color="primary">
{{ $t(`group.roles.${scope.row.myRoleInGroup}`) }}
{{ $t(`group.roles.${scope.row.membership.role}`) }}
</ds-chip>
</template>
<template #edit="scope">
<base-button
v-if="scope.row.myRoleInGroup !== 'owner'"
v-if="scope.row.membership.role !== 'owner'"
size="small"
primary
@click="
isOpen = true
userId = scope.row.id
userId = scope.row.user.id
"
>
{{ $t('group.removeMemberButton') }}

View File

@ -111,10 +111,14 @@ export const joinGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`
@ -124,10 +128,14 @@ export const leaveGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`
@ -137,10 +145,14 @@ export const changeGroupMemberRoleMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`
@ -150,10 +162,14 @@ export const removeUserFromGroupMutation = () => {
return gql`
mutation ($groupId: ID!, $userId: ID!) {
RemoveUserFromGroup(groupId: $groupId, userId: $userId) {
id
name
slug
myRoleInGroup
user {
id
name
slug
}
membership {
role
}
}
}
`
@ -215,12 +231,16 @@ export const groupMembersQuery = () => {
query ($id: ID!, $first: Int, $offset: Int) {
GroupMembers(id: $id, first: $first, offset: $offset) {
id
name
slug
myRoleInGroup
avatar {
...imageUrls
user {
id
name
slug
avatar {
...imageUrls
}
}
membership {
role
}
}
}

View File

@ -244,7 +244,12 @@ describe('GroupProfileSlug', () => {
...yogaPractice,
myRole: 'owner',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -282,7 +287,12 @@ describe('GroupProfileSlug', () => {
...yogaPractice,
myRole: 'usual',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -301,7 +311,12 @@ describe('GroupProfileSlug', () => {
...yogaPractice,
myRole: 'pending',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -320,7 +335,12 @@ describe('GroupProfileSlug', () => {
...yogaPractice,
myRole: null,
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -342,7 +362,12 @@ describe('GroupProfileSlug', () => {
...schoolForCitizens,
myRole: 'owner',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -361,7 +386,12 @@ describe('GroupProfileSlug', () => {
...schoolForCitizens,
myRole: 'usual',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -403,7 +433,12 @@ describe('GroupProfileSlug', () => {
...schoolForCitizens,
myRole: 'pending',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -422,7 +457,12 @@ describe('GroupProfileSlug', () => {
...schoolForCitizens,
myRole: null,
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -445,7 +485,12 @@ describe('GroupProfileSlug', () => {
...investigativeJournalism,
myRole: 'owner',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -464,7 +509,12 @@ describe('GroupProfileSlug', () => {
...investigativeJournalism,
myRole: 'usual',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -483,7 +533,12 @@ describe('GroupProfileSlug', () => {
...investigativeJournalism,
myRole: 'pending',
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})
@ -502,7 +557,12 @@ describe('GroupProfileSlug', () => {
...investigativeJournalism,
myRole: null,
},
GroupMembers: [peterLustig, jennyRostock, bobDerBaumeister, huey],
GroupMembers: [
{ user: peterLustig, membership: { role: 'owner' } },
{ user: jennyRostock, membership: { role: 'usual' } },
{ user: bobDerBaumeister, membership: { role: 'usual' } },
{ user: huey, membership: { role: 'usual' } },
],
}
})
})

View File

@ -181,7 +181,7 @@
:allProfilesCount="
isAllowedSeeingGroupMembers && group.membersCount ? group.membersCount : 0
"
:profiles="isAllowedSeeingGroupMembers ? groupMembers : []"
:profiles="isAllowedSeeingGroupMembers ? groupMembers.map((d) => d.user) : []"
:loading="$apollo.loading"
@fetchAllProfiles="fetchAllMembers"
/>