mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Hide disabled comments and posts by default
I had to trick neo4j-graphql-js to return disabled and deleted fields by default on any neo4j node. If disabled is not queried, then we don't have it in our middleware and we're not able to filter it.
This commit is contained in:
parent
045b5b1660
commit
7d82b27aaa
@ -1,28 +0,0 @@
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
const includeId = async (resolve, root, args, context, resolveInfo) => {
|
||||
// Keeping the graphql resolveInfo untouched ensures that we don't add the
|
||||
// following attributes to the result set returned to the graphQL client.
|
||||
// We only want to pass these attributes to our resolver for internal
|
||||
// purposes e.g. authorization.
|
||||
const copy = cloneDeep(resolveInfo)
|
||||
|
||||
copy.fieldNodes[0].selectionSet.selections.unshift({
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'id' }
|
||||
})
|
||||
return resolve(root, args, context, copy)
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
User: (resolve, root, args, context, info) => {
|
||||
return includeId(resolve, root, args, context, info)
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
CreatePost: (resolve, root, args, context, info) => {
|
||||
return includeId(resolve, root, args, context, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/middleware/includedFieldsMiddleware.js
Normal file
29
src/middleware/includedFieldsMiddleware.js
Normal file
@ -0,0 +1,29 @@
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
const _includeFieldsRecursively = (selectionSet, includedFields) => {
|
||||
if (!selectionSet) return
|
||||
includedFields.forEach((includedField) => {
|
||||
selectionSet.selections.unshift({
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: includedField }
|
||||
})
|
||||
})
|
||||
selectionSet.selections.forEach((selection) => {
|
||||
_includeFieldsRecursively(selection.selectionSet, includedFields)
|
||||
})
|
||||
}
|
||||
|
||||
const includeFieldsRecursively = (includedFields) => {
|
||||
return (resolve, root, args, context, resolveInfo) => {
|
||||
const copy = cloneDeep(resolveInfo)
|
||||
copy.fieldNodes.forEach((fieldNode) => {
|
||||
_includeFieldsRecursively(fieldNode.selectionSet, includedFields)
|
||||
})
|
||||
return resolve(root, args, context, copy)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: includeFieldsRecursively(['id', 'disabled', 'deleted']),
|
||||
Mutation: includeFieldsRecursively(['id', 'disabled', 'deleted'])
|
||||
}
|
||||
@ -7,7 +7,7 @@ import dateTimeMiddleware from './dateTimeMiddleware'
|
||||
import xssMiddleware from './xssMiddleware'
|
||||
import permissionsMiddleware from './permissionsMiddleware'
|
||||
import userMiddleware from './userMiddleware'
|
||||
import idMiddleware from './idMiddleware'
|
||||
import includedFieldsMiddleware from './includedFieldsMiddleware'
|
||||
|
||||
export default schema => {
|
||||
let middleware = [
|
||||
@ -19,7 +19,7 @@ export default schema => {
|
||||
fixImageUrlsMiddleware,
|
||||
softDeleteMiddleware,
|
||||
userMiddleware,
|
||||
idMiddleware
|
||||
includedFieldsMiddleware
|
||||
]
|
||||
|
||||
// add permisions middleware at the first position (unless we're seeding)
|
||||
|
||||
@ -1,26 +1,43 @@
|
||||
const setDefaults = (args) => {
|
||||
const isModerator = ({ user }) => {
|
||||
return user && (user.role === 'moderator' || user.role === 'admin')
|
||||
}
|
||||
|
||||
const setDefaultFilters = (resolve, root, args, context, info) => {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
if (typeof args.disabled !== 'boolean') {
|
||||
|
||||
if (!isModerator(context)) {
|
||||
args.disabled = false
|
||||
}
|
||||
return args
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
|
||||
const hideDisabledComments = async (resolve, root, args, context, info) => {
|
||||
const { comments } = root
|
||||
if (!Array.isArray(comments)) return resolve(root, args, context, info)
|
||||
if (!isModerator(context)) {
|
||||
root.comments = comments.filter((comment) => {
|
||||
return !comment.disabled
|
||||
})
|
||||
}
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Post: (resolve, root, args, context, info) => {
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
},
|
||||
Comment: async (resolve, root, args, context, info) => {
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
},
|
||||
User: async (resolve, root, args, context, info) => {
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
}
|
||||
Post: setDefaultFilters,
|
||||
Comment: setDefaultFilters,
|
||||
User: setDefaultFilters
|
||||
},
|
||||
Mutation: async (resolve, root, args, context, info) => {
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
}
|
||||
args.disabled = false
|
||||
// TODO: remove as soon as our factories don't need this anymore
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
Post: hideDisabledComments,
|
||||
User: hideDisabledComments
|
||||
}
|
||||
|
||||
@ -9,14 +9,14 @@ let action
|
||||
|
||||
beforeEach(async () => {
|
||||
await Promise.all([
|
||||
factory.create('User', { role: 'user', email: 'user@example.org', password: '1234' }),
|
||||
factory.create('User', { id: 'u1', role: 'user', email: 'user@example.org', password: '1234' }),
|
||||
factory.create('User', { id: 'm1', role: 'moderator', email: 'moderator@example.org', password: '1234' })
|
||||
])
|
||||
await factory.authenticateAs({ email: 'user@example.org', password: '1234' })
|
||||
await Promise.all([
|
||||
factory.create('Post', { title: 'Deleted post', deleted: true }),
|
||||
factory.create('Post', { id: 'p2', title: 'Disabled post', deleted: false }),
|
||||
factory.create('Post', { title: 'Publicly visible post', deleted: false })
|
||||
await factory.create('Post', { id: 'p1', title: 'Deleted post', deleted: true }),
|
||||
await factory.create('Post', { id: 'p2', title: 'Disabled post', deleted: false }),
|
||||
await factory.create('Post', { id: 'p3', title: 'Publicly visible post', deleted: false })
|
||||
])
|
||||
const moderatorFactory = Factory()
|
||||
await moderatorFactory.authenticateAs({ email: 'moderator@example.org', password: '1234' })
|
||||
@ -62,9 +62,68 @@ describe('softDeleteMiddleware', () => {
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('hides deleted or disabled posts', async () => {
|
||||
const expected = { Post: [{ title: 'Publicly visible post' }] }
|
||||
await expect(action()).resolves.toEqual(expected)
|
||||
it('shows disabled but hides deleted posts', async () => {
|
||||
const expected = [
|
||||
{ title: 'Disabled post' },
|
||||
{ title: 'Publicly visible post' }
|
||||
]
|
||||
const { Post } = await action()
|
||||
await expect(Post).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
|
||||
describe('.comments', () => {
|
||||
beforeEach(async () => {
|
||||
query = '{ Post(id: "p3") { title comments { content } } }'
|
||||
|
||||
const asModerator = Factory()
|
||||
await asModerator.authenticateAs({ email: 'moderator@example.org', password: '1234' })
|
||||
await Promise.all([
|
||||
asModerator.create('Comment', { id: 'c1', content: 'Disabled comment' }),
|
||||
asModerator.create('Comment', { id: 'c2', content: 'Enabled comment on public post' })
|
||||
])
|
||||
await Promise.all([
|
||||
asModerator.relate('Comment', 'Author', { from: 'u1', to: 'c1' }),
|
||||
asModerator.relate('Comment', 'Post', { from: 'c1', to: 'p3' }),
|
||||
asModerator.relate('Comment', 'Author', { from: 'u1', to: 'c2' }),
|
||||
asModerator.relate('Comment', 'Post', { from: 'c2', to: 'p3' })
|
||||
])
|
||||
await asModerator.mutate('mutation { disable( id: "c1") }')
|
||||
})
|
||||
|
||||
describe('as user', () => {
|
||||
beforeEach(async () => {
|
||||
const headers = await login({ email: 'user@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('hides disabled comments', async () => {
|
||||
const expected = { Post: [ {
|
||||
title: 'Publicly visible post',
|
||||
comments: [
|
||||
{ content: 'Enabled comment on public post' }
|
||||
]
|
||||
}
|
||||
] }
|
||||
await expect(action()).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('as moderator', () => {
|
||||
beforeEach(async () => {
|
||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('shows disabled comments', async () => {
|
||||
const expected = [
|
||||
{ content: 'Enabled comment on public post' },
|
||||
{ content: 'Disabled comment' }
|
||||
]
|
||||
const { Post: [{ comments }] } = await action()
|
||||
|
||||
await expect(comments).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ type User {
|
||||
)
|
||||
|
||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true RETURN COUNT(r)")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
|
||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
@ -189,7 +189,7 @@ type Post {
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
|
||||
comments: [Comment]! @relation(name: "COMMENTS", direction: "IN")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) RETURN COUNT(r)")
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
|
||||
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
|
||||
@ -107,9 +107,6 @@ import Factory from './factories'
|
||||
asTick.create('Post', { id: 'p15' })
|
||||
])
|
||||
|
||||
const disableMutation = 'mutation { disable( id: "p11") }'
|
||||
await asModerator.mutate(disableMutation)
|
||||
|
||||
await Promise.all([
|
||||
f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }),
|
||||
f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }),
|
||||
@ -214,6 +211,12 @@ import Factory from './factories'
|
||||
f.relate('Comment', 'Post', { from: 'c7', to: 'p2' })
|
||||
])
|
||||
|
||||
const disableMutation = 'mutation($id: ID!) { disable(id: $id) }'
|
||||
await Promise.all([
|
||||
asModerator.mutate(disableMutation, { id: 'p11' }),
|
||||
asModerator.mutate(disableMutation, { id: 'c5' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
asTick.create('Report', { description: 'I don\'t like this comment', id: 'c1' }),
|
||||
asTrick.create('Report', { description: 'I don\'t like this post', id: 'p1' }),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user