diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 116794fda..ce98090ad 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -152,8 +152,8 @@ const permissions = shield( // RemoveBadgeRewarded: isAdmin, reward: isAdmin, unreward: isAdmin, - follow: isAuthenticated, - unfollow: isAuthenticated, + followUser: isAuthenticated, + unfollowUser: isAuthenticated, shout: isAuthenticated, unshout: isAuthenticated, changePassword: isAuthenticated, diff --git a/backend/src/schema/resolvers/follow.js b/backend/src/schema/resolvers/follow.js index f0a98e1f7..ada417cff 100644 --- a/backend/src/schema/resolvers/follow.js +++ b/backend/src/schema/resolvers/follow.js @@ -4,24 +4,24 @@ const neode = getNeode() export default { Mutation: { - follow: async (_object, params, context, _resolveInfo) => { - const { id: followedId, type } = params + followUser: async (_object, params, context, _resolveInfo) => { + const { id: followedUserId } = params const { user: currentUser } = context - if (type === 'User' && currentUser.id === followedId) { + if (currentUser.id === followedUserId) { return null } - const [user, followedNode] = await Promise.all([ + const [user, followedUser] = await Promise.all([ neode.find('User', currentUser.id), - neode.find(type, followedId), + neode.find('User', followedUserId), ]) - await user.relateTo(followedNode, 'following') - return followedNode.toJson() + await user.relateTo(followedUser, 'following') + return followedUser.toJson() }, - unfollow: async (_object, params, context, _resolveInfo) => { - const { id: followedId, type } = params + unfollowUser: async (_object, params, context, _resolveInfo) => { + const { id: followedUserId } = params const { user: currentUser } = context /* @@ -30,15 +30,14 @@ export default { * However, pure cypher query looks cleaner IMO */ await neode.cypher( - `MATCH (user:User {id: $currentUser.id})-[relation:FOLLOWS]->(node {id: $followedId}) - WHERE $type IN labels(node) + `MATCH (user:User {id: $currentUser.id})-[relation:FOLLOWS]->(followedUser:User {id: $followedUserId}) DELETE relation RETURN COUNT(relation) > 0 as isFollowed`, - { followedId, type, currentUser }, + { followedUserId, currentUser }, ) - const followedNode = await neode.find(type, followedId) - return followedNode.toJson() + const followedUser = await neode.find('User', followedUserId) + return followedUser.toJson() }, }, } diff --git a/backend/src/schema/resolvers/follow.spec.js b/backend/src/schema/resolvers/follow.spec.js index 1aa146075..7b801d377 100644 --- a/backend/src/schema/resolvers/follow.spec.js +++ b/backend/src/schema/resolvers/follow.spec.js @@ -1,11 +1,12 @@ import { createTestClient } from 'apollo-server-testing' import Factory from '../../seed/factories' -import { getDriver } from '../../bootstrap/neo4j' +import { getDriver, neode as getNeode } from '../../bootstrap/neo4j' import createServer from '../../server' import { gql } from '../../jest/helpers' const factory = Factory() const driver = getDriver() +const neode = getNeode() let query let mutate @@ -16,8 +17,8 @@ let user2 let variables const mutationFollowUser = gql` - mutation($id: ID!, $type: FollowTypeEnum) { - follow(id: $id, type: $type) { + mutation($id: ID!) { + followUser(id: $id) { name followedBy { id @@ -29,8 +30,8 @@ const mutationFollowUser = gql` ` const mutationUnfollowUser = gql` - mutation($id: ID!, $type: FollowTypeEnum) { - unfollow(id: $id, type: $type) { + mutation($id: ID!) { + unfollowUser(id: $id) { name followedBy { id @@ -52,10 +53,12 @@ const userQuery = gql` } ` -beforeAll(() => { +beforeAll(async () => { + await factory.cleanDatabase() const { server } = createServer({ context: () => ({ driver, + neode, user: authenticatedUser, cypherParams: { currentUserId: authenticatedUser ? authenticatedUser.id : null, @@ -87,7 +90,7 @@ beforeEach(async () => { .then(user => user.toJson()) authenticatedUser = user1 - variables = { id: user2.id, type: 'User' } + variables = { id: user2.id } }) afterEach(async () => { @@ -99,71 +102,85 @@ describe('follow', () => { describe('unauthenticated follow', () => { test('throws authorization error', async () => { authenticatedUser = null - const { errors, data } = await mutate({ - mutation: mutationFollowUser, - variables, + await expect( + mutate({ + mutation: mutationFollowUser, + variables, + }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { followUser: null }, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - expect(data).toMatchObject({ follow: null }) }) }) test('I can follow another user', async () => { - const { data: result } = await mutate({ - mutation: mutationFollowUser, - variables, - }) const expectedUser = { name: user2.name, followedBy: [{ id: user1.id, name: user1.name }], followedByCurrentUser: true, } - expect(result).toMatchObject({ follow: expectedUser }) + await expect( + mutate({ + mutation: mutationFollowUser, + variables, + }), + ).resolves.toMatchObject({ + data: { followUser: expectedUser }, + errors: undefined, + }) }) test('I can`t follow myself', async () => { variables.id = user1.id - const { data: result } = await mutate({ mutation: mutationFollowUser, variables }) - const expectedResult = { follow: null } - expect(result).toMatchObject(expectedResult) - - const { data } = await query({ - query: userQuery, - variables: { id: user1.id }, + await expect(mutate({ mutation: mutationFollowUser, variables })).resolves.toMatchObject({ + data: { followUser: null }, + errors: undefined, }) + const expectedUser = { followedBy: [], followedByCurrentUser: false, } - expect(data).toMatchObject({ User: [expectedUser] }) + await expect( + query({ + query: userQuery, + variables: { id: user1.id }, + }), + ).resolves.toMatchObject({ + data: { + User: [expectedUser], + }, + errors: undefined, + }) }) }) describe('unfollow user', () => { beforeEach(async () => { - variables = { - id: user2.id, - type: 'User', - } + variables = { id: user2.id } await mutate({ mutation: mutationFollowUser, variables }) }) describe('unauthenticated follow', () => { test('throws authorization error', async () => { authenticatedUser = null - const { errors, data } = await mutate({ mutation: mutationUnfollowUser, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - expect(data).toMatchObject({ unfollow: null }) + await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ + data: { unfollowUser: null }, + errors: [{ message: 'Not Authorised!' }], + }) }) }) - it('I can unfollow a user', async () => { - const { data: result } = await mutate({ mutation: mutationUnfollowUser, variables }) + test('I can unfollow a user', async () => { const expectedUser = { name: user2.name, followedBy: [], followedByCurrentUser: false, } - expect(result).toMatchObject({ unfollow: expectedUser }) + await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ + data: { unfollowUser: expectedUser }, + errors: undefined, + }) }) }) }) diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 620039b28..06d0e061b 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -31,10 +31,8 @@ type Mutation { shout(id: ID!, type: ShoutTypeEnum): Boolean! # Unshout the given Type and ID unshout(id: ID!, type: ShoutTypeEnum): Boolean! - # Follow the given Type and ID - follow(id: ID!, type: FollowTypeEnum): User - # Unfollow the given Type and ID - unfollow(id: ID!, type: FollowTypeEnum): User + followUser(id: ID!): User + unfollowUser(id: ID!): User } type Report { @@ -57,9 +55,6 @@ enum Deletable { enum ShoutTypeEnum { Post } -enum FollowTypeEnum { - User -} type Reward { id: ID! diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index c0a684715..ab09b438d 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -118,13 +118,12 @@ export default function Factory(options = {}) { this.lastResponse = await this.graphQLClient.request(mutation) return this }, - async follow(properties) { - const { id, type } = properties + async followUser(properties) { + const { id } = properties const mutation = ` mutation { - follow( - id: "${id}", - type: ${type} + followUser( + id: "${id}" ) } ` @@ -166,7 +165,7 @@ export default function Factory(options = {}) { result.relate.bind(result) result.mutate.bind(result) result.shout.bind(result) - result.follow.bind(result) + result.followUser.bind(result) result.invite.bind(result) result.cleanDatabase.bind(result) return result diff --git a/webapp/components/FollowButton.vue b/webapp/components/FollowButton.vue index 5275035e5..e1cad8c8e 100644 --- a/webapp/components/FollowButton.vue +++ b/webapp/components/FollowButton.vue @@ -73,7 +73,7 @@ export default { variables: { id: this.followId }, }) - const followedUser = follow ? data.follow : data.unfollow + const followedUser = follow ? data.followUser : data.unfollowUser this.$emit('update', followedUser) } catch { optimisticResult.followedByCurrentUser = !follow diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index 11149f398..27b3785ae 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -106,7 +106,7 @@ export const followUserMutation = i18n => { return gql` ${userFragment(lang)} mutation($id: ID!) { - follow(id: $id, type: User) { + followUser(id: $id) { name followedByCount followedByCurrentUser @@ -123,7 +123,7 @@ export const unfollowUserMutation = i18n => { return gql` ${userFragment(lang)} mutation($id: ID!) { - unfollow(id: $id, type: User) { + unfollowUser(id: $id) { name followedByCount followedByCurrentUser