mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #3075 from Human-Connection/3074-don’t-expose-all-properties-of-report
feat: 🍰 Expose sensitive report type to moderators only
This commit is contained in:
commit
e164104791
@ -152,6 +152,7 @@ export default shield(
|
||||
User: {
|
||||
email: or(isMyOwn, isAdmin),
|
||||
},
|
||||
Report: isModerator,
|
||||
},
|
||||
{
|
||||
debug,
|
||||
|
||||
@ -9,14 +9,6 @@ const driver = getDriver()
|
||||
|
||||
let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator
|
||||
|
||||
const userQuery = gql`
|
||||
query($name: String) {
|
||||
User(name: $name) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('authorization', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
@ -30,7 +22,11 @@ describe('authorization', () => {
|
||||
query = createTestClient(server).query
|
||||
})
|
||||
|
||||
describe('given two existing users', () => {
|
||||
afterEach(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('given an owner, an other user, an admin, a moderator', () => {
|
||||
beforeEach(async () => {
|
||||
;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([
|
||||
Factory.build(
|
||||
@ -79,15 +75,20 @@ describe('authorization', () => {
|
||||
variables = {}
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('access email address', () => {
|
||||
const userQuery = gql`
|
||||
query($name: String) {
|
||||
User(name: $name) {
|
||||
email
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
beforeEach(() => {
|
||||
authenticatedUser = null
|
||||
})
|
||||
|
||||
it("throws an error and does not expose the owner's email address", async () => {
|
||||
await expect(
|
||||
query({ query: userQuery, variables: { name: 'Owner' } }),
|
||||
@ -143,7 +144,7 @@ describe('authorization', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('administrator', () => {
|
||||
describe('as an administrator', () => {
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = await administrator.toJson()
|
||||
})
|
||||
|
||||
@ -58,7 +58,7 @@ const reportMutation = gql`
|
||||
reasonCategory: $reasonCategory
|
||||
reasonDescription: $reasonDescription
|
||||
) {
|
||||
id
|
||||
reportId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,20 +1,10 @@
|
||||
const transformReturnType = record => {
|
||||
return {
|
||||
...record.get('review').properties,
|
||||
report: record.get('report').properties,
|
||||
resource: {
|
||||
__typename: record.get('type'),
|
||||
...record.get('resource').properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
import log from './helpers/databaseLogger'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
review: async (_object, params, context, _resolveInfo) => {
|
||||
const { user: moderator, driver } = context
|
||||
|
||||
let createdRelationshipWithNestedAttributes = null // return value
|
||||
const session = driver.session()
|
||||
try {
|
||||
const cypher = `
|
||||
@ -25,10 +15,11 @@ export default {
|
||||
ON CREATE SET review.createdAt = $dateTime, review.updatedAt = review.createdAt
|
||||
ON MATCH SET review.updatedAt = $dateTime
|
||||
SET review.disable = $params.disable
|
||||
SET report.updatedAt = $dateTime, report.closed = $params.closed
|
||||
SET resource.disabled = review.disable
|
||||
SET report.updatedAt = $dateTime, report.disable = review.disable, report.closed = $params.closed
|
||||
SET resource.disabled = report.disable
|
||||
|
||||
RETURN review, report, resource, labels(resource)[0] AS type
|
||||
WITH review, report, resource {.*, __typename: labels(resource)[0]} AS finalResource
|
||||
RETURN review {.*, report: properties(report), resource: properties(finalResource)}
|
||||
`
|
||||
const reviewWriteTxResultPromise = session.writeTransaction(async txc => {
|
||||
const reviewTransactionResponse = await txc.run(cypher, {
|
||||
@ -36,16 +27,14 @@ export default {
|
||||
moderatorId: moderator.id,
|
||||
dateTime: new Date().toISOString(),
|
||||
})
|
||||
return reviewTransactionResponse.records.map(transformReturnType)
|
||||
log(reviewTransactionResponse)
|
||||
return reviewTransactionResponse.records.map(record => record.get('review'))
|
||||
})
|
||||
const txResult = await reviewWriteTxResultPromise
|
||||
if (!txResult[0]) return null
|
||||
createdRelationshipWithNestedAttributes = txResult[0]
|
||||
const [reviewed] = await reviewWriteTxResultPromise
|
||||
return reviewed || null
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
|
||||
return createdRelationshipWithNestedAttributes
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,23 +1,13 @@
|
||||
import log from './helpers/databaseLogger'
|
||||
|
||||
const transformReturnType = record => {
|
||||
return {
|
||||
...record.get('report').properties,
|
||||
resource: {
|
||||
__typename: record.get('type'),
|
||||
...record.get('resource').properties,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
fileReport: async (_parent, params, context, _resolveInfo) => {
|
||||
const { resourceId, reasonCategory, reasonDescription } = params
|
||||
const { driver, user } = context
|
||||
const session = driver.session()
|
||||
const reportWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||
const reportTransactionResponse = await transaction.run(
|
||||
const fileReportWriteTxResultPromise = session.writeTransaction(async transaction => {
|
||||
const fileReportTransactionResponse = await transaction.run(
|
||||
`
|
||||
MATCH (submitter:User {id: $submitterId})
|
||||
MATCH (resource {id: $resourceId})
|
||||
@ -27,7 +17,8 @@ export default {
|
||||
WITH submitter, resource, report
|
||||
CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter)
|
||||
|
||||
RETURN report, resource, labels(resource)[0] AS type
|
||||
WITH filed, report, resource {.*, __typename: labels(resource)[0]} AS finalResource
|
||||
RETURN filed {.*, reportId: report.id, resource: properties(finalResource)} AS filedReport
|
||||
`,
|
||||
{
|
||||
resourceId,
|
||||
@ -37,13 +28,12 @@ export default {
|
||||
reasonDescription,
|
||||
},
|
||||
)
|
||||
log(reportTransactionResponse)
|
||||
return reportTransactionResponse.records.map(transformReturnType)
|
||||
log(fileReportTransactionResponse)
|
||||
return fileReportTransactionResponse.records.map(record => record.get('filedReport'))
|
||||
})
|
||||
try {
|
||||
const [createdRelationshipWithNestedAttributes] = await reportWriteTxResultPromise
|
||||
if (!createdRelationshipWithNestedAttributes) return null
|
||||
return createdRelationshipWithNestedAttributes
|
||||
const [filedReport] = await fileReportWriteTxResultPromise
|
||||
return filedReport || null
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
@ -76,14 +66,24 @@ export default {
|
||||
filterClause = ''
|
||||
}
|
||||
|
||||
if (params.closed) filterClause = 'AND report.closed = true'
|
||||
switch (params.closed) {
|
||||
case true:
|
||||
filterClause = 'AND report.closed = true'
|
||||
break
|
||||
case false:
|
||||
filterClause = 'AND report.closed = false'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
const offset =
|
||||
params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : ''
|
||||
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : ''
|
||||
|
||||
const reportReadTxPromise = session.readTransaction(async transaction => {
|
||||
const allReportsTransactionResponse = await transaction.run(
|
||||
const reportsReadTxPromise = session.readTransaction(async transaction => {
|
||||
const reportsTransactionResponse = await transaction.run(
|
||||
// !!! this Cypher query returns multiple reports on the same resource! i will create an issue for refactoring (bug fixing)
|
||||
`
|
||||
MATCH (report:Report)-[:BELONGS_TO]->(resource)
|
||||
WHERE (resource:User OR resource:Post OR resource:Comment)
|
||||
@ -101,11 +101,11 @@ export default {
|
||||
${offset} ${limit}
|
||||
`,
|
||||
)
|
||||
log(allReportsTransactionResponse)
|
||||
return allReportsTransactionResponse.records.map(record => record.get('report'))
|
||||
log(reportsTransactionResponse)
|
||||
return reportsTransactionResponse.records.map(record => record.get('report'))
|
||||
})
|
||||
try {
|
||||
const reports = await reportReadTxPromise
|
||||
const reports = await reportsReadTxPromise
|
||||
return reports
|
||||
} finally {
|
||||
session.close()
|
||||
|
||||
@ -10,18 +10,17 @@ const driver = getDriver()
|
||||
describe('file a report on a resource', () => {
|
||||
let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser, otherReportingUser
|
||||
const categoryIds = ['cat9']
|
||||
const reportMutation = gql`
|
||||
const fileReportMutation = gql`
|
||||
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
fileReport(
|
||||
resourceId: $resourceId
|
||||
reasonCategory: $reasonCategory
|
||||
reasonDescription: $reasonDescription
|
||||
) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
closed
|
||||
rule
|
||||
reasonCategory
|
||||
reasonDescription
|
||||
reportId
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
@ -34,6 +33,35 @@ describe('file a report on a resource', () => {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = {
|
||||
resourceId: 'invalid',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'Violates code of conduct !!!',
|
||||
}
|
||||
const reportsQuery = gql`
|
||||
query($closed: Boolean) {
|
||||
reports(orderBy: createdAt_desc, closed: $closed) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
rule
|
||||
disable
|
||||
closed
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
}
|
||||
... on Post {
|
||||
id
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
}
|
||||
}
|
||||
filed {
|
||||
submitter {
|
||||
id
|
||||
@ -45,11 +73,31 @@ describe('file a report on a resource', () => {
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = {
|
||||
resourceId: 'whatever',
|
||||
reasonCategory: 'other',
|
||||
reasonDescription: 'Violates code of conduct !!!',
|
||||
}
|
||||
const reviewMutation = gql`
|
||||
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
|
||||
review(resourceId: $resourceId, disable: $disable, closed: $closed) {
|
||||
createdAt
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
disabled
|
||||
}
|
||||
... on Post {
|
||||
id
|
||||
disabled
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
disabled
|
||||
}
|
||||
}
|
||||
report {
|
||||
disable
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
@ -74,7 +122,7 @@ describe('file a report on a resource', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
authenticatedUser = null
|
||||
await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({
|
||||
await expect(mutate({ mutation: fileReportMutation, variables })).resolves.toMatchObject({
|
||||
data: { fileReport: null },
|
||||
errors: [{ message: 'Not Authorised!' }],
|
||||
})
|
||||
@ -94,6 +142,17 @@ describe('file a report on a resource', () => {
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
moderator = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
id: 'moderator-id',
|
||||
role: 'moderator',
|
||||
},
|
||||
{
|
||||
email: 'moderator@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
otherReportingUser = await Factory.build(
|
||||
'user',
|
||||
{
|
||||
@ -127,7 +186,7 @@ describe('file a report on a resource', () => {
|
||||
|
||||
describe('invalid resource id', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({
|
||||
await expect(mutate({ mutation: fileReportMutation, variables })).resolves.toMatchObject({
|
||||
data: { fileReport: null },
|
||||
errors: undefined,
|
||||
})
|
||||
@ -139,47 +198,112 @@ describe('file a report on a resource', () => {
|
||||
it('which belongs to resource', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
id: expect.any(String),
|
||||
reportId: expect.any(String),
|
||||
resource: {
|
||||
name: 'abusive-user',
|
||||
},
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('creates only one report for multiple reports on the same resource', async () => {
|
||||
it('only one report for multiple reports on the same resource', async () => {
|
||||
const firstReport = await mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
authenticatedUser = await otherReportingUser.toJson()
|
||||
const secondReport = await mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
expect(firstReport.data.fileReport.id).toEqual(secondReport.data.fileReport.id)
|
||||
|
||||
expect(firstReport.data.fileReport.reportId).toEqual(
|
||||
secondReport.data.fileReport.reportId,
|
||||
)
|
||||
})
|
||||
|
||||
it('returns the rule for how the report was decided', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
describe('report properties are set correctly', () => {
|
||||
const reportsCypherQuery =
|
||||
'MATCH (resource:User {id: $resourceId})<-[:BELONGS_TO]-(report:Report {closed: false})<-[filed:FILED]-(user:User {id: $currentUserId}) RETURN report'
|
||||
|
||||
it('with the rule for how the report will be decided', async () => {
|
||||
await mutate({
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
rule: 'latestReviewUpdatedAtRules',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
|
||||
const reportsCypherQueryResponse = await instance.cypher(reportsCypherQuery, {
|
||||
resourceId: 'abusive-user-id',
|
||||
currentUserId: authenticatedUser.id,
|
||||
})
|
||||
expect(reportsCypherQueryResponse.records).toHaveLength(1)
|
||||
const [reportProperties] = reportsCypherQueryResponse.records.map(
|
||||
record => record.get('report').properties,
|
||||
)
|
||||
expect(reportProperties).toMatchObject({ rule: 'latestReviewUpdatedAtRules' })
|
||||
})
|
||||
|
||||
describe('with overtaken disabled from resource in disable property', () => {
|
||||
it('disable is false', async () => {
|
||||
await mutate({
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
|
||||
const reportsCypherQueryResponse = await instance.cypher(reportsCypherQuery, {
|
||||
resourceId: 'abusive-user-id',
|
||||
currentUserId: authenticatedUser.id,
|
||||
})
|
||||
expect(reportsCypherQueryResponse.records).toHaveLength(1)
|
||||
const [reportProperties] = reportsCypherQueryResponse.records.map(
|
||||
record => record.get('report').properties,
|
||||
)
|
||||
expect(reportProperties).toMatchObject({ disable: false })
|
||||
})
|
||||
|
||||
it('disable is true', async () => {
|
||||
// first time filling a report to enable a moderator the disable the resource
|
||||
await mutate({
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
authenticatedUser = await moderator.toJson()
|
||||
await mutate({
|
||||
mutation: reviewMutation,
|
||||
variables: {
|
||||
resourceId: 'abusive-user-id',
|
||||
disable: true,
|
||||
closed: true,
|
||||
},
|
||||
})
|
||||
authenticatedUser = await currentUser.toJson()
|
||||
// second time filling a report to see if the "disable is true" of the resource is overtaken
|
||||
await mutate({
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
})
|
||||
|
||||
const reportsCypherQueryResponse = await instance.cypher(reportsCypherQuery, {
|
||||
resourceId: 'abusive-user-id',
|
||||
currentUserId: authenticatedUser.id,
|
||||
})
|
||||
expect(reportsCypherQueryResponse.records).toHaveLength(1)
|
||||
const [reportProperties] = reportsCypherQueryResponse.records.map(
|
||||
record => record.get('report').properties,
|
||||
)
|
||||
expect(reportProperties).toMatchObject({ disable: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it.todo('creates multiple filed reports')
|
||||
})
|
||||
|
||||
@ -187,7 +311,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns __typename "User"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -205,7 +329,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns user attribute info', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -221,32 +345,10 @@ describe('file a report on a resource', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('returns the submitter', async () => {
|
||||
it('returns a createdAt', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
submitter: {
|
||||
id: 'current-user-id',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns a date', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: { ...variables, resourceId: 'abusive-user-id' },
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -262,7 +364,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns the reason category', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'abusive-user-id',
|
||||
@ -272,11 +374,7 @@ describe('file a report on a resource', () => {
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
reasonCategory: 'criminal_behavior_violation_german_law',
|
||||
},
|
||||
],
|
||||
reasonCategory: 'criminal_behavior_violation_german_law',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -286,7 +384,7 @@ describe('file a report on a resource', () => {
|
||||
it('gives an error if the reason category is not in enum "ReasonCategory"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'abusive-user-id',
|
||||
@ -307,7 +405,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns the reason description', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'abusive-user-id',
|
||||
@ -317,11 +415,7 @@ describe('file a report on a resource', () => {
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
reasonDescription: 'My reason!',
|
||||
},
|
||||
],
|
||||
reasonDescription: 'My reason!',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -331,7 +425,7 @@ describe('file a report on a resource', () => {
|
||||
it('sanitizes the reason description', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'abusive-user-id',
|
||||
@ -341,11 +435,7 @@ describe('file a report on a resource', () => {
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
fileReport: {
|
||||
filed: [
|
||||
{
|
||||
reasonDescription: 'My reason !',
|
||||
},
|
||||
],
|
||||
reasonDescription: 'My reason !',
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
@ -371,7 +461,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns type "Post"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'post-to-report-id',
|
||||
@ -392,7 +482,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns resource in post attribute', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'post-to-report-id',
|
||||
@ -442,7 +532,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns type "Comment"', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'comment-to-report-id',
|
||||
@ -463,7 +553,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns resource in comment attribute', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'comment-to-report-id',
|
||||
@ -493,7 +583,7 @@ describe('file a report on a resource', () => {
|
||||
it('returns null', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
...variables,
|
||||
resourceId: 'tag-to-report-id',
|
||||
@ -510,37 +600,6 @@ describe('file a report on a resource', () => {
|
||||
})
|
||||
|
||||
describe('query for reported resource', () => {
|
||||
const reportsQuery = gql`
|
||||
query {
|
||||
reports(orderBy: createdAt_desc) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
closed
|
||||
resource {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
}
|
||||
... on Post {
|
||||
id
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
}
|
||||
}
|
||||
filed {
|
||||
submitter {
|
||||
id
|
||||
}
|
||||
createdAt
|
||||
reasonCategory
|
||||
reasonDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
beforeEach(async () => {
|
||||
authenticatedUser = null
|
||||
moderator = await Factory.build(
|
||||
@ -632,7 +691,7 @@ describe('file a report on a resource', () => {
|
||||
authenticatedUser = await currentUser.toJson()
|
||||
await Promise.all([
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
resourceId: 'abusive-post-1',
|
||||
reasonCategory: 'other',
|
||||
@ -640,7 +699,7 @@ describe('file a report on a resource', () => {
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
resourceId: 'abusive-comment-1',
|
||||
reasonCategory: 'discrimination_etc',
|
||||
@ -648,7 +707,7 @@ describe('file a report on a resource', () => {
|
||||
},
|
||||
}),
|
||||
mutate({
|
||||
mutation: reportMutation,
|
||||
mutation: fileReportMutation,
|
||||
variables: {
|
||||
resourceId: 'abusive-user-1',
|
||||
reasonCategory: 'doxing',
|
||||
|
||||
@ -251,12 +251,12 @@ export default {
|
||||
boolean: {
|
||||
followedByCurrentUser:
|
||||
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
isBlocked:
|
||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
blocked:
|
||||
'MATCH (this)-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
isMuted:
|
||||
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
isBlocked:
|
||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
},
|
||||
count: {
|
||||
contributionsCount:
|
||||
|
||||
@ -16,3 +16,15 @@ enum ReasonCategory {
|
||||
advert_products_services_commercial
|
||||
criminal_behavior_violation_german_law
|
||||
}
|
||||
|
||||
type FiledReport {
|
||||
createdAt: String!
|
||||
reasonCategory: ReasonCategory!
|
||||
reasonDescription: String!
|
||||
reportId: ID!
|
||||
resource: ReportedResource!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): FiledReport
|
||||
}
|
||||
@ -4,7 +4,6 @@ type REVIEWED {
|
||||
disable: Boolean!
|
||||
closed: Boolean!
|
||||
report: Report
|
||||
# @cypher(statement: "MATCH (report:Report)<-[this:REVIEWED]-(:User) RETURN report")
|
||||
moderator: User
|
||||
resource: ReviewedResource
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@ type Report {
|
||||
rule: ReportRule!
|
||||
disable: Boolean!
|
||||
closed: Boolean!
|
||||
filed: [FILED]
|
||||
filed: [FILED]!
|
||||
reviewed: [REVIEWED]!
|
||||
resource: ReportedResource
|
||||
resource: ReportedResource!
|
||||
}
|
||||
|
||||
union ReportedResource = User | Post | Comment
|
||||
@ -16,10 +16,6 @@ enum ReportRule {
|
||||
latestReviewUpdatedAtRules
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): Report
|
||||
}
|
||||
|
||||
type Query {
|
||||
reports(orderBy: ReportOrdering, first: Int, offset: Int, reviewed: Boolean, closed: Boolean): [Report]
|
||||
}
|
||||
|
||||
@ -64,10 +64,11 @@ type User {
|
||||
# Is the currently logged in user following that user?
|
||||
followedByCurrentUser: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId})
|
||||
MATCH (this)<-[:FOLLOWS]-(u:User { id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
|
||||
isBlocked: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
|
||||
|
||||
@ -139,7 +139,7 @@ Given('somebody reported the following posts:', table => {
|
||||
.authenticateAs(submitter)
|
||||
.mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
|
||||
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
|
||||
id
|
||||
reportId
|
||||
}
|
||||
}`, {
|
||||
resourceId,
|
||||
|
||||
@ -44,13 +44,22 @@ export default {
|
||||
computed: {
|
||||
filterOptions() {
|
||||
return [
|
||||
{ label: this.$t('moderation.reports.filterLabel.all'), value: { reviewed: null } },
|
||||
{
|
||||
label: this.$t('moderation.reports.filterLabel.all'),
|
||||
value: { reviewed: null, closed: null },
|
||||
},
|
||||
{
|
||||
label: this.$t('moderation.reports.filterLabel.unreviewed'),
|
||||
value: { reviewed: false },
|
||||
value: { reviewed: false, closed: false },
|
||||
},
|
||||
{
|
||||
label: this.$t('moderation.reports.filterLabel.reviewed'),
|
||||
value: { reviewed: true, closed: false },
|
||||
},
|
||||
{
|
||||
label: this.$t('moderation.reports.filterLabel.closed'),
|
||||
value: { reviewed: null, closed: true },
|
||||
},
|
||||
{ label: this.$t('moderation.reports.filterLabel.reviewed'), value: { reviewed: true } },
|
||||
{ label: this.$t('moderation.reports.filterLabel.closed'), value: { closed: true } },
|
||||
]
|
||||
},
|
||||
modalData() {
|
||||
@ -108,13 +117,8 @@ export default {
|
||||
filter(option) {
|
||||
this.selected = option.label
|
||||
this.offset = 0
|
||||
if (option.value.closed) {
|
||||
this.closed = option.value.closed
|
||||
this.reviewed = null
|
||||
return
|
||||
}
|
||||
this.closed = null
|
||||
this.reviewed = option.value.reviewed
|
||||
this.closed = option.value.closed
|
||||
},
|
||||
async confirmCallback(resource) {
|
||||
const { disabled: disable, id: resourceId } = resource
|
||||
|
||||
@ -24,11 +24,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<template v-for="report in reports">
|
||||
<report-row
|
||||
:key="report.resource.id"
|
||||
:report="report"
|
||||
@confirm-report="$emit('confirm', report)"
|
||||
/>
|
||||
<!-- should be ':key="report.resource.id"' for having one element for every resource, but this crashes at the moment, because the 'reports' query returns multiple reports on the same resource! I will create an issue -->
|
||||
<report-row :key="report.id" :report="report" @confirm-report="$emit('confirm', report)" />
|
||||
</template>
|
||||
</table>
|
||||
<hc-empty v-else icon="alert" :message="$t('moderation.reports.empty')" />
|
||||
|
||||
@ -100,7 +100,7 @@ export const reportMutation = () => {
|
||||
reasonCategory: $reasonCategory
|
||||
reasonDescription: $reasonDescription
|
||||
) {
|
||||
id
|
||||
reportId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user