diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index df87f743e..31efb9316 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -122,7 +122,7 @@ const permissions = shield( embed: allow, Category: allow, Tag: allow, - Report: isModerator, + reports: isModerator, statistics: allow, currentUser: allow, Post: or(onlyEnabledContent, isModerator), diff --git a/backend/src/schema/index.js b/backend/src/schema/index.js index e3c835447..871810d47 100644 --- a/backend/src/schema/index.js +++ b/backend/src/schema/index.js @@ -35,7 +35,6 @@ export default applyScalars( 'Notfication', 'Post', 'Comment', - 'REPORTED', 'Statistics', 'LoggedInUser', 'Location', @@ -43,6 +42,7 @@ export default applyScalars( 'User', 'EMOTED', 'NOTIFIED', + 'REPORTED', ], // add 'User' here as soon as possible }, diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index 2cf1187e0..dd9375995 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -1,17 +1,10 @@ -import uuid from 'uuid/v4' - export default { Mutation: { - report: async ( - _parent, - { resourceId, reasonCategory, reasonDescription }, - { driver, _req, user }, - _resolveInfo, - ) => { - // Wolle const reportId = uuid() + report: async (_parent, params, { driver, _req, user }, _resolveInfo) => { + const { resourceId, reasonCategory, reasonDescription } = params + const session = driver.session() const reportProperties = { - // Wolle id: reportId, createdAt: new Date().toISOString(), reasonCategory, reasonDescription, @@ -54,7 +47,7 @@ export default { }, ) - session.close() + session.close() const [dbResponse] = res.records.map(r => { return { @@ -88,6 +81,60 @@ export default { break } + return response + }, + }, + Query: { + reports: async (_parent, _params, { driver, _req, _user }, _resolveInfo) => { + const session = driver.session() + const res = await session.run( + ` + MATCH (submitter:User)-[report:REPORTED]->(resource) + WHERE resource:User OR resource:Comment OR resource:Post + RETURN report, submitter, resource, labels(resource)[0] as type + `, + {}, + ) + session.close() + + const dbResponse = res.records.map(r => { + return { + report: r.get('report'), + submitter: r.get('submitter'), + resource: r.get('resource'), + type: r.get('type'), + } + }) + if (!dbResponse) return null + + const response = [] + dbResponse.forEach(ele => { + const { report, submitter, resource, type } = ele + + const responseEle = { + ...report.properties, + resource: resource.properties, + resourceId: resource.properties.id, + post: null, + comment: null, + user: null, + submitter: submitter.properties, + type, + } + switch (type) { + case 'Post': + responseEle.post = resource.properties + break + case 'Comment': + responseEle.comment = resource.properties + break + case 'User': + responseEle.user = resource.properties + break + } + response.push(responseEle) + }) + return response }, }, diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js index cf89dab7a..919153611 100644 --- a/backend/src/schema/resolvers/reports.spec.js +++ b/backend/src/schema/resolvers/reports.spec.js @@ -1,51 +1,24 @@ import { GraphQLClient } from 'graphql-request' import Factory from '../../seed/factories' -import { host, login } from '../../jest/helpers' -import { neode } from '../../bootstrap/neo4j' +import { host, login, gql } from '../../jest/helpers' +import { getDriver, neode } from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import createServer from '../.././server' const factory = Factory() const instance = neode() +const driver = getDriver() -describe('report', () => { +describe('report mutation', () => { let reportMutation let headers + let client let returnedObject let variables let createPostVariables let user const categoryIds = ['cat9'] - beforeEach(async () => { - returnedObject = '{ createdAt }' - variables = { - resourceId: 'whatever', - reasonCategory: 'reason-category-dummy', - reasonDescription: 'Violates code of conduct !!!', - } - headers = {} - user = await factory.create('User', { - email: 'test@example.org', - password: '1234', - id: 'u1', - }) - await factory.create('User', { - id: 'u2', - name: 'abusive-user', - role: 'user', - email: 'abusive-user@example.org', - }) - await instance.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) - }) - - afterEach(async () => { - await factory.cleanDatabase() - }) - - let client const action = () => { // because of the template `${returnedObject}` the 'gql' tag from 'jest/helpers' is not working here reportMutation = ` @@ -59,6 +32,37 @@ describe('report', () => { return client.request(reportMutation, variables) } + beforeEach(async () => { + returnedObject = '{ createdAt }' + variables = { + resourceId: 'whatever', + reasonCategory: 'reason-category-dummy', + reasonDescription: 'Violates code of conduct !!!', + } + headers = {} + user = await factory.create('User', { + id: 'u1', + role: 'user', + email: 'test@example.org', + password: '1234', + }) + await factory.create('User', { + id: 'u2', + role: 'user', + name: 'abusive-user', + email: 'abusive-user@example.org', + }) + await instance.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + describe('unauthenticated', () => { it('throws authorization error', async () => { await expect(action()).rejects.toThrow('Not Authorised') @@ -287,3 +291,227 @@ describe('report', () => { }) }) }) + +describe('reports query', () => { + let query + let mutate + let authenticatedUser = null + let moderator + let user + let author + const categoryIds = ['cat9'] + + const reportMutation = gql` + mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) { + report( + resourceId: $resourceId + reasonCategory: $reasonCategory + reasonDescription: $reasonDescription + ) { + type + } + } + ` + const reportsQuery = gql` + query { + reports(orderBy: createdAt_desc) { + createdAt + reasonCategory + reasonDescription + submitter { + id + } + resourceId + type + user { + id + } + post { + id + } + comment { + id + } + } + } + ` + + beforeAll(() => { + const { server } = createServer({ + context: () => { + return { + driver, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query + mutate = createTestClient(server).mutate + }) + + beforeEach(async () => { + authenticatedUser = null + + moderator = await factory.create('User', { + id: 'mod1', + role: 'moderator', + email: 'moderator@example.org', + password: '1234', + }) + user = await factory.create('User', { + id: 'user1', + role: 'user', + email: 'test@example.org', + password: '1234', + }) + author = await factory.create('User', { + id: 'auth1', + role: 'user', + name: 'abusive-user', + email: 'abusive-user@example.org', + }) + await instance.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }) + + await Promise.all([ + factory.create('Post', { + author, + id: 'p1', + categoryIds, + content: 'Not for you', + }), + factory.create('Post', { + author: moderator, + id: 'p2', + categoryIds, + content: 'Already seen post mention', + }), + factory.create('Post', { + author: user, + id: 'p3', + categoryIds, + content: 'You have been mentioned in a post', + }), + ]) + await Promise.all([ + factory.create('Comment', { + author: user, + id: 'c1', + postId: 'p1', + }), + ]) + + authenticatedUser = await user.toJson() + await Promise.all([ + mutate({ + mutation: reportMutation, + variables: { + resourceId: 'p1', + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', + }, + }), + mutate({ + mutation: reportMutation, + variables: { + resourceId: 'c1', + reasonCategory: 'discrimination-etc', + reasonDescription: 'This post is bigoted', + }, + }), + mutate({ + mutation: reportMutation, + variables: { + resourceId: 'auth1', + reasonCategory: 'doxing', + reasonDescription: 'This user is harassing me with bigoted remarks', + }, + }), + ]) + authenticatedUser = null + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + const { data, errors } = await query({ query: reportsQuery }) + expect(data).toEqual({ + reports: null, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + + it('roll "user" gets no reports', async () => { + authenticatedUser = await user.toJson() + const { data, errors } = await query({ query: reportsQuery }) + expect(data).toEqual({ + reports: null, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + + it('roll "moderator" gets reports', async () => { + const expected = { + // to check 'orderBy: createdAt_desc' is not possible here, because 'createdAt' does not differ + reports: expect.arrayContaining([ + expect.objectContaining({ + createdAt: expect.any(String), + reasonCategory: 'doxing', + reasonDescription: 'This user is harassing me with bigoted remarks', + submitter: expect.objectContaining({ + id: 'user1', + }), + resourceId: 'auth1', + type: 'User', + user: expect.objectContaining({ + id: 'auth1', + }), + post: null, + comment: null, + }), + expect.objectContaining({ + createdAt: expect.any(String), + reasonCategory: 'other', + reasonDescription: 'This comment is bigoted', + submitter: expect.objectContaining({ + id: 'user1', + }), + resourceId: 'p1', + type: 'Post', + user: null, + post: expect.objectContaining({ + id: 'p1', + }), + comment: null, + }), + expect.objectContaining({ + createdAt: expect.any(String), + reasonCategory: 'discrimination-etc', + reasonDescription: 'This post is bigoted', + submitter: expect.objectContaining({ + id: 'user1', + }), + resourceId: 'c1', + type: 'Comment', + user: null, + post: null, + comment: expect.objectContaining({ + id: 'c1', + }), + }), + ]), + } + + authenticatedUser = await moderator.toJson() + const { data } = await query({ query: reportsQuery }) + expect(data).toEqual(expected) + }) + }) +}) diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index e4986ad73..27fd2206c 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -34,29 +34,6 @@ type Mutation { unfollowUser(id: ID!): User } -# Wolle -# type Report { - # not necessary - # id: ID! - # done - # createdAt: String! - # done - # reasonCategory: String! - # done - # reasonDescription: String! - # done - # submitter: User @relation(name: "REPORTED", direction: "IN") - # done - # type: String! - # @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]") - # seems unused - # comment: Comment @relation(name: "REPORTED", direction: "OUT") - # seems unused - # post: Post @relation(name: "REPORTED", direction: "OUT") - # seems unused - # user: User @relation(name: "REPORTED", direction: "OUT") -# } - enum Deletable { Post Comment diff --git a/backend/src/schema/types/type/REPORTED.gql b/backend/src/schema/types/type/REPORTED.gql index 881184bb5..276c5913b 100644 --- a/backend/src/schema/types/type/REPORTED.gql +++ b/backend/src/schema/types/type/REPORTED.gql @@ -4,8 +4,9 @@ type REPORTED { reasonDescription: String submitter: User @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN user") - resource: ReportReource - @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource") + # not yet supported + # resource: ReportReource + # @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource") resourceId: ID @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource {.id}") type: String @@ -15,14 +16,15 @@ type REPORTED { comment: Comment } -union ReportReource = User | Post | Comment +# not yet supported +# union ReportReource = User | Post | Comment enum ReportOrdering { createdAt_desc } type Query { - Report(orderBy: ReportOrdering): [REPORTED] + reports(orderBy: ReportOrdering): [REPORTED] } type Mutation { diff --git a/webapp/graphql/Moderation.js b/webapp/graphql/Moderation.js index 981b8146e..8ead28b61 100644 --- a/webapp/graphql/Moderation.js +++ b/webapp/graphql/Moderation.js @@ -1,10 +1,10 @@ import gql from 'graphql-tag' export const reportListQuery = () => { - // no limit vor the moment like before: "Report(first: 20, orderBy: createdAt_desc)" + // no limit vor the moment like before: "reports(first: 20, orderBy: createdAt_desc)" return gql` query { - Report(orderBy: createdAt_desc) { + reports(orderBy: createdAt_desc) { createdAt reasonCategory reasonDescription