Update to use enum in tests, seed data, etc, refactor resolver

- Extract validations to the validations middleware to clean it up
- Remove resourceId since it throws an error in the mutation if the user
asks for it back, and the resourceId is returned in post/comment/user.id
- use writeTxResultPromise to benefit from automatic retries
- more descriptive variable naming
- extract cypher query to make db manipulation into script so that it
can be run from the command line, at least locally.
This commit is contained in:
mattwr18 2019-10-14 21:07:55 +02:00
parent cae897808b
commit faf0a15aee
7 changed files with 115 additions and 104 deletions

View File

@ -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,
},
}

View File

@ -1,87 +1,60 @@
export default {
Mutation: {
report: async (_parent, params, { driver, user }, _resolveInfo) => {
let createdRelationshipWithNestedAttributes
const { resourceId, reasonCategory, reasonDescription } = params
const session = driver.session()
const reportProperties = {
createdAt: new Date().toISOString(),
reasonCategory,
reasonDescription,
}
const reportQueryRes = await session.run(
`
MATCH (:User {id:$submitterId})-[: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)
}
const res = await session.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: reportProperties.createdAt,
reasonCategory: reportProperties.reasonCategory,
reasonDescription: reportProperties.reasonDescription,
},
)
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'),
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,
}
})
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':
createdRelationshipWithNestedAttributes.post = resource
break
case 'Comment':
createdRelationshipWithNestedAttributes.comment = resource
break
case 'User':
createdRelationshipWithNestedAttributes.user = resource
break
}
} finally {
session.close()
}
switch (type) {
case 'Post':
response.post = resource.properties
break
case 'Comment':
response.comment = resource.properties
break
case 'User':
response.user = resource.properties
break
}
return response
return createdRelationshipWithNestedAttributes
},
},
Query: {
@ -113,13 +86,13 @@ export default {
const responseEle = {
...report.properties,
resourceId: resource.properties.id,
post: null,
comment: null,
user: null,
submitter: submitter.properties,
type,
}
switch (type) {
case 'Post':
responseEle.post = resource.properties

View File

@ -20,7 +20,7 @@ describe('report mutation', () => {
const action = () => {
reportMutation = gql`
mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) {
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
@ -166,7 +166,7 @@ describe('report mutation', () => {
reasonCategory: 'my_category',
}
await expect(action()).rejects.toThrow(
'Expected a value of type "ReasonCategory" but received: "my_category"',
'got invalid value "my_category"; Expected type ReasonCategory',
)
})
@ -307,16 +307,11 @@ describe('report mutation', () => {
})
describe('reports query', () => {
let query
let mutate
let authenticatedUser = null
let moderator
let user
let author
let query, mutate, authenticatedUser, moderator, user, author
const categoryIds = ['cat9']
const reportMutation = gql`
mutation($resourceId: ID!, $reasonCategory: String!, $reasonDescription: String!) {
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
@ -335,7 +330,6 @@ describe('reports query', () => {
submitter {
id
}
resourceId
type
user {
id
@ -350,7 +344,8 @@ describe('reports query', () => {
}
`
beforeAll(() => {
beforeAll(async () => {
await factory.cleanDatabase()
const { server } = createServer({
context: () => {
return {
@ -480,7 +475,6 @@ describe('reports query', () => {
submitter: expect.objectContaining({
id: 'user1',
}),
resourceId: 'auth1',
type: 'User',
user: expect.objectContaining({
id: 'auth1',
@ -495,7 +489,6 @@ describe('reports query', () => {
submitter: expect.objectContaining({
id: 'user1',
}),
resourceId: 'p1',
type: 'Post',
user: null,
post: expect.objectContaining({
@ -510,7 +503,6 @@ describe('reports query', () => {
submitter: expect.objectContaining({
id: 'user1',
}),
resourceId: 'c1',
type: 'Comment',
user: null,
post: null,

View File

@ -5,10 +5,8 @@ type REPORTED {
submitter: User
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN user")
# not yet supported
# resource: ReportReource
# resource: ReportResource
# @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource")
resourceId: ID
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource {.id}")
type: String
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN labels(resource)[0]")
user: User
@ -29,7 +27,7 @@ enum ReasonCategory {
}
# not yet supported
# union ReportReource = User | Post | Comment
# union ReportResource = User | Post | Comment
enum ReportOrdering {
createdAt_desc
@ -40,5 +38,5 @@ type Query {
}
type Mutation {
report(resourceId: ID!, reasonCategory: String!, reasonDescription: String!): REPORTED
report(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): REPORTED
}

View File

@ -649,7 +649,7 @@ 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

View File

@ -1,7 +0,0 @@
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! Date-time is this creation date and time.'
RETURN reported

View File

@ -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