Merge pull request #201 from Human-Connection/distinct-shouts-and-follows

Fix follow and shout status
This commit is contained in:
Grzegorz Leoniec 2019-03-08 15:33:37 +01:00 committed by GitHub
commit e6d0948896
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 353 additions and 13 deletions

View File

@ -56,7 +56,7 @@
"lodash": "~4.17.11",
"ms": "~2.1.1",
"neo4j-driver": "~1.7.2",
"neo4j-graphql-js": "~2.3.1",
"neo4j-graphql-js": "~2.4.1",
"node-fetch": "~2.3.0",
"npm-run-all": "~4.1.5",
"sanitize-html": "~1.20.0",

View File

@ -0,0 +1,115 @@
import Factory from '../seed/factories'
import { GraphQLClient } from 'graphql-request'
import { host, login } from '../jest/helpers'
const factory = Factory()
let clientUser1
const mutationFollowUser = (id) => `
mutation {
follow(id: "${id}", type: User)
}
`
const mutationUnfollowUser = (id) => `
mutation {
unfollow(id: "${id}", type: User)
}
`
beforeEach(async () => {
await factory.create('User', {
id: 'u1',
email: 'test@example.org',
password: '1234'
})
await factory.create('User', {
id: 'u2',
email: 'test2@example.org',
password: '1234'
})
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('follow ', () => {
describe('(un)follow user', () => {
let headersUser1
beforeEach(async () => {
headersUser1 = await login({ email: 'test@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
})
it('I can follow another user', async () => {
const res = await clientUser1.request(
mutationFollowUser('u2')
)
const expected = {
follow: true
}
expect(res).toMatchObject(expected)
const { User } = await clientUser1.request(`{
User(id: "u2") {
followedBy { id }
followedByCurrentUser
}
}`)
const expected2 = {
followedBy: [
{ id: 'u1' }
],
followedByCurrentUser: true
}
expect(User[0]).toMatchObject(expected2)
})
it('I can unfollow a user', async () => {
// follow
await clientUser1.request(
mutationFollowUser('u2')
)
const expected = {
unfollow: true
}
// unfollow
const res = await clientUser1.request(mutationUnfollowUser('u2'))
expect(res).toMatchObject(expected)
const { User } = await clientUser1.request(`{
User(id: "u2") {
followedBy { id }
followedByCurrentUser
}
}`)
const expected2 = {
followedBy: [],
followedByCurrentUser: false
}
expect(User[0]).toMatchObject(expected2)
})
it('I can`t follow myself', async () => {
const res = await clientUser1.request(
mutationFollowUser('u1')
)
const expected = {
follow: false
}
expect(res).toMatchObject(expected)
const { User } = await clientUser1.request(`{
User(id: "u1") {
followedBy { id }
followedByCurrentUser
}
}`)
const expected2 = {
followedBy: [],
followedByCurrentUser: false
}
expect(User[0]).toMatchObject(expected2)
})
})
})

126
src/resolvers/shout.spec.js Normal file
View File

@ -0,0 +1,126 @@
import Factory from '../seed/factories'
import { GraphQLClient } from 'graphql-request'
import { host, login } from '../jest/helpers'
const factory = Factory()
let clientUser1, clientUser2
const mutationShoutPost = (id) => `
mutation {
shout(id: "${id}", type: Post)
}
`
const mutationUnshoutPost = (id) => `
mutation {
unshout(id: "${id}", type: Post)
}
`
beforeEach(async () => {
await factory.create('User', {
id: 'u1',
email: 'test@example.org',
password: '1234'
})
await factory.create('User', {
id: 'u2',
email: 'test2@example.org',
password: '1234'
})
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('shout ', () => {
describe('(un)shout foreign post', () => {
let headersUser1, headersUser2
beforeEach(async () => {
headersUser1 = await login({ email: 'test@example.org', password: '1234' })
headersUser2 = await login({ email: 'test2@example.org', password: '1234' })
clientUser1 = new GraphQLClient(host, { headers: headersUser1 })
clientUser2 = new GraphQLClient(host, { headers: headersUser2 })
await clientUser1.request(`
mutation {
CreatePost(id: "p1", title: "Post Title 1", content: "Some Post Content 1") {
id
title
}
}
`)
await clientUser2.request(`
mutation {
CreatePost(id: "p2", title: "Post Title 2", content: "Some Post Content 2") {
id
title
}
}
`)
})
it('I shout a post of another user', async () => {
const res = await clientUser1.request(
mutationShoutPost('p2')
)
const expected = {
shout: true
}
expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{
Post(id: "p2") {
shoutedByCurrentUser
}
}`)
const expected2 = {
shoutedByCurrentUser: true
}
expect(Post[0]).toMatchObject(expected2)
})
it('I unshout a post of another user', async () => {
// shout
await clientUser1.request(
mutationShoutPost('p2')
)
const expected = {
unshout: true
}
// unshout
const res = await clientUser1.request(mutationUnshoutPost('p2'))
expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{
Post(id: "p2") {
shoutedByCurrentUser
}
}`)
const expected2 = {
shoutedByCurrentUser: false
}
expect(Post[0]).toMatchObject(expected2)
})
it('I can`t shout my own post', async () => {
const res = await clientUser1.request(
mutationShoutPost('p1')
)
const expected = {
shout: false
}
expect(res).toMatchObject(expected)
const { Post } = await clientUser1.request(`{
Post(id: "p1") {
shoutedByCurrentUser
}
}`)
const expected2 = {
shoutedByCurrentUser: false
}
expect(Post[0]).toMatchObject(expected2)
})
})
})

View File

@ -1,12 +1,44 @@
type Query {
isLoggedIn: Boolean!
"Get the currently logged in User based on the given JWT Token"
currentUser: User
"Get the latest Network Statistics"
statistics: Statistics!
}
type Mutation {
"Get a JWT Token for the given Email and password"
login(email: String!, password: String!): String!
signup(email: String!, password: String!): Boolean!
report(resource: Resource!, description: String): Report
"Shout the given Type and ID"
shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """
MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId})
WHERE $type IN labels(n) AND NOT wu.id = $cypherParams.currentUserId
MERGE (u)-[r:SHOUTED]->(n)
RETURN COUNT(r) > 0
""")
"Unshout the given Type and ID"
unshout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """
MATCH (:User {id: $cypherParams.currentUserId})-[r:SHOUTED]->(n {id: $id})
WHERE $type IN labels(n)
DELETE r
RETURN COUNT(r) > 0
""")
"Follow the given Type and ID"
follow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
MATCH (n {id: $id}), (u:User {id: $cypherParams.currentUserId})
WHERE $type IN labels(n) AND NOT $id = $cypherParams.currentUserId
MERGE (u)-[r:FOLLOWS]->(n)
RETURN COUNT(r) > 0
""")
"Unfollow the given Type and ID"
unfollow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
MATCH (:User {id: $cypherParams.currentUserId})-[r:FOLLOWS]->(n {id: $id})
WHERE $type IN labels(n)
DELETE r
RETURN COUNT(r) > 0
""")
disable(resource: Resource!): Boolean!
enable(resource: Resource!): Boolean!
}
@ -87,13 +119,19 @@ type User {
updatedAt: String
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(r)")
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)")
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(r)")
followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)")
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(r)")
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)")
"Is the currently logged in user following that user?"
followedByCurrentUser: Boolean! @cypher(statement: """
MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1
""")
#contributions: [WrittenPost]!
#contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
@ -112,7 +150,7 @@ type User {
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true RETURN COUNT(r)")
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT")
organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT")
@ -153,7 +191,13 @@ type Post {
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) RETURN COUNT(r)")
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
"Has the currently logged in user shouted that post?"
shoutedByCurrentUser: Boolean! @cypher(statement: """
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1
""")
}
type Comment {
@ -207,6 +251,16 @@ enum BadgeStatusEnum {
permanent
temporary
}
enum ShoutTypeEnum {
Post
Organization
Project
}
enum FollowTypeEnum {
User
Organization
Project
}
type Organization {
id: ID!
@ -228,7 +282,7 @@ type Tag {
name: String!
taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN")
taggedOrganizations: [Organization]! @relation(name: "TAGGED", direction: "IN")
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(p)")
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(DISTINCT p)")
taggedCountUnique: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p)<-[:WROTE]-(u:User) RETURN COUNT(DISTINCT u)")
deleted: Boolean
disabled: Boolean

View File

@ -45,7 +45,10 @@ const createServer = (options) => {
return {
driver,
user,
req: request
req: request,
cypherParams: {
currentUserId: user ? user.id : null
}
}
},
schema: schema,

View File

@ -1285,6 +1285,14 @@ apollo-env@0.3.3:
core-js "3.0.0-beta.13"
node-fetch "^2.2.0"
apollo-errors@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/apollo-errors/-/apollo-errors-1.9.0.tgz#f1ed0ca0a6be5cd2f24e2eaa7b0860a10146ff51"
integrity sha512-XVukHd0KLvgY6tNjsPS3/Re3U6RQlTKrTbIpqqeTMo2N34uQMr+H1UheV21o8hOZBAFosvBORVricJiP5vfmrw==
dependencies:
assert "^1.4.1"
extendable-error "^0.1.5"
apollo-graphql@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.1.1.tgz#dc5eac3062abf9f063ac9869f0ef5c54fdc136e5"
@ -1577,6 +1585,13 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assert@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=
dependencies:
util "0.10.3"
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
@ -3003,6 +3018,11 @@ extend@^3.0.0, extend@~3.0.2:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extendable-error@^0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/extendable-error/-/extendable-error-0.1.5.tgz#122308a7097bc89a263b2c4fbf089c78140e3b6d"
integrity sha1-EiMIpwl7yJomOyxPvwiceBQOO20=
external-editor@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
@ -3350,6 +3370,15 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
graphql-auth-directives@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/graphql-auth-directives/-/graphql-auth-directives-2.1.0.tgz#85b83817844e2ec5fba8fe5de444287d6dd0f85a"
integrity sha512-mRVsjeMeMABPyjxyzl9mhkcW02YBwSj7dnu7C6wy2dIhiby6xTKy6Q54C8KeqXSYsy6ua4VmBH++d7GKqpvIoA==
dependencies:
apollo-errors "^1.9.0"
graphql-tools "^4.0.4"
jsonwebtoken "^8.3.0"
graphql-custom-directives@~0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/graphql-custom-directives/-/graphql-custom-directives-0.2.14.tgz#88611b8cb074477020ad85af47bfe168c4c23992"
@ -3783,6 +3812,11 @@ inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, i
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inherits@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
@ -4642,7 +4676,7 @@ jsonify@~0.0.0:
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
jsonwebtoken@~8.5.0:
jsonwebtoken@^8.3.0, jsonwebtoken@~8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#ebd0ca2a69797816e1c5af65b6c759787252947e"
integrity sha512-IqEycp0znWHNA11TpYi77bVgyBO/pGESDh7Ajhas+u0ttkGkKYIIAjniL4Bw5+oVejVF+SYkaI7XKfwCCyeTuA==
@ -5146,12 +5180,13 @@ neo4j-driver@^1.7.2, neo4j-driver@~1.7.2:
text-encoding "^0.6.4"
uri-js "^4.2.1"
neo4j-graphql-js@~2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.3.1.tgz#9a5de7e312594d63481e947a0cbe4e08b05ffed3"
integrity sha512-9DExWXD2vFdDJOmqorT1ygFOUEos7KF8KyF8Wt3jYxejWUuq+a5fAFBu7+YDH8QbvA23paKPEX0Pn1nS+Q5C1A==
neo4j-graphql-js@~2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/neo4j-graphql-js/-/neo4j-graphql-js-2.4.1.tgz#a1de65340fb6f1ad0ae6aadab90a0bb78b490b32"
integrity sha512-Py6RJuMT7A03Hzoo6qfKyo6DUnHQgbZlBcgucnhgQjbeysAzvM3F02UAVn/HxEtOgowAeGWjyjJvwozoNtiiqQ==
dependencies:
graphql "^14.0.2"
graphql-auth-directives "^2.0.0"
lodash "^4.17.11"
neo4j-driver "^1.7.2"
@ -7186,6 +7221,13 @@ util.promisify@^1.0.0:
define-properties "^1.1.2"
object.getownpropertydescriptors "^2.0.3"
util@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
dependencies:
inherits "2.0.1"
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"