Implement 'EnterGroup' resolver

This commit is contained in:
Wolfgang Huß 2022-08-18 10:12:11 +02:00
parent 27b74eb9e1
commit 25ed30dba1
5 changed files with 423 additions and 129 deletions

View File

@ -39,6 +39,17 @@ export const createGroupMutation = gql`
}
`
export const enterGroupMutation = gql`
mutation ($id: ID!, $userId: ID!) {
EnterGroup(id: $id, userId: $userId) {
id
name
slug
myRoleInGroup
}
}
`
// ------ queries
export const groupQuery = gql`
@ -93,3 +104,14 @@ export const groupQuery = gql`
}
}
`
export const groupMemberQuery = gql`
query ($id: ID!, $first: Int, $offset: Int, $orderBy: [_UserOrdering], $filter: _UserFilter) {
GroupMember(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) {
id
name
slug
myRoleInGroup
}
}
`

View File

@ -62,9 +62,9 @@ const isAllowSeeingMembersOfGroup = rule({
const transactionResponse = await transaction.run(
`
MATCH (group:Group {id: $groupId})
OPTIONAL MATCH (admin {id:User $userId})-[membership:MEMBER_OF]->(group)
OPTIONAL MATCH (admin:User {id: $userId})-[membership:MEMBER_OF]->(group)
WHERE membership.role IN ['admin', 'owner']
RETURN group, admin
RETURN group, admin {.*, myRoleInGroup: membership.role}
`,
{ groupId, userId: user.id },
)
@ -174,6 +174,7 @@ export default shield(
SignupVerification: allow,
UpdateUser: onlyYourself,
CreateGroup: isAuthenticated,
EnterGroup: isAuthenticated,
CreatePost: isAuthenticated,
UpdatePost: isAuthor,
DeletePost: isAuthor,

View File

@ -109,7 +109,7 @@ export default {
MERGE (owner)-[:CREATED]->(group)
MERGE (owner)-[membership:MEMBER_OF]->(group)
SET membership.createdAt = toString(datetime())
SET membership.updatedAt = toString(datetime())
SET membership.updatedAt = membership.createdAt
SET membership.role = 'owner'
${categoriesCypher}
RETURN group {.*, myRole: membership.role}
@ -122,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!')
@ -132,6 +131,35 @@ export default {
session.close()
}
},
EnterGroup: async (_parent, params, context, _resolveInfo) => {
const { id: groupId, userId } = params
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const enterGroupCypher = `
MATCH (member:User {id: $userId}), (group:Group {id: $groupId})
MERGE (member)-[membership:MEMBER_OF]->(group)
ON CREATE SET
membership.createdAt = toString(datetime()),
membership.updatedAt = membership.createdAt,
membership.role =
CASE WHEN group.groupType = 'public'
THEN 'usual'
ELSE 'pending'
END
RETURN member {.*, myRoleInGroup: membership.role}
`
const result = await transaction.run(enterGroupCypher, { 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()
}
},
},
Group: {
...Resolver('Group', {

View File

@ -1,6 +1,11 @@
import { createTestClient } from 'apollo-server-testing'
import Factory, { cleanDatabase } from '../../db/factories'
import { createGroupMutation, groupQuery } from '../../db/graphql/groups'
import {
createGroupMutation,
enterGroupMutation,
groupMemberQuery,
groupQuery,
} from '../../db/graphql/groups'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
import CONFIG from '../../config'
@ -94,10 +99,6 @@ describe('Group', () => {
})
describe('authenticated', () => {
beforeEach(async () => {
authenticatedUser = await user.toJson()
})
let otherUser
beforeEach(async () => {
@ -207,127 +208,127 @@ describe('Group', () => {
})
})
describe('GroupMember', () => {
describe('unauthenticated', () => {
it('throws authorization error', async () => {
const { errors } = await query({ query: groupQuery, variables: {} })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
})
})
// describe('GroupMember', () => {
// describe('unauthenticated', () => {
// it('throws authorization error', async () => {
// const { errors } = await query({ query: groupMemberQuery, variables: {} })
// expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
// })
// })
describe('authenticated', () => {
beforeEach(async () => {
authenticatedUser = await user.toJson()
})
// describe('authenticated', () => {
// beforeEach(async () => {
// authenticatedUser = await user.toJson()
// })
let otherUser
// let otherUser
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',
name: 'The Best Group',
about: 'We will change the world!',
description: 'Some description' + descriptionAdditional100,
groupType: 'public',
actionRadius: 'regional',
categoryIds,
},
})
})
// 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',
// name: 'The Best Group',
// about: 'We will change the world!',
// description: 'Some description' + descriptionAdditional100,
// groupType: 'public',
// actionRadius: 'regional',
// categoryIds,
// },
// })
// })
describe('query group members', () => {
describe('by owner', () => {
it.only('finds all members', async () => {
const expected = {
data: {
GroupMember: expect.arrayContaining([
expect.objectContaining({
id: 'my-group',
slug: 'the-best-group',
myRole: 'owner',
}),
// Wolle: expect.objectContaining({
// id: 'others-group',
// slug: 'uninteresting-group',
// myRole: null,
// }),
]),
},
errors: undefined,
}
await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected)
})
})
// describe('query group members', () => {
// describe('by owner', () => {
// it.only('finds all members', async () => {
// const expected = {
// data: {
// GroupMember: expect.arrayContaining([
// expect.objectContaining({
// id: 'my-group',
// slug: 'the-best-group',
// myRole: 'owner',
// }),
// // Wolle: expect.objectContaining({
// // id: 'others-group',
// // slug: 'uninteresting-group',
// // myRole: null,
// // }),
// ]),
// },
// errors: undefined,
// }
// await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected)
// })
// })
describe('isMember = true', () => {
it('finds only groups where user is member', async () => {
const expected = {
data: {
Group: [
{
id: 'my-group',
slug: 'the-best-group',
myRole: 'owner',
},
],
},
errors: undefined,
}
await expect(
query({ query: groupQuery, variables: { isMember: true } }),
).resolves.toMatchObject(expected)
})
})
// describe('isMember = true', () => {
// it('finds only groups where user is member', async () => {
// const expected = {
// data: {
// Group: [
// {
// id: 'my-group',
// slug: '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('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(() => {
@ -440,3 +441,241 @@ describe('CreateGroup', () => {
})
})
})
describe('EnterGroup', () => {
describe('unauthenticated', () => {
it('throws authorization error', async () => {
variables = {
id: 'not-existing-group',
userId: 'current-user',
}
const { errors } = await mutate({ mutation: enterGroupMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!')
})
})
describe('authenticated', () => {
let ownerOfClosedGroupUser
let ownerOfHiddenGroupUser
beforeEach(async () => {
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',
},
)
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,
},
})
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('entered by "owner-of-closed-group"', () => {
it('has "usual" as membership role', async () => {
variables = {
id: 'public-group',
userId: 'owner-of-closed-group',
}
const expected = {
data: {
EnterGroup: {
id: 'owner-of-closed-group',
myRoleInGroup: 'usual',
},
},
errors: undefined,
}
await expect(
mutate({
mutation: enterGroupMutation,
variables,
}),
).resolves.toMatchObject(expected)
})
})
describe('entered by its owner', () => {
describe('does not create additional "MEMBER_OF" relation and therefore', () => {
it('has still "owner" as membership role', async () => {
variables = {
id: 'public-group',
userId: 'current-user',
}
const expected = {
data: {
EnterGroup: {
id: 'current-user',
myRoleInGroup: 'owner',
},
},
errors: undefined,
}
await expect(
mutate({
mutation: enterGroupMutation,
variables,
}),
).resolves.toMatchObject(expected)
})
})
})
})
describe('closed group', () => {
describe('entered by "current-user"', () => {
it('has "pending" as membership role', async () => {
variables = {
id: 'closed-group',
userId: 'current-user',
}
const expected = {
data: {
EnterGroup: {
id: 'current-user',
myRoleInGroup: 'pending',
},
},
errors: undefined,
}
await expect(
mutate({
mutation: enterGroupMutation,
variables,
}),
).resolves.toMatchObject(expected)
})
})
describe('entered by its owner', () => {
describe('does not create additional "MEMBER_OF" relation and therefore', () => {
it('has still "owner" as membership role', async () => {
variables = {
id: 'closed-group',
userId: 'owner-of-closed-group',
}
const expected = {
data: {
EnterGroup: {
id: 'owner-of-closed-group',
myRoleInGroup: 'owner',
},
},
errors: undefined,
}
await expect(
mutate({
mutation: enterGroupMutation,
variables,
}),
).resolves.toMatchObject(expected)
})
})
})
})
describe('hidden group', () => {
describe('entered by "owner-of-closed-group"', () => {
it('has "pending" as membership role', async () => {
variables = {
id: 'hidden-group',
userId: 'owner-of-closed-group',
}
const expected = {
data: {
EnterGroup: {
id: 'owner-of-closed-group',
myRoleInGroup: 'pending',
},
},
errors: undefined,
}
await expect(
mutate({
mutation: enterGroupMutation,
variables,
}),
).resolves.toMatchObject(expected)
})
})
describe('entered by its owner', () => {
describe('does not create additional "MEMBER_OF" relation and therefore', () => {
it('has still "owner" as membership role', async () => {
variables = {
id: 'hidden-group',
userId: 'owner-of-hidden-group',
}
const expected = {
data: {
EnterGroup: {
id: 'owner-of-hidden-group',
myRoleInGroup: 'owner',
},
},
errors: undefined,
}
await expect(
mutate({
mutation: enterGroupMutation,
variables,
}),
).resolves.toMatchObject(expected)
})
})
})
})
})
})

View File

@ -71,15 +71,14 @@ type Query {
first: Int
offset: Int
orderBy: [_GroupOrdering]
filter: _GroupFilter
): [Group]
GroupMember(
id: ID
id: ID!
first: Int
offset: Int
orderBy: [_GroupOrdering]
filter: _GroupFilter
orderBy: [_UserOrdering]
filter: _UserFilter
): [User]
AvailableGroupTypes: [GroupType]!
@ -114,4 +113,9 @@ type Mutation {
): Group
DeleteGroup(id: ID!): Group
EnterGroup(
id: ID!
userId: ID!
): User
}