This commit is contained in:
Wolfgang Huß 2019-03-12 18:45:49 +01:00
commit a6f96cad78
5 changed files with 76 additions and 39 deletions

View File

@ -6,9 +6,11 @@ import reports from './resolvers/reports.js'
import posts from './resolvers/posts.js' import posts from './resolvers/posts.js'
import moderation from './resolvers/moderation.js' import moderation from './resolvers/moderation.js'
export const typeDefs = export const typeDefs = fs
fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')) .readFileSync(
.toString('utf-8') process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')
)
.toString('utf-8')
export const resolvers = { export const resolvers = {
Query: { Query: {

View File

@ -1,12 +1,14 @@
import uniqueSlug from './slugify/uniqueSlug' import uniqueSlug from './slugify/uniqueSlug'
const isUniqueFor = (context, type) => { const isUniqueFor = (context, type) => {
return async (slug) => { return async slug => {
const session = context.driver.session() const session = context.driver.session()
const response = await session.run( const response = await session.run(
`MATCH(p:${type} {slug: $slug }) return p.slug`, { `MATCH(p:${type} {slug: $slug }) return p.slug`,
{
slug slug
}) }
)
session.close() session.close()
return response.records.length === 0 return response.records.length === 0
} }
@ -15,19 +17,27 @@ const isUniqueFor = (context, type) => {
export default { export default {
Mutation: { Mutation: {
CreatePost: async (resolve, root, args, context, info) => { CreatePost: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.title, isUniqueFor(context, 'Post')) args.slug =
args.slug ||
(await uniqueSlug(args.title, isUniqueFor(context, 'Post')))
return resolve(root, args, context, info) return resolve(root, args, context, info)
}, },
CreateUser: async (resolve, root, args, context, info) => { CreateUser: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'User')) args.slug =
args.slug ||
(await uniqueSlug(args.name, isUniqueFor(context, 'User')))
return resolve(root, args, context, info) return resolve(root, args, context, info)
}, },
CreateOrganization: async (resolve, root, args, context, info) => { CreateOrganization: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Organization')) args.slug =
args.slug ||
(await uniqueSlug(args.name, isUniqueFor(context, 'Organization')))
return resolve(root, args, context, info) return resolve(root, args, context, info)
}, },
CreateCategory: async (resolve, root, args, context, info) => { CreateCategory: async (resolve, root, args, context, info) => {
args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Category')) args.slug =
args.slug ||
(await uniqueSlug(args.name, isUniqueFor(context, 'Category')))
return resolve(root, args, context, info) return resolve(root, args, context, info)
} }
} }

View File

@ -8,7 +8,10 @@ const factory = Factory()
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { email: 'user@example.org', password: '1234' }) await factory.create('User', { email: 'user@example.org', password: '1234' })
await factory.create('User', { email: 'someone@example.org', password: '1234' }) await factory.create('User', {
email: 'someone@example.org',
password: '1234'
})
headers = await login({ email: 'user@example.org', password: '1234' }) headers = await login({ email: 'user@example.org', password: '1234' })
authenticatedClient = new GraphQLClient(host, { headers }) authenticatedClient = new GraphQLClient(host, { headers })
}) })
@ -26,7 +29,9 @@ describe('slugify', () => {
content: "Some content" content: "Some content"
) { slug } ) { slug }
}`) }`)
expect(response).toEqual({ CreatePost: { slug: 'i-am-a-brand-new-post' } }) expect(response).toEqual({
CreatePost: { slug: 'i-am-a-brand-new-post' }
})
}) })
describe('if slug exists', () => { describe('if slug exists', () => {
@ -48,12 +53,15 @@ describe('slugify', () => {
content: "Some content" content: "Some content"
) { slug } ) { slug }
}`) }`)
expect(response).toEqual({ CreatePost: { slug: 'pre-existing-post-1' } }) expect(response).toEqual({
CreatePost: { slug: 'pre-existing-post-1' }
})
}) })
describe('but if the client specifies a slug', () => { describe('but if the client specifies a slug', () => {
it('rejects CreatePost', async () => { it('rejects CreatePost', async () => {
await expect(authenticatedClient.request(`mutation { await expect(
authenticatedClient.request(`mutation {
CreatePost( CreatePost(
title: "Pre-existing post", title: "Pre-existing post",
content: "Some content", content: "Some content",
@ -73,28 +81,33 @@ describe('slugify', () => {
}`) }`)
} }
it('generates a slug based on name', async () => { it('generates a slug based on name', async () => {
await expect(action('CreateUser', 'name: "I am a user"')) await expect(
.resolves.toEqual({ CreateUser: { slug: 'i-am-a-user' } }) action('CreateUser', 'name: "I am a user"')
).resolves.toEqual({ CreateUser: { slug: 'i-am-a-user' } })
}) })
describe('if slug exists', () => { describe('if slug exists', () => {
beforeEach(async () => { beforeEach(async () => {
await action('CreateUser', 'name: "Pre-existing user", slug: "pre-existing-user"') await action(
'CreateUser',
'name: "Pre-existing user", slug: "pre-existing-user"'
)
}) })
it('chooses another slug', async () => { it('chooses another slug', async () => {
await expect(action( await expect(
'CreateUser', action('CreateUser', 'name: "pre-existing-user"')
'name: "pre-existing-user"' ).resolves.toEqual({ CreateUser: { slug: 'pre-existing-user-1' } })
)).resolves.toEqual({ CreateUser: { slug: 'pre-existing-user-1' } })
}) })
describe('but if the client specifies a slug', () => { describe('but if the client specifies a slug', () => {
it('rejects CreateUser', async () => { it('rejects CreateUser', async () => {
await expect(action( await expect(
'CreateUser', action(
'name: "Pre-existing user", slug: "pre-existing-user"' 'CreateUser',
)).rejects.toThrow('already exists') 'name: "Pre-existing user", slug: "pre-existing-user"'
)
).rejects.toThrow('already exists')
}) })
}) })
}) })

View File

@ -4,6 +4,18 @@ type Query {
currentUser: User currentUser: User
"Get the latest Network Statistics" "Get the latest Network Statistics"
statistics: Statistics! statistics: Statistics!
findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
statement: """
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
YIELD node as post, score
MATCH (post)<-[:WROTE]-(user:User)
WHERE score >= 0.2
AND NOT user.deleted = true AND NOT user.disabled = true
AND NOT post.deleted = true AND NOT post.disabled = true
RETURN post
LIMIT $limit
"""
)
} }
type Mutation { type Mutation {
"Get a JWT Token for the given Email and password" "Get a JWT Token for the given Email and password"

View File

@ -1,6 +1,5 @@
import { GraphQLClient, request } from 'graphql-request' import { GraphQLClient, request } from 'graphql-request'
import { getDriver } from '../../bootstrap/neo4j' import { getDriver } from '../../bootstrap/neo4j'
import createBadge from './badges.js' import createBadge from './badges.js'
import createUser from './users.js' import createUser from './users.js'
import createOrganization from './organizations.js' import createOrganization from './organizations.js'
@ -23,26 +22,24 @@ const authenticatedHeaders = async ({ email, password }, host) => {
} }
} }
const factories = { const factories = {
'Badge': createBadge, Badge: createBadge,
'User': createUser, User: createUser,
'Organization': createOrganization, Organization: createOrganization,
'Post': createPost, Post: createPost,
'Comment': createComment, Comment: createComment,
'Category': createCategory, Category: createCategory,
'Tag': createTag, Tag: createTag,
'Report': createReport Report: createReport
} }
export const cleanDatabase = async (options = {}) => { export const cleanDatabase = async (options = {}) => {
const { const { driver = getDriver() } = options
driver = getDriver()
} = options
const session = driver.session() const session = driver.session()
const cypher = 'MATCH (n) DETACH DELETE n' const cypher = 'MATCH (n) DETACH DELETE n'
try { try {
return await session.run(cypher) return await session.run(cypher)
} catch (error) { } catch (error) {
throw (error) throw error
} finally { } finally {
session.close() session.close()
} }
@ -63,7 +60,10 @@ export default function Factory (options = {}) {
factories, factories,
lastResponse: null, lastResponse: null,
async authenticateAs ({ email, password }) { async authenticateAs ({ email, password }) {
const headers = await authenticatedHeaders({ email, password }, seedServerHost) const headers = await authenticatedHeaders(
{ email, password },
seedServerHost
)
this.lastResponse = headers this.lastResponse = headers
this.graphQLClient = new GraphQLClient(seedServerHost, { headers }) this.graphQLClient = new GraphQLClient(seedServerHost, { headers })
return this return this