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/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js
index 0ecb6c115..1dbd36b72 100644
--- a/backend/src/middleware/validation/validationMiddleware.js
+++ b/backend/src/middleware/validation/validationMiddleware.js
@@ -57,11 +57,37 @@ const validateUpdatePost = async (resolve, root, args, context, info) => {
return validatePost(resolve, root, args, context, info)
}
+const validateReport = async (resolve, root, args, context, info) => {
+ const { resourceId } = args
+ const { user, driver } = context
+ if (resourceId === user.id) throw new Error('You cannot report yourself!')
+ const session = driver.session()
+ const reportQueryRes = await session.run(
+ `
+ MATCH (:User {id:$submitterId})-[:REPORTED]->(resource {id:$resourceId})
+ RETURN labels(resource)[0] as label
+ `,
+ {
+ resourceId,
+ submitterId: user.id,
+ },
+ )
+ const [existingReportedResource] = reportQueryRes.records.map(record => {
+ return {
+ label: record.get('label'),
+ }
+ })
+
+ if (existingReportedResource) throw new Error(`${existingReportedResource.label}`)
+ return resolve(root, args, context, info)
+}
+
export default {
Mutation: {
CreateComment: validateCommentCreation,
UpdateComment: validateUpdateComment,
CreatePost: validatePost,
UpdatePost: validateUpdatePost,
+ report: validateReport,
},
}
diff --git a/backend/src/middleware/xssMiddleware.js b/backend/src/middleware/xssMiddleware.js
index f98ab9d61..9b4e3e759 100644
--- a/backend/src/middleware/xssMiddleware.js
+++ b/backend/src/middleware/xssMiddleware.js
@@ -85,7 +85,7 @@ function clean(dirty) {
return dirty
}
-const fields = ['content', 'contentExcerpt']
+const fields = ['content', 'contentExcerpt', 'reasonDescription']
export default {
Mutation: async (resolve, root, args, context, info) => {
diff --git a/backend/src/schema/index.js b/backend/src/schema/index.js
index 4d1b9574e..95fa9ef61 100644
--- a/backend/src/schema/index.js
+++ b/backend/src/schema/index.js
@@ -23,6 +23,7 @@ export default applyScalars(
'Location',
'SocialMedia',
'NOTIFIED',
+ 'REPORTED',
],
// add 'User' here as soon as possible
},
@@ -35,7 +36,6 @@ export default applyScalars(
'Notfication',
'Post',
'Comment',
- 'Report',
'Statistics',
'LoggedInUser',
'Location',
@@ -43,6 +43,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 79cae032b..48677429a 100644
--- a/backend/src/schema/resolvers/reports.js
+++ b/backend/src/schema/resolvers/reports.js
@@ -1,55 +1,76 @@
-import uuid from 'uuid/v4'
-
export default {
Mutation: {
- report: async (parent, { id, description }, { driver, req, user }, resolveInfo) => {
- const reportId = uuid()
+ report: async (_parent, params, { driver, user }, _resolveInfo) => {
+ let createdRelationshipWithNestedAttributes
+ const { resourceId, reasonCategory, reasonDescription } = params
const session = driver.session()
- const reportData = {
- id: reportId,
- createdAt: new Date().toISOString(),
- description: description,
- }
-
- const reportQueryRes = await session.run(
- `
- match (u:User {id:$submitterId}) -[:REPORTED]->(report)-[:REPORTED]-> (resource {id: $resourceId})
- return labels(resource)[0] as label
- `,
- {
- resourceId: id,
- submitterId: user.id,
- },
- )
- const [rep] = reportQueryRes.records.map(record => {
- return {
- label: record.get('label'),
- }
+ const writeTxResultPromise = session.writeTransaction(async txc => {
+ const reportRelationshipTransactionResponse = await txc.run(
+ `
+ MATCH (submitter:User {id: $submitterId})
+ MATCH (resource {id: $resourceId})
+ WHERE resource:User OR resource:Comment OR resource:Post
+ CREATE (resource)<-[report:REPORTED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter)
+ RETURN report, submitter, resource, labels(resource)[0] as type
+ `,
+ {
+ resourceId,
+ submitterId: user.id,
+ createdAt: new Date().toISOString(),
+ reasonCategory,
+ reasonDescription,
+ },
+ )
+ return reportRelationshipTransactionResponse.records.map(record => ({
+ report: record.get('report'),
+ submitter: record.get('submitter'),
+ resource: record.get('resource').properties,
+ type: record.get('type'),
+ }))
})
-
- if (rep) {
- throw new Error(rep.label)
+ try {
+ const txResult = await writeTxResultPromise
+ if (!txResult[0]) return null
+ const { report, submitter, resource, type } = txResult[0]
+ createdRelationshipWithNestedAttributes = {
+ ...report.properties,
+ post: null,
+ comment: null,
+ user: null,
+ submitter: submitter.properties,
+ type,
+ }
+ switch (type) {
+ case 'Post':
+ createdRelationshipWithNestedAttributes.post = resource
+ break
+ case 'Comment':
+ createdRelationshipWithNestedAttributes.comment = resource
+ break
+ case 'User':
+ createdRelationshipWithNestedAttributes.user = resource
+ break
+ }
+ } finally {
+ session.close()
}
+ return createdRelationshipWithNestedAttributes
+ },
+ },
+ Query: {
+ reports: async (_parent, _params, { driver }, _resolveInfo) => {
+ const session = driver.session()
const res = await session.run(
`
- MATCH (submitter:User {id: $userId})
- MATCH (resource {id: $resourceId})
- WHERE resource:User OR resource:Comment OR resource:Post
- MERGE (report:Report {id: {reportData}.id })
- MERGE (resource)<-[:REPORTED]-(report)
- MERGE (report)<-[:REPORTED]-(submitter)
- RETURN report, submitter, resource, labels(resource)[0] as type
+ MATCH (submitter:User)-[report:REPORTED]->(resource)
+ WHERE resource:User OR resource:Comment OR resource:Post
+ RETURN report, submitter, resource, labels(resource)[0] as type
`,
- {
- resourceId: id,
- userId: user.id,
- reportData,
- },
+ {},
)
-
session.close()
- const [dbResponse] = res.records.map(r => {
+ const dbResponse = res.records.map(r => {
return {
report: r.get('report'),
submitter: r.get('submitter'),
@@ -58,27 +79,33 @@ export default {
}
})
if (!dbResponse) return null
- const { report, submitter, resource, type } = dbResponse
- const response = {
- ...report.properties,
- post: null,
- comment: null,
- user: null,
- submitter: submitter.properties,
- type,
- }
- switch (type) {
- case 'Post':
- response.post = resource.properties
- break
- case 'Comment':
- response.comment = resource.properties
- break
- case 'User':
- response.user = resource.properties
- break
- }
+ const response = []
+ dbResponse.forEach(ele => {
+ const { report, submitter, resource, type } = ele
+
+ const responseEle = {
+ ...report.properties,
+ 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 512d8d956..4022be1b1 100644
--- a/backend/src/schema/resolvers/reports.spec.js
+++ b/backend/src/schema/resolvers/reports.spec.js
@@ -1,35 +1,73 @@
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', () => {
- let mutation
+describe('report mutation', () => {
+ let reportMutation
let headers
- let returnedObject
+ let client
let variables
let createPostVariables
let user
const categoryIds = ['cat9']
+ const action = () => {
+ reportMutation = gql`
+ mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
+ report(
+ resourceId: $resourceId
+ reasonCategory: $reasonCategory
+ reasonDescription: $reasonDescription
+ ) {
+ createdAt
+ reasonCategory
+ reasonDescription
+ type
+ submitter {
+ email
+ }
+ user {
+ name
+ }
+ post {
+ title
+ }
+ comment {
+ content
+ }
+ }
+ }
+ `
+ client = new GraphQLClient(host, {
+ headers,
+ })
+ return client.request(reportMutation, variables)
+ }
+
beforeEach(async () => {
- returnedObject = '{ description }'
variables = {
- id: 'whatever',
+ resourceId: 'whatever',
+ reasonCategory: 'other',
+ reasonDescription: 'Violates code of conduct !!!',
}
headers = {}
user = await factory.create('User', {
+ id: 'u1',
+ role: 'user',
email: 'test@example.org',
password: '1234',
- id: 'u1',
})
await factory.create('User', {
id: 'u2',
- name: 'abusive-user',
role: 'user',
+ name: 'abusive-user',
email: 'abusive-user@example.org',
})
await instance.create('Category', {
@@ -43,59 +81,57 @@ describe('report', () => {
await factory.cleanDatabase()
})
- let client
- const action = () => {
- mutation = `
- mutation($id: ID!) {
- report(
- id: $id,
- description: "Violates code of conduct"
- ) ${returnedObject}
- }
- `
- client = new GraphQLClient(host, {
- headers,
- })
- return client.request(mutation, variables)
- }
-
describe('unauthenticated', () => {
it('throws authorization error', async () => {
await expect(action()).rejects.toThrow('Not Authorised')
})
+ })
- describe('authenticated', () => {
- beforeEach(async () => {
- headers = await login({
- email: 'test@example.org',
- password: '1234',
+ describe('authenticated', () => {
+ beforeEach(async () => {
+ headers = await login({
+ email: 'test@example.org',
+ password: '1234',
+ })
+ })
+
+ describe('invalid resource id', () => {
+ it('returns null', async () => {
+ await expect(action()).resolves.toEqual({
+ report: null,
})
})
+ })
- describe('invalid resource id', () => {
- it('returns null', async () => {
- await expect(action()).resolves.toEqual({
- report: null,
- })
- })
- })
-
- describe('valid resource id', () => {
+ describe('valid resource id', () => {
+ describe('reported resource is a user', () => {
beforeEach(async () => {
variables = {
- id: 'u2',
+ ...variables,
+ resourceId: 'u2',
}
})
- /*
- it('creates a report', async () => {
- await expect(action()).resolves.toEqual({
- type: null,
- })
- })
- */
+
+ it('returns type "User"', async () => {
+ await expect(action()).resolves.toMatchObject({
+ report: {
+ type: 'User',
+ },
+ })
+ })
+
+ it('returns resource in user attribute', async () => {
+ await expect(action()).resolves.toMatchObject({
+ report: {
+ user: {
+ name: 'abusive-user',
+ },
+ },
+ })
+ })
+
it('returns the submitter', async () => {
- returnedObject = '{ submitter { email } }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
submitter: {
email: 'test@example.org',
@@ -104,138 +140,382 @@ describe('report', () => {
})
})
- describe('reported resource is a user', () => {
- it('returns type "User"', async () => {
- returnedObject = '{ type }'
- await expect(action()).resolves.toEqual({
- report: {
- type: 'User',
- },
- })
- })
-
- it('returns resource in user attribute', async () => {
- returnedObject = '{ user { name } }'
- await expect(action()).resolves.toEqual({
- report: {
- user: {
- name: 'abusive-user',
- },
- },
- })
+ it('returns a date', async () => {
+ await expect(action()).resolves.toMatchObject({
+ report: {
+ createdAt: expect.any(String),
+ },
})
})
- describe('reported resource is a post', () => {
- beforeEach(async () => {
- await factory.create('Post', {
- author: user,
- id: 'p23',
- title: 'Matt and Robert having a pair-programming',
- categoryIds,
- })
- variables = {
- id: 'p23',
- }
- })
-
- it('returns type "Post"', async () => {
- returnedObject = '{ type }'
- await expect(action()).resolves.toEqual({
- report: {
- type: 'Post',
- },
- })
- })
-
- it('returns resource in post attribute', async () => {
- returnedObject = '{ post { title } }'
- await expect(action()).resolves.toEqual({
- report: {
- post: {
- title: 'Matt and Robert having a pair-programming',
- },
- },
- })
- })
-
- it('returns null in user attribute', async () => {
- returnedObject = '{ user { name } }'
- await expect(action()).resolves.toEqual({
- report: {
- user: null,
- },
- })
+ it('returns the reason category', async () => {
+ variables = {
+ ...variables,
+ reasonCategory: 'criminal_behavior_violation_german_law',
+ }
+ await expect(action()).resolves.toMatchObject({
+ report: {
+ reasonCategory: 'criminal_behavior_violation_german_law',
+ },
})
})
- /* An der Stelle würde ich den p23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
+ it('gives an error if the reason category is not in enum "ReasonCategory"', async () => {
+ variables = {
+ ...variables,
+ reasonCategory: 'my_category',
+ }
+ await expect(action()).rejects.toThrow(
+ 'got invalid value "my_category"; Expected type ReasonCategory',
+ )
+ })
+
+ it('returns the reason description', async () => {
+ variables = {
+ ...variables,
+ reasonDescription: 'My reason!',
+ }
+ await expect(action()).resolves.toMatchObject({
+ report: {
+ reasonDescription: 'My reason!',
+ },
+ })
+ })
+
+ it('sanitize the reason description', async () => {
+ variables = {
+ ...variables,
+ reasonDescription: 'My reason