diff --git a/backend/src/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js index b8c539a0f..4dcfb6ed4 100644 --- a/backend/src/middleware/validation/validationMiddleware.js +++ b/backend/src/middleware/validation/validationMiddleware.js @@ -66,43 +66,50 @@ const validateReport = async (resolve, root, args, context, info) => { const validateReview = async (resolve, root, args, context, info) => { const { resourceId } = args + let existingReportedResource const { user, driver } = context if (resourceId === user.id) throw new Error('You cannot review yourself!') const session = driver.session() - const reportQueryRes = await session.run( - ` - MATCH (resource {id: $resourceId}) - WHERE resource:User OR resource:Post OR resource:Comment - OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource) - OPTIONAL MATCH (resource)<-[:WROTE]-(author:User) - RETURN labels(resource)[0] AS label, author, filed - `, - { - resourceId, - submitterId: user.id, - }, - ) - session.close() - const [existingReportedResource] = reportQueryRes.records.map(record => { - return { + const reportReadTxPromise = session.writeTransaction(async txc => { + const validateReviewTransactionResponse = await txc.run( + ` + MATCH (resource {id: $resourceId}) + WHERE resource:User OR resource:Post OR resource:Comment + OPTIONAL MATCH (:User)-[filed:FILED]->(:Report {closed: false})-[:BELONGS_TO]->(resource) + OPTIONAL MATCH (resource)<-[:WROTE]-(author:User) + RETURN labels(resource)[0] AS label, author, filed + `, + { + resourceId, + submitterId: user.id, + }, + ) + return validateReviewTransactionResponse.records.map(record => ({ label: record.get('label'), author: record.get('author'), filed: record.get('filed'), - } + })) }) + try { + const txResult = await reportReadTxPromise + existingReportedResource = txResult + if (!existingReportedResource || !existingReportedResource.length) + throw new Error(`Resource not found or is not a Post|Comment|User!`) + existingReportedResource = existingReportedResource[0] + if (!existingReportedResource.filed) + throw new Error( + `Before starting the review process, please report the ${existingReportedResource.label}!`, + ) + const authorId = + existingReportedResource.label !== 'User' && existingReportedResource.author + ? existingReportedResource.author.properties.id + : null + if (authorId && authorId === user.id) + throw new Error(`You cannot review your own ${existingReportedResource.label}!`) + } finally { + session.close() + } - if (!existingReportedResource) - throw new Error(`Resource not found or is not a Post|Comment|User!`) - if (!existingReportedResource.filed) - throw new Error( - `Before starting the review process, please report the ${existingReportedResource.label}!`, - ) - const authorId = - existingReportedResource.label !== 'User' && existingReportedResource.author - ? existingReportedResource.author.properties.id - : null - if (authorId && authorId === user.id) - throw new Error(`You cannot review your own ${existingReportedResource.label}!`) return resolve(root, args, context, info) } diff --git a/backend/src/schema/resolvers/moderation.js b/backend/src/schema/resolvers/moderation.js index b5aed1e14..4bdf82d50 100644 --- a/backend/src/schema/resolvers/moderation.js +++ b/backend/src/schema/resolvers/moderation.js @@ -24,8 +24,8 @@ export default { MERGE (report)<-[review:REVIEWED]-(moderator) ON CREATE SET review.createdAt = $dateTime, review.updatedAt = review.createdAt ON MATCH SET review.updatedAt = $dateTime - SET review.disable = $params.disable, review.closed = $params.closed - SET report.updatedAt = $dateTime, report.closed = review.closed + SET review.disable = $params.disable + SET report.updatedAt = $dateTime, report.closed = $params.closed SET resource.disabled = review.disable RETURN review, report, resource, labels(resource)[0] AS type diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index c3ac3caac..fc93229ae 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -63,7 +63,7 @@ export default { default: orderByClause = '' } - const readTxPromise = session.readTransaction(async tx => { + const reportReadTxPromise = session.readTransaction(async tx => { const allReportsTransactionResponse = await tx.run( ` MATCH (submitter:User)-[filed:FILED]->(report:Report)-[:BELONGS_TO]->(resource) @@ -76,7 +76,7 @@ export default { return allReportsTransactionResponse.records.map(transformReturnType) }) try { - const txResult = await readTxPromise + const txResult = await reportReadTxPromise if (!txResult[0]) return null reports = txResult } finally { diff --git a/neo4j/change_disabled_relationship_to_report_node.sh b/neo4j/change_disabled_relationship_to_report_node.sh new file mode 100755 index 000000000..55dd1333c --- /dev/null +++ b/neo4j/change_disabled_relationship_to_report_node.sh @@ -0,0 +1,55 @@ +#!/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 " +// convert old DISABLED to new REVIEWED-Report-BELONGS_TO structure +MATCH (moderator:User)-[disabled:DISABLED]->(disabledResource) +WHERE disabledResource:User OR disabledResource:Comment OR disabledResource:Post +DELETE disabled +CREATE (moderator)-[review:REVIEWED]->(report:Report)-[:BELONGS_TO]->(disabledResource) +SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true +SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false + +// if disabledResource has no filed report, then create a moderators default filed report +WITH moderator, disabledResource, report +OPTIONAL MATCH (disabledResourceReporter:User)-[existingFiledReport:FILED]->(disabledResource) +FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NULL THEN [1] ELSE [] END | + CREATE (moderator)-[addModeratorReport:FILED]->(report) + SET addModeratorReport.createdAt = toString(datetime()), addModeratorReport.reasonCategory = 'other', addModeratorReport.reasonDescription = 'Old DISABLED relation had no now mandatory report !!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.' +) +FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NOT NULL THEN [1] ELSE [] END | + CREATE (disabledResourceReporter)-[moveModeratorReport:FILED]->(report) + SET moveModeratorReport = existingFiledReport + DELETE existingFiledReport +) + +RETURN disabledResource {.id}; +" | cypher-shell + +echo " +// for FILED resources without DISABLED relation which are handled above, create new FILED-Report-BELONGS_TO structure +MATCH (reporter:User)-[oldReport:REPORTED]->(notDisabledResource) +WHERE notDisabledResource:User OR notDisabledResource:Comment OR notDisabledResource:Post +MERGE (report:Report)-[:BELONGS_TO]->(notDisabledResource) +ON CREATE SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false +CREATE (reporter)-[filed:FILED]->(report) +SET report = oldReport +DELETE oldReport + +RETURN notDisabledResource {.id}; +" | cypher-shell +