diff --git a/backend/src/schema/resolvers/follow.js b/backend/src/schema/resolvers/follow.js index 730a66cfd..f0a98e1f7 100644 --- a/backend/src/schema/resolvers/follow.js +++ b/backend/src/schema/resolvers/follow.js @@ -1,51 +1,44 @@ +import { neode as getNeode } from '../../bootstrap/neo4j' + +const neode = getNeode() + export default { Mutation: { follow: async (_object, params, context, _resolveInfo) => { - const { id, type } = params + const { id: followedId, type } = params + const { user: currentUser } = context - const session = context.driver.session() - const transactionRes = await session.run( - `MATCH (node {id: $id}), (user:User {id: $userId}) - WHERE $type IN labels(node) AND NOT $id = $userId - MERGE (user)-[relation:FOLLOWS]->(node) - RETURN COUNT(relation) > 0 as isFollowed`, - { - id, - type, - userId: context.user.id, - }, - ) + if (type === 'User' && currentUser.id === followedId) { + return null + } - const [isFollowed] = transactionRes.records.map(record => { - return record.get('isFollowed') - }) - - session.close() - - return isFollowed + const [user, followedNode] = await Promise.all([ + neode.find('User', currentUser.id), + neode.find(type, followedId), + ]) + await user.relateTo(followedNode, 'following') + return followedNode.toJson() }, unfollow: async (_object, params, context, _resolveInfo) => { - const { id, type } = params - const session = context.driver.session() + const { id: followedId, type } = params + const { user: currentUser } = context - const transactionRes = await session.run( - `MATCH (user:User {id: $userId})-[relation:FOLLOWS]->(node {id: $id}) + /* + * Note: Neode doesn't provide an easy method for retrieving or removing relationships. + * It's suggested to use query builder feature (https://github.com/adam-cowley/neode/issues/67) + * 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) DELETE relation RETURN COUNT(relation) > 0 as isFollowed`, - { - id, - type, - userId: context.user.id, - }, + { followedId, type, currentUser }, ) - const [isFollowed] = transactionRes.records.map(record => { - return record.get('isFollowed') - }) - session.close() - return isFollowed + const followedNode = await neode.find(type, followedId) + return followedNode.toJson() }, }, } diff --git a/backend/src/schema/resolvers/follow.spec.js b/backend/src/schema/resolvers/follow.spec.js index 2f3f1f4ad..1aa146075 100644 --- a/backend/src/schema/resolvers/follow.spec.js +++ b/backend/src/schema/resolvers/follow.spec.js @@ -17,13 +17,27 @@ let variables const mutationFollowUser = gql` mutation($id: ID!, $type: FollowTypeEnum) { - follow(id: $id, type: $type) + follow(id: $id, type: $type) { + name + followedBy { + id + name + } + followedByCurrentUser + } } ` const mutationUnfollowUser = gql` mutation($id: ID!, $type: FollowTypeEnum) { - unfollow(id: $id, type: $type) + unfollow(id: $id, type: $type) { + name + followedBy { + id + name + } + followedByCurrentUser + } } ` @@ -58,6 +72,7 @@ beforeEach(async () => { user1 = await factory .create('User', { id: 'u1', + name: 'user1', email: 'test@example.org', password: '1234', }) @@ -65,6 +80,7 @@ beforeEach(async () => { user2 = await factory .create('User', { id: 'u2', + name: 'user2', email: 'test2@example.org', password: '1234', }) @@ -81,39 +97,34 @@ afterEach(async () => { describe('follow', () => { describe('follow user', () => { describe('unauthenticated follow', () => { - it('throws authorization error', async () => { + test('throws authorization error', async () => { authenticatedUser = null - const { errors } = await mutate({ + const { errors, data } = await mutate({ mutation: mutationFollowUser, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(data).toMatchObject({ follow: null }) }) }) - it('I can follow another user', async () => { + test('I can follow another user', async () => { const { data: result } = await mutate({ mutation: mutationFollowUser, variables, }) - const expectedResult = { follow: true } - expect(result).toMatchObject(expectedResult) - - const { data } = await query({ - query: userQuery, - variables: { id: user2.id }, - }) const expectedUser = { - followedBy: [{ id: user1.id }], + name: user2.name, + followedBy: [{ id: user1.id, name: user1.name }], followedByCurrentUser: true, } - expect(data).toMatchObject({ User: [expectedUser] }) + expect(result).toMatchObject({ follow: expectedUser }) }) - it('I can`t follow myself', async () => { + test('I can`t follow myself', async () => { variables.id = user1.id const { data: result } = await mutate({ mutation: mutationFollowUser, variables }) - const expectedResult = { follow: false } + const expectedResult = { follow: null } expect(result).toMatchObject(expectedResult) const { data } = await query({ @@ -137,27 +148,22 @@ describe('follow', () => { }) describe('unauthenticated follow', () => { - it('throws authorization error', async () => { + test('throws authorization error', async () => { authenticatedUser = null - const { errors } = await mutate({ mutation: mutationUnfollowUser, variables }) + const { errors, data } = await mutate({ mutation: mutationUnfollowUser, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(data).toMatchObject({ unfollow: null }) }) }) it('I can unfollow a user', async () => { const { data: result } = await mutate({ mutation: mutationUnfollowUser, variables }) - const expectedResult = { unfollow: true } - expect(result).toMatchObject(expectedResult) - - const { data } = await query({ - query: userQuery, - variables: { id: user2.id }, - }) const expectedUser = { + name: user2.name, followedBy: [], followedByCurrentUser: false, } - expect(data).toMatchObject({ User: [expectedUser] }) + expect(result).toMatchObject({ unfollow: expectedUser }) }) }) }) diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index c641763f0..620039b28 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -32,9 +32,9 @@ type Mutation { # Unshout the given Type and ID unshout(id: ID!, type: ShoutTypeEnum): Boolean! # Follow the given Type and ID - follow(id: ID!, type: FollowTypeEnum): Boolean! + follow(id: ID!, type: FollowTypeEnum): User # Unfollow the given Type and ID - unfollow(id: ID!, type: FollowTypeEnum): Boolean! + unfollow(id: ID!, type: FollowTypeEnum): User } type Report {