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..ff3992ead 100644
--- a/backend/src/middleware/validation/validationMiddleware.js
+++ b/backend/src/middleware/validation/validationMiddleware.js
@@ -57,11 +57,40 @@ 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(
+ `You have already reported the ${existingReportedResource.label}, please only report the same ${existingReportedResource.label} once`,
+ )
+ return resolve(root, args, context, info)
+}
+
export default {
Mutation: {
CreateComment: validateCommentCreation,
UpdateComment: validateUpdateComment,
CreatePost: validatePost,
UpdatePost: validateUpdatePost,
+ report: validateReport,
},
}
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 4b86e57f4..48677429a 100644
--- a/backend/src/schema/resolvers/reports.js
+++ b/backend/src/schema/resolvers/reports.js
@@ -1,62 +1,76 @@
-import uuid from 'uuid/v4'
-
export default {
Mutation: {
- report: async (
- _parent,
- { resourceId, reasonCategory, reasonDescription },
- { 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 reportProperties = {
- id: reportId,
- createdAt: new Date().toISOString(),
- reasonCategory,
- reasonDescription,
- }
-
- const reportQueryRes = await session.run(
- `
- MATCH (u:User {id:$submitterId})-[:REPORTED]->(report)-[:REPORTED]->(resource {id: $resourceId})
- RETURN labels(resource)[0] as label
- `,
- {
- resourceId,
- 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})
+ MATCH (submitter:User)-[report:REPORTED]->(resource)
WHERE resource:User OR resource:Comment OR resource:Post
- CREATE (report:Report {reportProperties})
- MERGE (resource)<-[:REPORTED]-(report)
- MERGE (report)<-[:REPORTED]-(submitter)
RETURN report, submitter, resource, labels(resource)[0] as type
`,
- {
- resourceId,
- userId: user.id,
- reportProperties,
- },
+ {},
)
-
session.close()
- const [dbResponse] = res.records.map(r => {
+ const dbResponse = res.records.map(r => {
return {
report: r.get('report'),
submitter: r.get('submitter'),
@@ -65,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 4b0aa7ba4..4022be1b1 100644
--- a/backend/src/schema/resolvers/reports.spec.js
+++ b/backend/src/schema/resolvers/reports.spec.js
@@ -1,37 +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', () => {
+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 = '{ id }'
variables = {
resourceId: 'whatever',
- reasonCategory: 'reason-category-dummy',
+ 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', {
@@ -45,20 +81,6 @@ describe('report', () => {
await factory.cleanDatabase()
})
- let client
- const action = () => {
- // because of the template `${returnedObject}` the 'gql' tag from 'jest/helpers' is not working here
- reportMutation = `
- mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) {
- report(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) ${returnedObject}
- }
- `
- client = new GraphQLClient(host, {
- headers,
- })
- return client.request(reportMutation, variables)
- }
-
describe('unauthenticated', () => {
it('throws authorization error', async () => {
await expect(action()).rejects.toThrow('Not Authorised')
@@ -91,8 +113,7 @@ describe('report', () => {
})
it('returns type "User"', async () => {
- returnedObject = '{ type }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
type: 'User',
},
@@ -100,8 +121,7 @@ describe('report', () => {
})
it('returns resource in user attribute', async () => {
- returnedObject = '{ user { name } }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
user: {
name: 'abusive-user',
@@ -111,8 +131,7 @@ describe('report', () => {
})
it('returns the submitter', async () => {
- returnedObject = '{ submitter { email } }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
submitter: {
email: 'test@example.org',
@@ -122,36 +141,41 @@ describe('report', () => {
})
it('returns a date', async () => {
- returnedObject = '{ createdAt }'
- await expect(action()).resolves.toEqual(
- expect.objectContaining({
- report: {
- createdAt: expect.any(String),
- },
- }),
- )
+ await expect(action()).resolves.toMatchObject({
+ report: {
+ createdAt: expect.any(String),
+ },
+ })
})
it('returns the reason category', async () => {
variables = {
...variables,
- reasonCategory: 'my-category',
+ reasonCategory: 'criminal_behavior_violation_german_law',
}
- returnedObject = '{ reasonCategory }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
- reasonCategory: 'my-category',
+ reasonCategory: 'criminal_behavior_violation_german_law',
},
})
})
+ 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!',
}
- returnedObject = '{ reasonDescription }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
reasonDescription: 'My reason!',
},
@@ -163,8 +187,7 @@ describe('report', () => {
...variables,
reasonDescription: 'My reason !',
}
- returnedObject = '{ reasonDescription }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
reasonDescription: 'My reason !',
},
@@ -187,8 +210,7 @@ describe('report', () => {
})
it('returns type "Post"', async () => {
- returnedObject = '{ type }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
type: 'Post',
},
@@ -196,8 +218,7 @@ describe('report', () => {
})
it('returns resource in post attribute', async () => {
- returnedObject = '{ post { title } }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
post: {
title: 'Matt and Robert having a pair-programming',
@@ -207,8 +228,7 @@ describe('report', () => {
})
it('returns null in user attribute', async () => {
- returnedObject = '{ user { name } }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
user: null,
},
@@ -241,8 +261,7 @@ describe('report', () => {
})
it('returns type "Comment"', async () => {
- returnedObject = '{ type }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
type: 'Comment',
},
@@ -250,8 +269,7 @@ describe('report', () => {
})
it('returns resource in comment attribute', async () => {
- returnedObject = '{ comment { content } }'
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: {
comment: {
content: 'Robert getting tired.',
@@ -276,7 +294,7 @@ describe('report', () => {
})
it('returns null', async () => {
- await expect(action()).resolves.toEqual({
+ await expect(action()).resolves.toMatchObject({
report: null,
})
})
@@ -287,3 +305,217 @@ describe('report', () => {
})
})
})
+
+describe('reports query', () => {
+ let query, mutate, authenticatedUser, moderator, user, author
+ const categoryIds = ['cat9']
+
+ const reportMutation = gql`
+ mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
+ report(
+ resourceId: $resourceId
+ reasonCategory: $reasonCategory
+ reasonDescription: $reasonDescription
+ ) {
+ type
+ }
+ }
+ `
+ const reportsQuery = gql`
+ query {
+ reports(orderBy: createdAt_desc) {
+ createdAt
+ reasonCategory
+ reasonDescription
+ submitter {
+ id
+ }
+ type
+ user {
+ id
+ }
+ post {
+ id
+ }
+ comment {
+ id
+ }
+ }
+ }
+ `
+
+ beforeAll(async () => {
+ await factory.cleanDatabase()
+ 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: 'Interesting Knowledge',
+ }),
+ factory.create('Post', {
+ author: moderator,
+ id: 'p2',
+ categoryIds,
+ content: 'More things to do …',
+ }),
+ factory.create('Post', {
+ author: user,
+ id: 'p3',
+ categoryIds,
+ content: 'I am at school …',
+ }),
+ ])
+ 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
+ expect(query({ query: reportsQuery })).resolves.toMatchObject({
+ data: { reports: null },
+ errors: [{ message: 'Not Authorised!' }],
+ })
+ })
+
+ it('role "user" gets no reports', async () => {
+ authenticatedUser = await user.toJson()
+ expect(query({ query: reportsQuery })).resolves.toMatchObject({
+ data: { reports: null },
+ errors: [{ message: 'Not Authorised!' }],
+ })
+ })
+
+ it('role "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',
+ }),
+ 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',
+ }),
+ 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',
+ }),
+ 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 cdf0d7f87..27fd2206c 100644
--- a/backend/src/schema/types/schema.gql
+++ b/backend/src/schema/types/schema.gql
@@ -24,7 +24,6 @@ type Mutation {
changePassword(oldPassword: String!, newPassword: String!): String!
requestPasswordReset(email: String!): Boolean!
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
- report(resourceId: ID!, reasonCategory: String!, reasonDescription: String!): Report
disable(id: ID!): ID
enable(id: ID!): ID
# Shout the given Type and ID
@@ -35,19 +34,6 @@ type Mutation {
unfollowUser(id: ID!): User
}
-type Report {
- id: ID!
- createdAt: String!
- reasonCategory: String!
- reasonDescription: String!
- submitter: User @relation(name: "REPORTED", direction: "IN")
- type: String!
- @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
- comment: Comment @relation(name: "REPORTED", direction: "OUT")
- post: Post @relation(name: "REPORTED", direction: "OUT")
- 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
new file mode 100644
index 000000000..0822c14ed
--- /dev/null
+++ b/backend/src/schema/types/type/REPORTED.gql
@@ -0,0 +1,42 @@
+type REPORTED {
+ createdAt: String
+ reasonCategory: ReasonCategory
+ reasonDescription: String
+ submitter: User
+ @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN user")
+ # not yet supported
+ # resource: ReportResource
+ # @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource")
+ type: String
+ @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN labels(resource)[0]")
+ user: User
+ post: Post
+ comment: Comment
+}
+
+# this list equals the strings of an array in file "webapp/components/Modal/ReportModal.vue"
+enum ReasonCategory {
+ other
+ discrimination_etc
+ pornographic_content_links
+ glorific_trivia_of_cruel_inhuman_acts
+ doxing
+ intentional_intimidation_stalking_persecution
+ advert_products_services_commercial
+ criminal_behavior_violation_german_law
+}
+
+# not yet supported
+# union ReportResource = User | Post | Comment
+
+enum ReportOrdering {
+ createdAt_desc
+}
+
+type Query {
+ reports(orderBy: ReportOrdering): [REPORTED]
+}
+
+type Mutation {
+ report(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): REPORTED
+}
diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js
index 789211373..76fbb4875 100644
--- a/backend/src/seed/seed-db.js
+++ b/backend/src/seed/seed-db.js
@@ -649,13 +649,13 @@ import { gql } from '../jest/helpers'
// There is no error logged or the 'try' fails if this mutation is wrong. Why?
const reportMutation = gql`
- mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) {
+ mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
reasonDescription: $reasonDescription
) {
- id
+ type
}
}
`
diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js
index 907c3d5eb..c32d5e10a 100644
--- a/cypress/integration/common/report.js
+++ b/cypress/integration/common/report.js
@@ -127,9 +127,9 @@ Given('somebody reported the following posts:', table => {
cy.factory()
.create('User', submitter)
.authenticateAs(submitter)
- .mutate(`mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) {
+ .mutate(`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
- id
+ type
}
}`, {
resourceId,
diff --git a/neo4j/change_report_node_to_relationship.sh b/neo4j/change_report_node_to_relationship.sh
new file mode 100755
index 000000000..a17dc6fe0
--- /dev/null
+++ b/neo4j/change_report_node_to_relationship.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+ENV_FILE=$(dirname "$0")/.env
+[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
+
+if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
+ echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
+ echo "Database manipulation is not possible without connecting to the database."
+ echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
+fi
+
+until echo 'RETURN "Connection successful" as info;' | cypher-shell
+do
+ echo "Connecting to neo4j failed, trying again..."
+ sleep 1
+done
+
+echo "
+MATCH (submitter:User)-[:REPORTED]->(report:Report)-[:REPORTED]->(resource)
+DETACH DELETE report
+CREATE (submitter)-[reported:REPORTED]->(resource)
+SET reported.createdAt = toString(datetime())
+SET reported.reasonCategory = 'other'
+SET reported.reasonDescription = '!!! Created automatically to ensure database consistency! createdAt is when the database manipulation happened.'
+RETURN reported;
+" | cypher-shell
diff --git a/webapp/components/Modal/ReportModal.vue b/webapp/components/Modal/ReportModal.vue
index 7c237c923..c33515a37 100644
--- a/webapp/components/Modal/ReportModal.vue
+++ b/webapp/components/Modal/ReportModal.vue
@@ -62,6 +62,7 @@ export default {
id: { type: String, required: true },
},
data() {
+ // this list equals to enums in GraphQL schema file "backend/src/schema/types/type/REPORTED.gql"
let valuesReasonCategoryOptions = [
'discrimination_etc',
'pornographic_content_links',
diff --git a/webapp/graphql/Moderation.js b/webapp/graphql/Moderation.js
index d86205042..1a5bdb367 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: "reports(first: 20, orderBy: createdAt_desc)"
return gql`
query {
- Report(first: 20, orderBy: createdAt_desc) {
- id
+ reports(orderBy: createdAt_desc) {
createdAt
reasonCategory
reasonDescription
@@ -83,13 +83,13 @@ export const reportListQuery = () => {
export const reportMutation = () => {
return gql`
- mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) {
+ mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
reasonDescription: $reasonDescription
) {
- id
+ type
}
}
`
diff --git a/webapp/locales/de.json b/webapp/locales/de.json
index 6620bb6b2..9b5bfb934 100644
--- a/webapp/locales/de.json
+++ b/webapp/locales/de.json
@@ -431,6 +431,7 @@
"name": "Meldungen",
"reasonCategory": "Kategorie",
"reasonDescription": "Beschreibung",
+ "createdAt": "Datum",
"submitter": "Gemeldet von",
"disabledBy": "Deaktiviert von"
}
diff --git a/webapp/locales/en.json b/webapp/locales/en.json
index 7a901ebd1..43fb9239f 100644
--- a/webapp/locales/en.json
+++ b/webapp/locales/en.json
@@ -432,6 +432,7 @@
"name": "Reports",
"reasonCategory": "Category",
"reasonDescription": "Description",
+ "createdAt": "Date",
"submitter": "Reported by",
"disabledBy": "Disabled by"
}
diff --git a/webapp/pages/moderation/index.vue b/webapp/pages/moderation/index.vue
index 1f05c93ab..535b2613c 100644
--- a/webapp/pages/moderation/index.vue
+++ b/webapp/pages/moderation/index.vue
@@ -1,7 +1,7 @@
{{ $t('moderation.reports.name') }}
-
+
@@ -82,6 +82,14 @@
{{ scope.row.submitter.name }}
+
+
+
+
+
+
+
+
import HcEmpty from '~/components/Empty.vue'
+import HcRelativeDateTime from '~/components/RelativeDateTime'
import { reportListQuery } from '~/graphql/Moderation.js'
export default {
components: {
HcEmpty,
+ HcRelativeDateTime,
},
data() {
return {
- Report: [],
+ reports: [],
}
},
computed: {
@@ -142,13 +152,14 @@ export default {
reasonCategory: this.$t('moderation.reports.reasonCategory'),
reasonDescription: this.$t('moderation.reports.reasonDescription'),
submitter: this.$t('moderation.reports.submitter'),
+ createdAt: this.$t('moderation.reports.createdAt'),
disabledBy: this.$t('moderation.reports.disabledBy'),
// actions: ' '
}
},
},
apollo: {
- Report: {
+ reports: {
query: reportListQuery(),
fetchPolicy: 'cache-and-network',
},