diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js
index 2a611f324..c6f110ed1 100644
--- a/backend/src/db/graphql/groups.js
+++ b/backend/src/db/graphql/groups.js
@@ -39,6 +39,28 @@ export const createGroupMutation = gql`
}
`
+export const joinGroupMutation = gql`
+ mutation ($groupId: ID!, $userId: ID!) {
+ JoinGroup(groupId: $groupId, userId: $userId) {
+ id
+ name
+ slug
+ myRoleInGroup
+ }
+ }
+`
+
+export const changeGroupMemberRoleMutation = gql`
+ mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
+ ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
+ id
+ name
+ slug
+ myRoleInGroup
+ }
+ }
+`
+
// ------ queries
export const groupQuery = gql`
@@ -93,3 +115,14 @@ export const groupQuery = gql`
}
}
`
+
+export const groupMembersQuery = gql`
+ query ($id: ID!, $first: Int, $offset: Int, $orderBy: [_UserOrdering], $filter: _UserFilter) {
+ GroupMembers(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) {
+ id
+ name
+ slug
+ myRoleInGroup
+ }
+ }
+`
diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js
index e41ef1abc..a71b11131 100644
--- a/backend/src/db/seed.js
+++ b/backend/src/db/seed.js
@@ -5,7 +5,11 @@ import createServer from '../server'
import faker from '@faker-js/faker'
import Factory from '../db/factories'
import { getNeode, getDriver } from '../db/neo4j'
-import { createGroupMutation } from './graphql/groups'
+import {
+ createGroupMutation,
+ joinGroupMutation,
+ changeGroupMemberRoleMutation,
+} from './graphql/groups'
import { createPostMutation } from './graphql/posts'
import { createCommentMutation } from './graphql/comments'
@@ -400,6 +404,62 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
}),
])
+ await Promise.all([
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u2',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u3',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u4',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u6',
+ },
+ }),
+ ])
+ await Promise.all([
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u2',
+ roleInGroup: 'usual',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u4',
+ roleInGroup: 'admin',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u3',
+ roleInGroup: 'owner',
+ },
+ }),
+ ])
authenticatedUser = await jennyRostock.toJson()
await Promise.all([
@@ -416,6 +476,77 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
}),
])
+ await Promise.all([
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g1',
+ userId: 'u1',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g1',
+ userId: 'u2',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g1',
+ userId: 'u5',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g1',
+ userId: 'u6',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g1',
+ userId: 'u7',
+ },
+ }),
+ ])
+ await Promise.all([
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u1',
+ roleInGroup: 'usual',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u2',
+ roleInGroup: 'usual',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u5',
+ roleInGroup: 'admin',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u6',
+ roleInGroup: 'owner',
+ },
+ }),
+ ])
authenticatedUser = await bobDerBaumeister.toJson()
await Promise.all([
@@ -432,6 +563,62 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
}),
])
+ await Promise.all([
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g2',
+ userId: 'u4',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g2',
+ userId: 'u5',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g2',
+ userId: 'u6',
+ },
+ }),
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'g2',
+ userId: 'u7',
+ },
+ }),
+ ])
+ await Promise.all([
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u4',
+ roleInGroup: 'usual',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u5',
+ roleInGroup: 'usual',
+ },
+ }),
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'g0',
+ userId: 'u6',
+ roleInGroup: 'usual',
+ },
+ }),
+ ])
// Create Posts
diff --git a/backend/src/helpers/jest.js b/backend/src/helpers/jest.js
index ecfc1a042..e3f6a3c84 100644
--- a/backend/src/helpers/jest.js
+++ b/backend/src/helpers/jest.js
@@ -7,3 +7,12 @@
export function gql(strings) {
return strings.join('')
}
+
+// sometime we have to wait to check a db state by having a look into the db in a certain moment
+// or we wait a bit to check if we missed to set an await somewhere
+// see: https://www.sitepoint.com/delay-sleep-pause-wait/
+export function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms))
+}
+// usage – 4 seconds for example
+// await sleep(4 * 1000)
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index c81b069d2..cf57aae85 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -52,6 +52,115 @@ const isMySocialMedia = rule({
return socialMedia.ownedBy.node.id === user.id
})
+const isAllowedSeeingMembersOfGroup = rule({
+ cache: 'no_cache',
+})(async (_parent, args, { user, driver }) => {
+ if (!(user && user.id)) return false
+ const { id: groupId } = args
+ const session = driver.session()
+ const readTxPromise = session.readTransaction(async (transaction) => {
+ const transactionResponse = await transaction.run(
+ `
+ MATCH (group:Group {id: $groupId})
+ OPTIONAL MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group)
+ RETURN group {.*}, member {.*, myRoleInGroup: membership.role}
+ `,
+ { groupId, userId: user.id },
+ )
+ return {
+ member: transactionResponse.records.map((record) => record.get('member'))[0],
+ group: transactionResponse.records.map((record) => record.get('group'))[0],
+ }
+ })
+ try {
+ const { member, group } = await readTxPromise
+ return (
+ !!group &&
+ (group.groupType === 'public' ||
+ (['closed', 'hidden'].includes(group.groupType) &&
+ !!member &&
+ ['usual', 'admin', 'owner'].includes(member.myRoleInGroup)))
+ )
+ } catch (error) {
+ throw new Error(error)
+ } finally {
+ session.close()
+ }
+})
+
+const isAllowedToChangeGroupMemberRole = rule({
+ cache: 'no_cache',
+})(async (_parent, args, { user, driver }) => {
+ if (!(user && user.id)) return false
+ const adminId = user.id
+ const { groupId, userId, roleInGroup } = args
+ if (adminId === userId) return false
+ const session = driver.session()
+ const readTxPromise = session.readTransaction(async (transaction) => {
+ const transactionResponse = await transaction.run(
+ `
+ MATCH (admin:User {id: $adminId})-[adminMembership:MEMBER_OF]->(group:Group {id: $groupId})
+ OPTIONAL MATCH (group)<-[userMembership:MEMBER_OF]-(member:User {id: $userId})
+ RETURN group {.*}, admin {.*, myRoleInGroup: adminMembership.role}, member {.*, myRoleInGroup: userMembership.role}
+ `,
+ { groupId, adminId, userId },
+ )
+ return {
+ admin: transactionResponse.records.map((record) => record.get('admin'))[0],
+ group: transactionResponse.records.map((record) => record.get('group'))[0],
+ member: transactionResponse.records.map((record) => record.get('member'))[0],
+ }
+ })
+ try {
+ const { admin, group, member } = await readTxPromise
+ return (
+ !!group &&
+ !!admin &&
+ (!member ||
+ (!!member &&
+ (member.myRoleInGroup === roleInGroup || !['owner'].includes(member.myRoleInGroup)))) &&
+ ((['admin'].includes(admin.myRoleInGroup) &&
+ ['pending', 'usual', 'admin'].includes(roleInGroup)) ||
+ (['owner'].includes(admin.myRoleInGroup) &&
+ ['pending', 'usual', 'admin', 'owner'].includes(roleInGroup)))
+ )
+ } catch (error) {
+ throw new Error(error)
+ } finally {
+ session.close()
+ }
+})
+
+const isAllowedToJoinGroup = rule({
+ cache: 'no_cache',
+})(async (_parent, args, { user, driver }) => {
+ if (!(user && user.id)) return false
+ const { groupId, userId } = args
+ const session = driver.session()
+ const readTxPromise = session.readTransaction(async (transaction) => {
+ const transactionResponse = await transaction.run(
+ `
+ MATCH (group:Group {id: $groupId})
+ OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(member:User {id: $userId})
+ RETURN group {.*}, member {.*, myRoleInGroup: membership.role}
+ `,
+ { groupId, userId },
+ )
+ return {
+ group: transactionResponse.records.map((record) => record.get('group'))[0],
+ member: transactionResponse.records.map((record) => record.get('member'))[0],
+ }
+ })
+ try {
+ const { group, member } = await readTxPromise
+ return !!group && (group.groupType !== 'hidden' || (!!member && !!member.myRoleInGroup))
+ } catch (error) {
+ throw new Error(error)
+ } finally {
+ session.close()
+ }
+})
+
const isAuthor = rule({
cache: 'no_cache',
})(async (_parent, args, { user, driver }) => {
@@ -78,7 +187,7 @@ const isAuthor = rule({
const isDeletingOwnAccount = rule({
cache: 'no_cache',
-})(async (parent, args, context, info) => {
+})(async (parent, args, context, _info) => {
return context.user.id === args.id
})
@@ -115,6 +224,7 @@ export default shield(
statistics: allow,
currentUser: allow,
Group: isAuthenticated,
+ GroupMembers: isAllowedSeeingMembersOfGroup,
Post: allow,
profilePagePosts: allow,
Comment: allow,
@@ -142,6 +252,8 @@ export default shield(
SignupVerification: allow,
UpdateUser: onlyYourself,
CreateGroup: isAuthenticated,
+ JoinGroup: isAllowedToJoinGroup,
+ ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole,
CreatePost: isAuthenticated,
UpdatePost: isAuthor,
DeletePost: isAuthor,
diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js
index 5737f5505..abaa1716f 100644
--- a/backend/src/schema/resolvers/groups.js
+++ b/backend/src/schema/resolvers/groups.js
@@ -46,6 +46,27 @@ export default {
session.close()
}
},
+ GroupMembers: async (_object, params, context, _resolveInfo) => {
+ const { id: groupId } = params
+ const session = context.driver.session()
+ const readTxResultPromise = session.readTransaction(async (txc) => {
+ const groupMemberCypher = `
+ MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
+ RETURN user {.*, myRoleInGroup: membership.role}
+ `
+ const result = await txc.run(groupMemberCypher, {
+ groupId,
+ })
+ return result.records.map((record) => record.get('user'))
+ })
+ try {
+ return await readTxResultPromise
+ } catch (error) {
+ throw new Error(error)
+ } finally {
+ session.close()
+ }
+ },
},
Mutation: {
CreateGroup: async (_parent, params, context, _resolveInfo) => {
@@ -86,9 +107,10 @@ export default {
MATCH (owner:User {id: $userId})
MERGE (owner)-[:CREATED]->(group)
MERGE (owner)-[membership:MEMBER_OF]->(group)
- SET membership.createdAt = toString(datetime())
- SET membership.updatedAt = toString(datetime())
- SET membership.role = 'owner'
+ SET
+ membership.createdAt = toString(datetime()),
+ membership.updatedAt = null,
+ membership.role = 'owner'
${categoriesCypher}
RETURN group {.*, myRole: membership.role}
`,
@@ -100,8 +122,7 @@ export default {
return group
})
try {
- const group = await writeTxResultPromise
- return group
+ return await writeTxResultPromise
} catch (error) {
if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Group with this slug already exists!')
@@ -110,6 +131,63 @@ export default {
session.close()
}
},
+ JoinGroup: async (_parent, params, context, _resolveInfo) => {
+ const { groupId, userId } = params
+ 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)
+ ON CREATE SET
+ membership.createdAt = toString(datetime()),
+ membership.updatedAt = null,
+ membership.role =
+ CASE WHEN group.groupType = 'public'
+ THEN 'usual'
+ ELSE 'pending'
+ END
+ RETURN member {.*, myRoleInGroup: membership.role}
+ `
+ const result = await transaction.run(joinGroupCypher, { groupId, userId })
+ const [member] = await result.records.map((record) => record.get('member'))
+ return member
+ })
+ try {
+ return await writeTxResultPromise
+ } catch (error) {
+ throw new Error(error)
+ } finally {
+ session.close()
+ }
+ },
+ ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => {
+ const { groupId, userId, roleInGroup } = params
+ 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)
+ ON CREATE SET
+ membership.createdAt = toString(datetime()),
+ membership.updatedAt = null,
+ membership.role = $roleInGroup
+ ON MATCH SET
+ membership.updatedAt = toString(datetime()),
+ membership.role = $roleInGroup
+ RETURN member {.*, myRoleInGroup: membership.role}
+ `
+ const result = await transaction.run(joinGroupCypher, { groupId, userId, roleInGroup })
+ const [member] = await result.records.map((record) => record.get('member'))
+ return member
+ })
+ try {
+ return await writeTxResultPromise
+ } catch (error) {
+ throw new Error(error)
+ } finally {
+ session.close()
+ }
+ },
},
Group: {
...Resolver('Group', {
diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js
index 6890e9147..1d272de2b 100644
--- a/backend/src/schema/resolvers/groups.spec.js
+++ b/backend/src/schema/resolvers/groups.spec.js
@@ -1,6 +1,12 @@
import { createTestClient } from 'apollo-server-testing'
import Factory, { cleanDatabase } from '../../db/factories'
-import { createGroupMutation, groupQuery } from '../../db/graphql/groups'
+import {
+ createGroupMutation,
+ joinGroupMutation,
+ changeGroupMemberRoleMutation,
+ groupMembersQuery,
+ groupQuery,
+} from '../../db/graphql/groups'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
import CONFIG from '../../config'
@@ -8,8 +14,6 @@ import CONFIG from '../../config'
const driver = getDriver()
const neode = getNeode()
-let query
-let mutate
let authenticatedUser
let user
@@ -18,27 +22,18 @@ const descriptionAdditional100 =
' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789'
let variables = {}
-beforeAll(async () => {
- await cleanDatabase()
-
- const { server } = createServer({
- context: () => {
- return {
- driver,
- neode,
- user: authenticatedUser,
- }
- },
- })
- query = createTestClient(server).query
- mutate = createTestClient(server).mutate
+const { server } = createServer({
+ context: () => {
+ return {
+ driver,
+ neode,
+ user: authenticatedUser,
+ }
+ },
})
+const { mutate, query } = createTestClient(server)
-afterAll(async () => {
- await cleanDatabase()
-})
-
-beforeEach(async () => {
+const seedBasicsAndClearAuthentication = async () => {
variables = {}
user = await Factory.build(
'user',
@@ -78,241 +73,1973 @@ beforeEach(async () => {
}),
])
authenticatedUser = null
-})
+}
-// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
-afterEach(async () => {
+beforeAll(async () => {
await cleanDatabase()
})
-describe('Group', () => {
- describe('unauthenticated', () => {
- it('throws authorization error', async () => {
- const { errors } = await query({ query: groupQuery, variables: {} })
- expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
- })
- })
+afterAll(async () => {
+ await cleanDatabase()
+})
- describe('authenticated', () => {
+describe('in mode', () => {
+ describe('clean db after each single test', () => {
beforeEach(async () => {
- authenticatedUser = await user.toJson()
+ await seedBasicsAndClearAuthentication()
})
- let otherUser
+ // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
+ afterEach(async () => {
+ await cleanDatabase()
+ })
- beforeEach(async () => {
- otherUser = await Factory.build(
- 'user',
- {
- id: 'other-user',
- name: 'Other TestUser',
- },
- {
- email: 'test2@example.org',
- password: '1234',
- },
- )
- authenticatedUser = await otherUser.toJson()
- await mutate({
- mutation: createGroupMutation,
- variables: {
- id: 'others-group',
- name: 'Uninteresting Group',
- about: 'We will change nothing!',
- description: 'We love it like it is!?' + descriptionAdditional100,
- groupType: 'closed',
- actionRadius: 'global',
- categoryIds,
- },
- })
- authenticatedUser = await user.toJson()
- await mutate({
- mutation: createGroupMutation,
- variables: {
- id: 'my-group',
+ describe('CreateGroup', () => {
+ beforeEach(() => {
+ variables = {
+ ...variables,
+ id: 'g589',
name: 'The Best Group',
+ slug: 'the-group',
about: 'We will change the world!',
description: 'Some description' + descriptionAdditional100,
groupType: 'public',
actionRadius: 'regional',
categoryIds,
- },
+ }
})
- })
- describe('query groups', () => {
- describe('without any filters', () => {
- it('finds all groups', async () => {
- const expected = {
- data: {
- Group: expect.arrayContaining([
- expect.objectContaining({
- id: 'my-group',
- slug: 'the-best-group',
- myRole: 'owner',
- }),
- expect.objectContaining({
- id: 'others-group',
- slug: 'uninteresting-group',
- myRole: null,
- }),
- ]),
- },
- errors: undefined,
- }
- await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected)
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({ mutation: createGroupMutation, variables })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
})
})
- describe('isMember = true', () => {
- it('finds only groups where user is member', async () => {
- const expected = {
- data: {
- Group: [
- {
- id: 'my-group',
- slug: 'the-best-group',
+ describe('authenticated', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ it('creates a group', async () => {
+ await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject(
+ {
+ data: {
+ CreateGroup: {
+ name: 'The Best Group',
+ slug: 'the-group',
+ about: 'We will change the world!',
+ },
+ },
+ errors: undefined,
+ },
+ )
+ })
+
+ it('assigns the authenticated user as owner', async () => {
+ await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject(
+ {
+ data: {
+ CreateGroup: {
+ name: 'The Best Group',
myRole: 'owner',
},
- ],
- },
- errors: undefined,
- }
- await expect(
- query({ query: groupQuery, variables: { isMember: true } }),
- ).resolves.toMatchObject(expected)
- })
- })
-
- describe('isMember = false', () => {
- it('finds only groups where user is not(!) member', async () => {
- const expected = {
- data: {
- Group: expect.arrayContaining([
- expect.objectContaining({
- id: 'others-group',
- slug: 'uninteresting-group',
- myRole: null,
- }),
- ]),
- },
- errors: undefined,
- }
- await expect(
- query({ query: groupQuery, variables: { isMember: false } }),
- ).resolves.toMatchObject(expected)
- })
- })
- })
- })
-})
-
-describe('CreateGroup', () => {
- beforeEach(() => {
- variables = {
- ...variables,
- id: 'g589',
- name: 'The Best Group',
- slug: 'the-group',
- about: 'We will change the world!',
- description: 'Some description' + descriptionAdditional100,
- groupType: 'public',
- actionRadius: 'regional',
- categoryIds,
- }
- })
-
- describe('unauthenticated', () => {
- it('throws authorization error', async () => {
- const { errors } = await mutate({ mutation: createGroupMutation, variables })
- expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
- })
- })
-
- describe('authenticated', () => {
- beforeEach(async () => {
- authenticatedUser = await user.toJson()
- })
-
- it('creates a group', async () => {
- const expected = {
- data: {
- CreateGroup: {
- name: 'The Best Group',
- slug: 'the-group',
- about: 'We will change the world!',
- },
- },
- errors: undefined,
- }
- await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject(
- expected,
- )
- })
-
- it('assigns the authenticated user as owner', async () => {
- const expected = {
- data: {
- CreateGroup: {
- name: 'The Best Group',
- myRole: 'owner',
- },
- },
- errors: undefined,
- }
- await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject(
- expected,
- )
- })
-
- it('has "disabled" and "deleted" default to "false"', async () => {
- const expected = { data: { CreateGroup: { disabled: false, deleted: false } } }
- await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject(
- expected,
- )
- })
-
- describe('description', () => {
- describe('length without HTML', () => {
- describe('less then 100 chars', () => {
- it('throws error: "Too view categories!"', async () => {
- const { errors } = await mutate({
- mutation: createGroupMutation,
- variables: {
- ...variables,
- description:
- '0123456789' +
- '0123456789',
},
+ errors: undefined,
+ },
+ )
+ })
+
+ it('has "disabled" and "deleted" default to "false"', async () => {
+ await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject(
+ {
+ data: { CreateGroup: { disabled: false, deleted: false } },
+ },
+ )
+ })
+
+ describe('description', () => {
+ describe('length without HTML', () => {
+ describe('less then 100 chars', () => {
+ it('throws error: "Too view categories!"', async () => {
+ const { errors } = await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ ...variables,
+ description:
+ '0123456789' +
+ '0123456789',
+ },
+ })
+ expect(errors[0]).toHaveProperty('message', 'Description too short!')
+ })
+ })
+ })
+ })
+
+ describe('categories', () => {
+ beforeEach(() => {
+ CONFIG.CATEGORIES_ACTIVE = true
+ })
+
+ describe('not even one', () => {
+ it('throws error: "Too view categories!"', async () => {
+ const { errors } = await mutate({
+ mutation: createGroupMutation,
+ variables: { ...variables, categoryIds: null },
+ })
+ expect(errors[0]).toHaveProperty('message', 'Too view categories!')
+ })
+ })
+
+ describe('four', () => {
+ it('throws error: "Too many categories!"', async () => {
+ const { errors } = await mutate({
+ mutation: createGroupMutation,
+ variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] },
+ })
+ expect(errors[0]).toHaveProperty('message', 'Too many categories!')
+ })
+ })
+ })
+ })
+ })
+ })
+
+ describe('building up – clean db after each resolver', () => {
+ describe('Group', () => {
+ beforeAll(async () => {
+ await seedBasicsAndClearAuthentication()
+ })
+
+ afterAll(async () => {
+ await cleanDatabase()
+ })
+
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
+ const { errors } = await query({ query: groupQuery, variables: {} })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('authenticated', () => {
+ let otherUser
+
+ beforeAll(async () => {
+ otherUser = await Factory.build(
+ 'user',
+ {
+ id: 'other-user',
+ name: 'Other TestUser',
+ },
+ {
+ email: 'test2@example.org',
+ password: '1234',
+ },
+ )
+ authenticatedUser = await otherUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'others-group',
+ name: 'Uninteresting Group',
+ about: 'We will change nothing!',
+ description: 'We love it like it is!?' + descriptionAdditional100,
+ groupType: 'closed',
+ actionRadius: 'global',
+ categoryIds,
+ },
+ })
+ authenticatedUser = await user.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'my-group',
+ name: 'The Best Group',
+ about: 'We will change the world!',
+ description: 'Some description' + descriptionAdditional100,
+ groupType: 'public',
+ actionRadius: 'regional',
+ categoryIds,
+ },
+ })
+ })
+
+ describe('query groups', () => {
+ describe('without any filters', () => {
+ it('finds all groups', async () => {
+ await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({
+ data: {
+ Group: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'my-group',
+ slug: 'the-best-group',
+ myRole: 'owner',
+ }),
+ expect.objectContaining({
+ id: 'others-group',
+ slug: 'uninteresting-group',
+ myRole: null,
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('isMember = true', () => {
+ it('finds only groups where user is member', async () => {
+ await expect(
+ query({ query: groupQuery, variables: { isMember: true } }),
+ ).resolves.toMatchObject({
+ data: {
+ Group: [
+ {
+ id: 'my-group',
+ slug: 'the-best-group',
+ myRole: 'owner',
+ },
+ ],
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('isMember = false', () => {
+ it('finds only groups where user is not(!) member', async () => {
+ await expect(
+ query({ query: groupQuery, variables: { isMember: false } }),
+ ).resolves.toMatchObject({
+ data: {
+ Group: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'others-group',
+ slug: 'uninteresting-group',
+ myRole: null,
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
})
- expect(errors[0]).toHaveProperty('message', 'Description too short!')
})
})
})
})
- describe('categories', () => {
- beforeEach(() => {
- CONFIG.CATEGORIES_ACTIVE = true
+ describe('JoinGroup', () => {
+ beforeAll(async () => {
+ await seedBasicsAndClearAuthentication()
})
- describe('not even one', () => {
- it('throws error: "Too view categories!"', async () => {
+ afterAll(async () => {
+ await cleanDatabase()
+ })
+
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
const { errors } = await mutate({
- mutation: createGroupMutation,
- variables: { ...variables, categoryIds: null },
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'not-existing-group',
+ userId: 'current-user',
+ },
})
- expect(errors[0]).toHaveProperty('message', 'Too view categories!')
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
})
})
- describe('four', () => {
- it('throws error: "Too many categories!"', async () => {
- const { errors } = await mutate({
+ describe('authenticated', () => {
+ let ownerOfClosedGroupUser
+ let ownerOfHiddenGroupUser
+
+ beforeAll(async () => {
+ // create users
+ ownerOfClosedGroupUser = await Factory.build(
+ 'user',
+ {
+ id: 'owner-of-closed-group',
+ name: 'Owner Of Closed Group',
+ },
+ {
+ email: 'owner-of-closed-group@example.org',
+ password: '1234',
+ },
+ )
+ ownerOfHiddenGroupUser = await Factory.build(
+ 'user',
+ {
+ id: 'owner-of-hidden-group',
+ name: 'Owner Of Hidden Group',
+ },
+ {
+ email: 'owner-of-hidden-group@example.org',
+ password: '1234',
+ },
+ )
+ // create groups
+ // public-group
+ authenticatedUser = await ownerOfClosedGroupUser.toJson()
+ await mutate({
mutation: createGroupMutation,
- variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] },
+ variables: {
+ id: 'closed-group',
+ name: 'Uninteresting Group',
+ about: 'We will change nothing!',
+ description: 'We love it like it is!?' + descriptionAdditional100,
+ groupType: 'closed',
+ actionRadius: 'national',
+ categoryIds,
+ },
+ })
+ authenticatedUser = await ownerOfHiddenGroupUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'hidden-group',
+ name: 'Investigative Journalism Group',
+ about: 'We will change all.',
+ description: 'We research …' + descriptionAdditional100,
+ groupType: 'hidden',
+ actionRadius: 'global',
+ categoryIds,
+ },
+ })
+ authenticatedUser = await user.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'public-group',
+ name: 'The Best Group',
+ about: 'We will change the world!',
+ description: 'Some description' + descriptionAdditional100,
+ groupType: 'public',
+ actionRadius: 'regional',
+ categoryIds,
+ },
+ })
+ })
+
+ describe('public group', () => {
+ describe('joined by "owner-of-closed-group"', () => {
+ it('has "usual" as membership role', async () => {
+ await expect(
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'public-group',
+ userId: 'owner-of-closed-group',
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ JoinGroup: {
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'usual',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('joined by its owner', () => {
+ describe('does not create additional "MEMBER_OF" relation and therefore', () => {
+ it('has still "owner" as membership role', async () => {
+ await expect(
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'public-group',
+ userId: 'current-user',
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ JoinGroup: {
+ id: 'current-user',
+ myRoleInGroup: 'owner',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+ })
+
+ describe('closed group', () => {
+ describe('joined by "current-user"', () => {
+ it('has "pending" as membership role', async () => {
+ await expect(
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'closed-group',
+ userId: 'current-user',
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ JoinGroup: {
+ id: 'current-user',
+ myRoleInGroup: 'pending',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('joined by its owner', () => {
+ describe('does not create additional "MEMBER_OF" relation and therefore', () => {
+ it('has still "owner" as membership role', async () => {
+ await expect(
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'closed-group',
+ userId: 'owner-of-closed-group',
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ JoinGroup: {
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'owner',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+ })
+
+ describe('hidden group', () => {
+ describe('joined by "owner-of-closed-group"', () => {
+ it('throws authorization error', async () => {
+ const { errors } = await query({
+ query: joinGroupMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'owner-of-closed-group',
+ },
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('joined by its owner', () => {
+ describe('does not create additional "MEMBER_OF" relation and therefore', () => {
+ it('has still "owner" as membership role', async () => {
+ await expect(
+ mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'owner-of-hidden-group',
+ },
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ JoinGroup: {
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'owner',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+ })
+ })
+ })
+
+ describe('GroupMembers', () => {
+ beforeAll(async () => {
+ await seedBasicsAndClearAuthentication()
+ })
+
+ afterAll(async () => {
+ await cleanDatabase()
+ })
+
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
+ variables = {
+ id: 'not-existing-group',
+ }
+ const { errors } = await query({ query: groupMembersQuery, variables })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('authenticated', () => {
+ let otherUser
+ let pendingUser
+ let ownerOfClosedGroupUser
+ let ownerOfHiddenGroupUser
+
+ beforeAll(async () => {
+ // create users
+ otherUser = await Factory.build(
+ 'user',
+ {
+ id: 'other-user',
+ name: 'Other TestUser',
+ },
+ {
+ email: 'other-user@example.org',
+ password: '1234',
+ },
+ )
+ pendingUser = await Factory.build(
+ 'user',
+ {
+ id: 'pending-user',
+ name: 'Pending TestUser',
+ },
+ {
+ email: 'pending@example.org',
+ password: '1234',
+ },
+ )
+ ownerOfClosedGroupUser = await Factory.build(
+ 'user',
+ {
+ id: 'owner-of-closed-group',
+ name: 'Owner Of Closed Group',
+ },
+ {
+ email: 'owner-of-closed-group@example.org',
+ password: '1234',
+ },
+ )
+ ownerOfHiddenGroupUser = await Factory.build(
+ 'user',
+ {
+ id: 'owner-of-hidden-group',
+ name: 'Owner Of Hidden Group',
+ },
+ {
+ email: 'owner-of-hidden-group@example.org',
+ password: '1234',
+ },
+ )
+ // create groups
+ // public-group
+ authenticatedUser = await user.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'public-group',
+ name: 'The Best Group',
+ about: 'We will change the world!',
+ description: 'Some description' + descriptionAdditional100,
+ groupType: 'public',
+ actionRadius: 'regional',
+ categoryIds,
+ },
+ })
+ await mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'public-group',
+ userId: 'owner-of-closed-group',
+ },
+ })
+ await mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'public-group',
+ userId: 'owner-of-hidden-group',
+ },
+ })
+ // closed-group
+ authenticatedUser = await ownerOfClosedGroupUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'closed-group',
+ name: 'Uninteresting Group',
+ about: 'We will change nothing!',
+ description: 'We love it like it is!?' + descriptionAdditional100,
+ groupType: 'closed',
+ actionRadius: 'national',
+ categoryIds,
+ },
+ })
+ await mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'closed-group',
+ userId: 'current-user',
+ },
+ })
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'closed-group',
+ userId: 'owner-of-hidden-group',
+ roleInGroup: 'usual',
+ },
+ })
+ // hidden-group
+ authenticatedUser = await ownerOfHiddenGroupUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'hidden-group',
+ name: 'Investigative Journalism Group',
+ about: 'We will change all.',
+ description: 'We research …' + descriptionAdditional100,
+ groupType: 'hidden',
+ actionRadius: 'global',
+ categoryIds,
+ },
+ })
+ // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'pending-user',
+ roleInGroup: 'pending',
+ },
+ })
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'current-user',
+ roleInGroup: 'usual',
+ },
+ })
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'owner-of-closed-group',
+ roleInGroup: 'admin',
+ },
+ })
+
+ authenticatedUser = null
+ })
+
+ describe('public group', () => {
+ beforeEach(async () => {
+ variables = {
+ id: 'public-group',
+ }
+ })
+
+ describe('query group members', () => {
+ describe('by owner "current-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'owner',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'usual',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'usual',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(3)
+ })
+ })
+
+ describe('by usual member "owner-of-closed-group"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerOfClosedGroupUser.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'owner',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'usual',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'usual',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(3)
+ })
+ })
+
+ describe('by none member "other-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await otherUser.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'owner',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'usual',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'usual',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(3)
+ })
+ })
+ })
+ })
+
+ describe('closed group', () => {
+ beforeEach(async () => {
+ variables = {
+ id: 'closed-group',
+ }
+ })
+
+ describe('query group members', () => {
+ describe('by owner "owner-of-closed-group"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerOfClosedGroupUser.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'pending',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'owner',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'usual',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(3)
+ })
+ })
+
+ describe('by usual member "owner-of-hidden-group"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerOfHiddenGroupUser.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'pending',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'owner',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'usual',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(3)
+ })
+ })
+
+ describe('by pending member "current-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await query({ query: groupMembersQuery, variables })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('by none member "other-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await otherUser.toJson()
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await query({ query: groupMembersQuery, variables })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+ })
+
+ describe('hidden group', () => {
+ beforeEach(async () => {
+ variables = {
+ id: 'hidden-group',
+ }
+ })
+
+ describe('query group members', () => {
+ describe('by owner "owner-of-hidden-group"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerOfHiddenGroupUser.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'pending-user',
+ myRoleInGroup: 'pending',
+ }),
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'usual',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'admin',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'owner',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(4)
+ })
+ })
+
+ describe('by usual member "current-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'pending-user',
+ myRoleInGroup: 'pending',
+ }),
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'usual',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'admin',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'owner',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(4)
+ })
+ })
+
+ describe('by admin member "owner-of-closed-group"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerOfClosedGroupUser.toJson()
+ })
+
+ it('finds all members', async () => {
+ const result = await mutate({
+ mutation: groupMembersQuery,
+ variables,
+ })
+ expect(result).toMatchObject({
+ data: {
+ GroupMembers: expect.arrayContaining([
+ expect.objectContaining({
+ id: 'pending-user',
+ myRoleInGroup: 'pending',
+ }),
+ expect.objectContaining({
+ id: 'current-user',
+ myRoleInGroup: 'usual',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-closed-group',
+ myRoleInGroup: 'admin',
+ }),
+ expect.objectContaining({
+ id: 'owner-of-hidden-group',
+ myRoleInGroup: 'owner',
+ }),
+ ]),
+ },
+ errors: undefined,
+ })
+ expect(result.data.GroupMembers.length).toBe(4)
+ })
+ })
+
+ describe('by pending member "pending-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await pendingUser.toJson()
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await query({ query: groupMembersQuery, variables })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('by none member "other-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await otherUser.toJson()
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await query({ query: groupMembersQuery, variables })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+ })
+ })
+ })
+
+ describe('ChangeGroupMemberRole', () => {
+ let pendingMemberUser
+ let usualMemberUser
+ let adminMemberUser
+ let ownerMemberUser
+ let secondOwnerMemberUser
+
+ beforeAll(async () => {
+ await seedBasicsAndClearAuthentication()
+ // create users
+ pendingMemberUser = await Factory.build(
+ 'user',
+ {
+ id: 'pending-member-user',
+ name: 'Pending Member TestUser',
+ },
+ {
+ email: 'pending-member-user@example.org',
+ password: '1234',
+ },
+ )
+ usualMemberUser = await Factory.build(
+ 'user',
+ {
+ id: 'usual-member-user',
+ name: 'Usual Member TestUser',
+ },
+ {
+ email: 'usual-member-user@example.org',
+ password: '1234',
+ },
+ )
+ adminMemberUser = await Factory.build(
+ 'user',
+ {
+ id: 'admin-member-user',
+ name: 'Admin Member TestUser',
+ },
+ {
+ email: 'admin-member-user@example.org',
+ password: '1234',
+ },
+ )
+ ownerMemberUser = await Factory.build(
+ 'user',
+ {
+ id: 'owner-member-user',
+ name: 'Owner Member TestUser',
+ },
+ {
+ email: 'owner-member-user@example.org',
+ password: '1234',
+ },
+ )
+ secondOwnerMemberUser = await Factory.build(
+ 'user',
+ {
+ id: 'second-owner-member-user',
+ name: 'Second Owner Member TestUser',
+ },
+ {
+ email: 'second-owner-member-user@example.org',
+ password: '1234',
+ },
+ )
+ // create groups
+ // public-group
+ authenticatedUser = await usualMemberUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'public-group',
+ name: 'The Best Group',
+ about: 'We will change the world!',
+ description: 'Some description' + descriptionAdditional100,
+ groupType: 'public',
+ actionRadius: 'regional',
+ categoryIds,
+ },
+ })
+ await mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'public-group',
+ userId: 'owner-of-closed-group',
+ },
+ })
+ await mutate({
+ mutation: joinGroupMutation,
+ variables: {
+ groupId: 'public-group',
+ userId: 'owner-of-hidden-group',
+ },
+ })
+ // closed-group
+ authenticatedUser = await ownerMemberUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'closed-group',
+ name: 'Uninteresting Group',
+ about: 'We will change nothing!',
+ description: 'We love it like it is!?' + descriptionAdditional100,
+ groupType: 'closed',
+ actionRadius: 'national',
+ categoryIds,
+ },
+ })
+ // hidden-group
+ authenticatedUser = await adminMemberUser.toJson()
+ await mutate({
+ mutation: createGroupMutation,
+ variables: {
+ id: 'hidden-group',
+ name: 'Investigative Journalism Group',
+ about: 'We will change all.',
+ description: 'We research …' + descriptionAdditional100,
+ groupType: 'hidden',
+ actionRadius: 'global',
+ categoryIds,
+ },
+ })
+ // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'admin-member-user',
+ roleInGroup: 'usual',
+ },
+ })
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'second-owner-member-user',
+ roleInGroup: 'usual',
+ },
+ })
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'admin-member-user',
+ roleInGroup: 'usual',
+ },
+ })
+ await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'hidden-group',
+ userId: 'second-owner-member-user',
+ roleInGroup: 'usual',
+ },
+ })
+
+ authenticatedUser = null
+ })
+
+ afterAll(async () => {
+ await cleanDatabase()
+ })
+
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables: {
+ groupId: 'not-existing-group',
+ userId: 'current-user',
+ roleInGroup: 'pending',
+ },
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('authenticated', () => {
+ describe('in all group types – here "closed-group" for example', () => {
+ beforeEach(async () => {
+ variables = {
+ groupId: 'closed-group',
+ }
+ })
+
+ describe('join the members and give them their prospective roles', () => {
+ describe('by owner "owner-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerMemberUser.toJson()
+ })
+
+ describe('for "usual-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'usual-member-user',
+ }
+ })
+
+ describe('as usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('has role usual', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'usual-member-user',
+ myRoleInGroup: 'usual',
+ },
+ },
+ errors: undefined,
+ })
+ })
+
+ // the GQL mutation needs this fields in the result for testing
+ it.todo('has "updatedAt" newer as "createdAt"')
+ })
+ })
+
+ describe('for "admin-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'admin-member-user',
+ }
+ })
+
+ describe('as admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('has role admin', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'admin-member-user',
+ myRoleInGroup: 'admin',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+
+ describe('for "second-owner-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'second-owner-member-user',
+ }
+ })
+
+ describe('as owner', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'owner',
+ }
+ })
+
+ it('has role owner', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'second-owner-member-user',
+ myRoleInGroup: 'owner',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+ })
+ })
+
+ describe('switch role', () => {
+ describe('of owner "owner-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'owner-member-user',
+ }
+ })
+
+ describe('by owner themself "owner-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerMemberUser.toJson()
+ })
+
+ describe('to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ // shall this be possible in the future?
+ // or shall only an owner who gave the second owner the owner role downgrade themself for savety?
+ // otherwise the first owner who downgrades the other one has the victory over the group!
+ describe('by second owner "second-owner-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await secondOwnerMemberUser.toJson()
+ })
+
+ describe('to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('to same role owner', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'owner',
+ }
+ })
+
+ it('has role owner still', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'owner-member-user',
+ myRoleInGroup: 'owner',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+
+ describe('by admin "admin-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await adminMemberUser.toJson()
+ })
+
+ describe('to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by usual member "usual-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await usualMemberUser.toJson()
+ })
+
+ describe('to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by still pending member "pending-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await pendingMemberUser.toJson()
+ })
+
+ describe('to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+ })
+
+ describe('of admin "admin-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'admin-member-user',
+ }
+ })
+
+ describe('by owner "owner-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerMemberUser.toJson()
+ })
+
+ describe('to owner', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'owner',
+ }
+ })
+
+ it('has role owner', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'admin-member-user',
+ myRoleInGroup: 'owner',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('back to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by usual member "usual-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await usualMemberUser.toJson()
+ })
+
+ describe('upgrade to owner', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'owner',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('degrade to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by still pending member "pending-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await pendingMemberUser.toJson()
+ })
+
+ describe('upgrade to owner', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'owner',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('degrade to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by none member "current-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ describe('upgrade to owner', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'owner',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('degrade to pending again', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'pending',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+ })
+
+ describe('of usual member "usual-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'usual-member-user',
+ }
+ })
+
+ describe('by owner "owner-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerMemberUser.toJson()
+ })
+
+ describe('to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('has role admin', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'usual-member-user',
+ myRoleInGroup: 'admin',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('back to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('has role usual again', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'usual-member-user',
+ myRoleInGroup: 'usual',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+
+ describe('by usual member "usual-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await usualMemberUser.toJson()
+ })
+
+ describe('upgrade to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('degrade to pending', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'pending',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by still pending member "pending-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await pendingMemberUser.toJson()
+ })
+
+ describe('upgrade to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('degrade to pending', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'pending',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by none member "current-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ describe('upgrade to admin', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'admin',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+
+ describe('degrade to pending again', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'pending',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+ })
+
+ describe('of still pending member "pending-member-user"', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ userId: 'pending-member-user',
+ }
+ })
+
+ describe('by owner "owner-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await ownerMemberUser.toJson()
+ })
+
+ describe('to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('has role usual', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'pending-member-user',
+ myRoleInGroup: 'usual',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+
+ describe('back to pending', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'pending',
+ }
+ })
+
+ it('has role usual again', async () => {
+ await expect(
+ mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ }),
+ ).resolves.toMatchObject({
+ data: {
+ ChangeGroupMemberRole: {
+ id: 'pending-member-user',
+ myRoleInGroup: 'pending',
+ },
+ },
+ errors: undefined,
+ })
+ })
+ })
+ })
+
+ describe('by usual member "usual-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await usualMemberUser.toJson()
+ })
+
+ describe('upgrade to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by still pending member "pending-member-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await pendingMemberUser.toJson()
+ })
+
+ describe('upgrade to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+
+ describe('by none member "current-user"', () => {
+ beforeEach(async () => {
+ authenticatedUser = await user.toJson()
+ })
+
+ describe('upgrade to usual', () => {
+ beforeEach(async () => {
+ variables = {
+ ...variables,
+ roleInGroup: 'usual',
+ }
+ })
+
+ it('throws authorization error', async () => {
+ const { errors } = await mutate({
+ mutation: changeGroupMemberRoleMutation,
+ variables,
+ })
+ expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
+ })
+ })
+ })
+ })
})
- expect(errors[0]).toHaveProperty('message', 'Too many categories!')
})
})
})
diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql
index 3165b4a44..e254e5086 100644
--- a/backend/src/schema/types/type/Group.gql
+++ b/backend/src/schema/types/type/Group.gql
@@ -59,7 +59,7 @@ input _GroupFilter {
type Query {
Group(
- isMember: Boolean # if 'undefined' or 'null' then all groups
+ isMember: Boolean # if 'undefined' or 'null' then get all groups
id: ID
name: String
slug: String
@@ -71,14 +71,21 @@ type Query {
first: Int
offset: Int
orderBy: [_GroupOrdering]
- filter: _GroupFilter
): [Group]
- AvailableGroupTypes: [GroupType]!
+ GroupMembers(
+ id: ID!
+ first: Int
+ offset: Int
+ orderBy: [_UserOrdering]
+ filter: _UserFilter
+ ): [User]
- AvailableGroupActionRadii: [GroupActionRadius]!
+ # AvailableGroupTypes: [GroupType]!
- AvailableGroupMemberRoles: [GroupMemberRole]!
+ # AvailableGroupActionRadii: [GroupActionRadius]!
+
+ # AvailableGroupMemberRoles: [GroupMemberRole]!
}
type Mutation {
@@ -95,15 +102,26 @@ type Mutation {
locationName: String
): Group
- UpdateGroup(
- id: ID!
- name: String
- slug: String
- avatar: ImageInput
- locationName: String
- about: String
- description: String
- ): Group
+ # UpdateGroup(
+ # id: ID!
+ # name: String
+ # slug: String
+ # avatar: ImageInput
+ # locationName: String
+ # about: String
+ # description: String
+ # ): Group
- DeleteGroup(id: ID!): Group
+ # DeleteGroup(id: ID!): Group
+
+ JoinGroup(
+ groupId: ID!
+ userId: ID!
+ ): User
+
+ ChangeGroupMemberRole(
+ groupId: ID!
+ userId: ID!
+ roleInGroup: GroupMemberRole!
+ ): User
}
diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql
index a25e51079..4219cd00e 100644
--- a/backend/src/schema/types/type/User.gql
+++ b/backend/src/schema/types/type/User.gql
@@ -114,6 +114,8 @@ type User {
badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
emotions: [EMOTED]
+
+ myRoleInGroup: GroupMemberRole
}