diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index 8a6ca380e..1f636d981 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -130,6 +130,7 @@ Factory.define('post') deleted: false, imageBlurred: false, imageAspectRatio: 1.333, + clickedCount: 0, }) .attr('pinned', ['pinned'], (pinned) => { // Convert false to null diff --git a/backend/src/middleware/userInteractions.js b/backend/src/middleware/userInteractions.js index 91dc7f80f..553aefe78 100644 --- a/backend/src/middleware/userInteractions.js +++ b/backend/src/middleware/userInteractions.js @@ -1,33 +1,33 @@ const createRelatedCypher = (relation) => ` MATCH (user:User { id: $currentUser}) -MATCH (post:Post { id: $postId})<-[r:${relation}]-(u:User) +MATCH (post:Post { id: $postId}) +OPTIONAL MATCH (post)<-[r:${relation}]-(u:User) WHERE NOT u.disabled AND NOT u.deleted WITH user, post, count(DISTINCT u) AS count MERGE (user)-[relation:${relation} { }]->(post) ON CREATE SET relation.count = 1, relation.createdAt = toString(datetime()), -post.clickCount = count + 1 +post.clickedCount = count + 1 ON MATCH SET relation.count = relation.count + 1, relation.updatedAt = toString(datetime()), -post.clickCount = count +post.clickedCount = count RETURN user, post, relation ` const setPostCounter = async (postId, relation, context) => { - const { user: currentUser } = context + const { + user: { id: currentUser }, + } = context const session = context.driver.session() try { await session.writeTransaction((txc) => { - return txc.run( - createRelatedCypher(relation), - { currentUser, postId }, - ) + return txc.run(createRelatedCypher(relation), { currentUser, postId }) }) } finally { session.close() - } + } } const userClickedPost = async (resolve, root, args, context, info) => { diff --git a/backend/src/middleware/userInteractions.spec.js b/backend/src/middleware/userInteractions.spec.js new file mode 100644 index 000000000..77c9fbd1d --- /dev/null +++ b/backend/src/middleware/userInteractions.spec.js @@ -0,0 +1,98 @@ +import Factory, { cleanDatabase } from '../db/factories' +import { gql } from '../helpers/jest' +import { getNeode, getDriver } from '../db/neo4j' +import createServer from '../server' +import { createTestClient } from 'apollo-server-testing' + +let query, aUser, bUser, post, authenticatedUser, variables + +const driver = getDriver() +const neode = getNeode() + +const postQuery = gql` + query($id: ID) { + Post(id: $id) { + clickedCount + } + } +` + +beforeAll(async () => { + await cleanDatabase() + aUser = await Factory.build('user', { + id: 'a-user', + }) + bUser = await Factory.build('user', { + id: 'b-user', + }) + post = await Factory.build('post') + authenticatedUser = await aUser.toJson() + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query +}) + +afterAll(async () => { + await cleanDatabase() +}) + +describe('middleware/userInteractions', () => { + describe('given one post', () => { + it('does not change clickedCount when queried without ID', async () => { + await expect(query({ query: postQuery, variables })).resolves.toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + clickedCount: 0, + }, + ]), + }, + }) + }) + + it('changes clickedCount when queried with ID', async () => { + variables = { id: post.get('id') } + await expect(query({ query: postQuery, variables })).resolves.toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + clickedCount: 1, + }, + ]), + }, + }) + }) + + it('does not change clickedCount when same user queries the post again', async () => { + await expect(query({ query: postQuery, variables })).resolves.toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + clickedCount: 1, + }, + ]), + }, + }) + }) + + it('changes clickedCount when another user queries the post', async () => { + authenticatedUser = await bUser.toJson() + await expect(query({ query: postQuery, variables })).resolves.toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + clickedCount: 2, + }, + ]), + }, + }) + }) + }) +}) diff --git a/backend/src/models/Post.js b/backend/src/models/Post.js index 43f63ebd3..42bf844e1 100644 --- a/backend/src/models/Post.js +++ b/backend/src/models/Post.js @@ -22,6 +22,7 @@ export default { contentExcerpt: { type: 'string', allow: [null] }, deleted: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false }, + clickedCount: { type: 'int', default: 0 }, notified: { type: 'relationship', relationship: 'NOTIFIED', diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index a02eb599d..d0452bc78 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -88,6 +88,7 @@ export default { SET post += $params SET post.createdAt = toString(datetime()) SET post.updatedAt = toString(datetime()) + SET post.clickedCount = 0 WITH post MATCH (author:User {id: $userId}) MERGE (post)<-[:WROTE]-(author) diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index f7f5aa69f..99e1e9e59 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -157,9 +157,7 @@ type Post { ) clickedCount: Int! - @cypher( - statement: "MATCH (this)<-[:CLICKED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)" - ) + emotions: [EMOTED] emotionsCount: Int! @cypher(statement: "MATCH (this)<-[emoted:EMOTED]-(:User) RETURN COUNT(DISTINCT emoted)")