diff --git a/.travis.yml b/.travis.yml index f4a01b147..e699197cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,10 +16,12 @@ before_install: - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/bin + - yarn global add wait-on install: - docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT --target production -t humanconnection/nitro-backend:latest . - docker-compose -f docker-compose.yml -f docker-compose.travis.yml up -d + - wait-on http://localhost:7474 && docker-compose exec neo4j migrate script: - docker-compose exec backend yarn run lint diff --git a/README.md b/README.md index 1b12562d2..7f4676a6c 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,15 @@ > This Prototype tries to resolve the biggest hurdle of connecting > our services together. This is not possible in a sane way using -> our current approach. -> -> With this Prototype we can explore using the combination of +> our current approach. +> +> With this Prototype we can explore using the combination of > GraphQL and the Neo4j Graph Database for achieving the connected > nature of a social graph with better development experience as we > do not need to connect data by our own any more through weird table > structures etc. -> +> > #### Advantages: > - easer data structure > - better connected data @@ -19,10 +19,10 @@ > - more performant and better to understand API > - better API client that uses caching > -> We still need to evaluate the drawbacks and estimate the development +> We still need to evaluate the drawbacks and estimate the development > cost of such an approach -## How to get in touch +## How to get in touch Connect with other developers over [Discord](https://discord.gg/6ub73U3) ## Quick Start @@ -35,6 +35,10 @@ Before you start, fork the repository using the fork button above, then clone it Run: ```sh docker-compose up + +# create indices etc. +docker-compose exec neo4j migrate + # if you want seed data # open another terminal and run docker-compose exec backend yarn run db:seed @@ -94,7 +98,7 @@ _.env_ ```yaml NEO4J_URI=bolt://localhost:7687 -NEO4J_USER=neo4j +NEO4J_USERNAME=neo4j NEO4J_PASSWORD=letmein ``` @@ -116,7 +120,7 @@ Just set `MOCK=true` inside `.env` or pass it on application start. ## Seed and Reset the Database -Optionally you can seed the GraphQL service by executing mutations that +Optionally you can seed the GraphQL service by executing mutations that will write sample data to the database: ```bash @@ -152,5 +156,5 @@ npm run test - [x] check if sorting is working - [x] check if pagination is working - [ ] check if upload is working (using graphql-yoga?) -- [x] evaluate middleware +- [x] evaluate middleware - [ ] ignore Posts and Comments by blacklisted Users diff --git a/docker-compose.override.yml b/docker-compose.override.yml index ef7d52c7e..b972c31f6 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -11,6 +11,13 @@ services: - /nitro-backend/node_modules command: yarn run dev neo4j: + environment: + - NEO4J_AUTH=none ports: - 7687:7687 - 7474:7474 + volumes: + - neo4j-data:/data + +volumes: + neo4j-data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 000000000..c4f5dc4f5 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,9 @@ +version: "3.7" + +services: + neo4j: + environment: + - NEO4J_PASSWORD=letmein + backend: + environment: + - NEO4J_PASSWORD=letmein diff --git a/docker-compose.travis.yml b/docker-compose.travis.yml index 761a2aa64..e1998f6dd 100644 --- a/docker-compose.travis.yml +++ b/docker-compose.travis.yml @@ -1,6 +1,12 @@ version: "3.7" services: + neo4j: + environment: + - NEO4J_AUTH=none + ports: + - 7687:7687 + - 7474:7474 backend: image: humanconnection/nitro-backend:builder build: diff --git a/docker-compose.yml b/docker-compose.yml index 6905bb893..1e8c9158c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,14 +27,7 @@ services: context: neo4j networks: - hc-network - volumes: - - neo4j-data:/data - environment: - - NEO4J_AUTH=none networks: hc-network: name: hc-network - -volumes: - neo4j-data: diff --git a/neo4j/Dockerfile b/neo4j/Dockerfile index cb7fd228f..f6e71811b 100644 --- a/neo4j/Dockerfile +++ b/neo4j/Dockerfile @@ -1,2 +1,3 @@ FROM neo4j:3.5.0 RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.1/apoc-3.5.0.1-all.jar -P plugins/ +COPY migrate.sh /usr/local/bin/migrate diff --git a/neo4j/migrate.sh b/neo4j/migrate.sh new file mode 100755 index 000000000..1ec5212ad --- /dev/null +++ b/neo4j/migrate.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# If the user has the password `neo4j` this is a strong indicator, that we are +# the initial default user. Before we can create constraints, we have to change +# the default password. This is a security feature of neo4j. +if echo ":exit" | cypher-shell --password neo4j 2> /dev/null ; then + echo "CALL dbms.security.changePassword('${NEO4J_PASSWORD}');" | cypher-shell --password neo4j +fi + +set -e + +echo ' +CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]); +CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE; +CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE; +CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE; +CREATE CONSTRAINT ON (o:Organization) ASSERT o.slug IS UNIQUE; +' | cypher-shell diff --git a/package.json b/package.json index b65292acd..2578052d3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js", "lint": "eslint src --config .eslintrc.js", "test": "nyc --reporter=text-lcov yarn run test:jest", + "test:cypress": "run-p --race test:before:*", "test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 babel-node src/ 2> /dev/null", "test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 PERMISSIONS=disabled babel-node src/ 2> /dev/null", "test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand", @@ -21,9 +22,8 @@ "test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --", "test:coverage": "nyc report --reporter=text-lcov > coverage.lcov", "db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js", - "db:script:reset": "wait-on tcp:4001 && babel-node src/seed/reset-db.js", - "db:seed": "$npm_package_config_no_auth run-p --race dev db:script:seed", - "db:reset": "$npm_package_config_no_auth run-p --race dev db:script:reset" + "db:reset": "babel-node src/seed/reset-db.js", + "db:seed": "$npm_package_config_no_auth run-p --race dev db:script:seed" }, "author": "Human Connection gGmbH", "license": "MIT", diff --git a/src/bootstrap/neo4j.js b/src/bootstrap/neo4j.js index 766c12065..935449a0a 100644 --- a/src/bootstrap/neo4j.js +++ b/src/bootstrap/neo4j.js @@ -1,20 +1,18 @@ import { v1 as neo4j } from 'neo4j-driver' +import dotenv from 'dotenv' + +dotenv.config() let driver -export default function () { - return { - getDriver () { - if (!driver) { - driver = neo4j.driver( - process.env.NEO4J_URI || 'bolt://localhost:7687', - neo4j.auth.basic( - process.env.NEO4J_USER || 'neo4j', - process.env.NEO4J_PASSWORD || 'neo4j' - ) - ) - } - return driver - } +export function getDriver (options = {}) { + const { + uri = process.env.NEO4J_URI || 'bolt://localhost:7687', + username = process.env.NEO4J_USERNAME || 'neo4j', + password = process.env.NEO4J_PASSWORD || 'neo4j' + } = options + if (!driver) { + driver = neo4j.driver(uri, neo4j.auth.basic(username, password)) } + return driver } diff --git a/src/graphql-schema.js b/src/graphql-schema.js index 8b5f369e0..521ac97bd 100644 --- a/src/graphql-schema.js +++ b/src/graphql-schema.js @@ -6,6 +6,7 @@ import generateJwt from './jwt/generateToken' import uuid from 'uuid/v4' import { fixUrl } from './middleware/fixImageUrlsMiddleware' import { AuthenticationError } from 'apollo-server' +import { neo4jgraphql } from 'neo4j-graphql-js' export const typeDefs = fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')) @@ -161,6 +162,22 @@ export const resolvers = { // TODO: output Report compatible object return data + }, + CreatePost: async (object, params, ctx, resolveInfo) => { + const result = await neo4jgraphql(object, params, ctx, resolveInfo, false) + + const session = ctx.driver.session() + await session.run( + 'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' + + 'MERGE (post)<-[:WROTE]-(author) ' + + 'RETURN author', { + userId: ctx.user.id, + postId: result.id + }) + session.close() + + return result } + } } diff --git a/src/graphql-schema.spec.js b/src/graphql-schema.spec.js index 69073444c..463d91af2 100644 --- a/src/graphql-schema.spec.js +++ b/src/graphql-schema.spec.js @@ -1,7 +1,20 @@ -import { request } from 'graphql-request' -import { create, cleanDatabase } from './seed/factories' +import Factory from './seed/factories' import jwt from 'jsonwebtoken' -import { host } from './jest/helpers' +import { host, login } from './jest/helpers' +import { GraphQLClient, request } from 'graphql-request' + +const factory = Factory() + +beforeEach(async () => { + await factory.create('user', { + email: 'test@example.org', + password: '1234' + }) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) describe('login', () => { const mutation = (params) => { @@ -14,48 +27,85 @@ describe('login', () => { }` } - describe('given an existing user', () => { - beforeEach(async () => { - await create('user', { - email: 'test@example.org', - password: '1234' + describe('ask for a `token`', () => { + describe('with valid email/password combination', () => { + it('responds with a JWT token', async () => { + const data = await request(host, mutation({ + email: 'test@example.org', + password: '1234' + })) + const { token } = data.login + jwt.verify(token, process.env.JWT_SECRET, (err, data) => { + expect(data.email).toEqual('test@example.org') + expect(err).toBeNull() + }) }) }) - afterEach(async () => { - await cleanDatabase() + describe('with a valid email but incorrect password', () => { + it('responds with "Incorrect email address or password."', async () => { + await expect( + request(host, mutation({ + email: 'test@example.org', + password: 'wrong' + })) + ).rejects.toThrow('Incorrect email address or password.') + }) }) - describe('asking for a `token`', () => { - describe('with valid email/password combination', () => { - it('responds with a JWT token', async () => { - const data = await request(host, mutation({ email: 'test@example.org', password: '1234' })) - const { token } = data.login - jwt.verify(token, process.env.JWT_SECRET, (err, data) => { - expect(data.email).toEqual('test@example.org') - expect(err).toBeNull() - }) - }) - }) - - describe('with a valid email but incorrect password', () => { - it('responds with "Incorrect email address or password."', async () => { - try { - await request(host, mutation({ email: 'test@example.org', password: 'wrong' })) - } catch (error) { - expect(error.response.errors[0].message).toEqual('Incorrect email address or password.') - } - }) - }) - - describe('with a non-existing email', () => { - it('responds with "Incorrect email address or password."', async () => { - try { - await request(host, mutation({ email: 'non-existent@example.org', password: 'wrong' })) - } catch (error) { - expect(error.response.errors[0].message).toEqual('Incorrect email address or password.') - } - }) + describe('with a non-existing email', () => { + it('responds with "Incorrect email address or password."', async () => { + await expect( + request(host, mutation({ + email: 'non-existent@example.org', + password: 'wrong' + })) + ).rejects.toThrow('Incorrect email address or password.') + }) + }) + }) +}) + +describe('CreatePost', () => { + describe('unauthenticated', () => { + let client + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect(client.request(`mutation { + CreatePost( + title: "I am a post", + content: "Some content" + ) { slug } + }`)).rejects.toThrow('Not Authorised') + }) + + describe('authenticated', () => { + let headers + let response + beforeEach(async () => { + headers = await login({ email: 'test@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + response = await client.request(`mutation { + CreatePost( + title: "A title", + content: "Some content" + ) { title, content } + }`, { headers }) + }) + + it('creates a post', () => { + expect(response).toEqual({ CreatePost: { title: 'A title', content: 'Some content' } }) + }) + + it('assigns the authenticated user as author', async () => { + const { User } = await client.request(`{ + User(email:"test@example.org") { + contributions { + title + } + } + }`, { headers }) + expect(User).toEqual([ { contributions: [ { title: 'A title' } ] } ]) }) }) }) diff --git a/src/jest/helpers.js b/src/jest/helpers.js index 01a26e9d3..ff6a535e2 100644 --- a/src/jest/helpers.js +++ b/src/jest/helpers.js @@ -1,8 +1,10 @@ import { request } from 'graphql-request' +// this is the to-be-tested server host +// not to be confused with the seeder host export const host = 'http://127.0.0.1:4123' -export async function authenticatedHeaders ({ email, password }) { +export async function login ({ email, password }) { const mutation = ` mutation { login(email:"${email}", password:"${password}"){ diff --git a/src/middleware/idMiddleware.js b/src/middleware/idMiddleware.js new file mode 100644 index 000000000..d6dac0580 --- /dev/null +++ b/src/middleware/idMiddleware.js @@ -0,0 +1,24 @@ +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: (resolve, root, args, context, info) => { + return includeId(resolve, root, args, context, info) + }, + Mutation: (resolve, root, args, context, info) => { + return includeId(resolve, root, args, context, info) + } +} diff --git a/src/middleware/index.js b/src/middleware/index.js index bd95f0e93..5cc20f969 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -7,6 +7,7 @@ import dateTimeMiddleware from './dateTimeMiddleware' import xssMiddleware from './xssMiddleware' import permissionsMiddleware from './permissionsMiddleware' import userMiddleware from './userMiddleware' +import idMiddleware from './idMiddleware' export default schema => { let middleware = [ @@ -17,7 +18,8 @@ export default schema => { xssMiddleware, fixImageUrlsMiddleware, softDeleteMiddleware, - userMiddleware + userMiddleware, + idMiddleware ] // add permisions middleware at the first position (unless we're seeding) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 0dd4a9a86..1a3f04ceb 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -16,7 +16,7 @@ const isModerator = rule()(async (parent, args, ctx, info) => { }) */ -const isOwner = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => { +const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => { return ctx.user.id === parent.id }) @@ -28,13 +28,16 @@ const permissions = shield({ // customers: and(isAuthenticated, isAdmin) }, Mutation: { + CreatePost: isAuthenticated, + // TODO UpdatePost: isOwner, + // TODO DeletePost: isOwner, report: isAuthenticated // addFruitToBasket: isAuthenticated // CreateUser: allow, }, User: { - email: isOwner, - password: isOwner + email: isMyOwn, + password: isMyOwn } // Post: isAuthenticated }) diff --git a/src/middleware/permissionsMiddleware.spec.js b/src/middleware/permissionsMiddleware.spec.js index cf86d11c9..a22f2bb72 100644 --- a/src/middleware/permissionsMiddleware.spec.js +++ b/src/middleware/permissionsMiddleware.spec.js @@ -1,16 +1,18 @@ -import { create, cleanDatabase } from '../seed/factories' -import { host, authenticatedHeaders } from '../jest/helpers' +import Factory from '../seed/factories' +import { host, login } from '../jest/helpers' import { GraphQLClient } from 'graphql-request' +const factory = Factory() + describe('authorization', () => { describe('given two existing users', () => { beforeEach(async () => { - await create('user', { + await factory.create('user', { email: 'owner@example.org', name: 'Owner', password: 'iamtheowner' }) - await create('user', { + await factory.create('user', { email: 'someone@example.org', name: 'Someone else', password: 'else' @@ -18,51 +20,63 @@ describe('authorization', () => { }) afterEach(async () => { - await cleanDatabase() + await factory.cleanDatabase() }) describe('access email address', () => { let headers = {} - const action = async (headers) => { + let loginCredentials = null + const action = async () => { + if (loginCredentials) { + headers = await login(loginCredentials) + } const graphQLClient = new GraphQLClient(host, { headers }) - return graphQLClient.request(`{ - User(name: "Owner") { - email - } - }`) + return graphQLClient.request('{User(name: "Owner") { email } }') } describe('not logged in', async () => { + it('rejects', async () => { + await expect(action()).rejects.toThrow('Not Authorised!') + }) + it('does not expose the owner\'s email address', async () => { try { - await action(headers) + await action() } catch (error) { - expect(error.response.errors[0].message).toEqual('Not Authorised!') expect(error.response.data).toEqual({ User: [ { email: null } ] }) } }) }) describe('as owner', () => { - it('exposes the owner\'s email address', async () => { - headers = await authenticatedHeaders({ + beforeEach(() => { + loginCredentials = { email: 'owner@example.org', password: 'iamtheowner' - }) - expect(await action(headers)).toEqual({ User: [ { email: 'owner@example.org' } ] }) + } + }) + + it('exposes the owner\'s email address', async () => { + await expect(action()).resolves.toEqual({ User: [ { email: 'owner@example.org' } ] }) }) }) - describe('as someone else', () => { - it('does not expose the owner\'s email address', async () => { - headers = await authenticatedHeaders({ + describe('authenticated as another user', () => { + beforeEach(async () => { + loginCredentials = { email: 'someone@example.org', password: 'else' - }) + } + }) + + it('rejects', async () => { + await expect(action()).rejects.toThrow('Not Authorised!') + }) + + it('does not expose the owner\'s email address', async () => { try { - await action(headers) + await action() } catch (error) { - expect(error.response.errors[0].message).toEqual('Not Authorised!') expect(error.response.data).toEqual({ User: [ { email: null } ] }) } }) diff --git a/src/middleware/sluggifyMiddleware.js b/src/middleware/sluggifyMiddleware.js index f059cb680..1a9177daa 100644 --- a/src/middleware/sluggifyMiddleware.js +++ b/src/middleware/sluggifyMiddleware.js @@ -1,40 +1,34 @@ -import slug from 'slug' +import uniqueSlug from './slugify/uniqueSlug' + +const isUniqueFor = (context, type) => { + return async (slug) => { + const session = context.driver.session() + const response = await session.run( + `MATCH(p:${type} {slug: $slug }) return p.slug`, { + slug + }) + session.close() + return response.records.length === 0 + } +} export default { Mutation: { CreatePost: async (resolve, root, args, context, info) => { - args.slug = slug(args.title, { - lower: true - }) - const result = await resolve(root, args, context, info) - return result + args.slug = args.slug || await uniqueSlug(args.title, isUniqueFor(context, 'Post')) + return resolve(root, args, context, info) }, CreateUser: async (resolve, root, args, context, info) => { - if (!args.slug) { - args.slug = slug(args.name, { - lower: true - }) - } - const result = await resolve(root, args, context, info) - return result + args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'User')) + return resolve(root, args, context, info) }, CreateOrganization: async (resolve, root, args, context, info) => { - if (!args.slug) { - args.slug = slug(args.name, { - lower: true - }) - } - const result = await resolve(root, args, context, info) - return result + args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Organization')) + return resolve(root, args, context, info) }, CreateCategory: async (resolve, root, args, context, info) => { - if (!args.slug) { - args.slug = slug(args.name, { - lower: true - }) - } - const result = await resolve(root, args, context, info) - return result + args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Category')) + return resolve(root, args, context, info) } } } diff --git a/src/middleware/slugify/uniqueSlug.js b/src/middleware/slugify/uniqueSlug.js new file mode 100644 index 000000000..8b04edc6f --- /dev/null +++ b/src/middleware/slugify/uniqueSlug.js @@ -0,0 +1,15 @@ +import slugify from 'slug' +export default async function uniqueSlug (string, isUnique) { + let slug = slugify(string, { + lower: true + }) + if (await isUnique(slug)) return slug + + let count = 0 + let uniqueSlug + do { + count += 1 + uniqueSlug = `${slug}-${count}` + } while (!await isUnique(uniqueSlug)) + return uniqueSlug +} diff --git a/src/middleware/slugify/uniqueSlug.spec.js b/src/middleware/slugify/uniqueSlug.spec.js new file mode 100644 index 000000000..190899795 --- /dev/null +++ b/src/middleware/slugify/uniqueSlug.spec.js @@ -0,0 +1,18 @@ +import uniqueSlug from './uniqueSlug' + +describe('uniqueSlug', () => { + it('slugifies given string', () => { + const string = 'Hello World' + const isUnique = jest.fn() + .mockResolvedValue(true) + expect(uniqueSlug(string, isUnique)).resolves.toEqual('hello-world') + }) + + it('increments slugified string until unique', () => { + const string = 'Hello World' + const isUnique = jest.fn() + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(true) + expect(uniqueSlug(string, isUnique)).resolves.toEqual('hello-world-1') + }) +}) diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js new file mode 100644 index 000000000..aaa09d29a --- /dev/null +++ b/src/middleware/slugifyMiddleware.spec.js @@ -0,0 +1,102 @@ +import Factory from '../seed/factories' +import { host, login } from '../jest/helpers' +import { GraphQLClient } from 'graphql-request' + +let authenticatedClient +let headers +const factory = Factory() + +beforeEach(async () => { + await factory.create('user', { email: 'user@example.org', password: '1234' }) + await factory.create('user', { email: 'someone@example.org', password: '1234' }) + headers = await login({ email: 'user@example.org', password: '1234' }) + authenticatedClient = new GraphQLClient(host, { headers }) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('slugify', () => { + describe('CreatePost', () => { + it('generates a slug based on title', async () => { + const response = await authenticatedClient.request(`mutation { + CreatePost( + title: "I am a brand new post", + content: "Some content" + ) { slug } + }`) + expect(response).toEqual({ CreatePost: { slug: 'i-am-a-brand-new-post' } }) + }) + + describe('if slug exists', () => { + beforeEach(async () => { + const asSomeoneElse = await Factory().authenticateAs({ + email: 'someone@example.org', + password: '1234' + }) + await asSomeoneElse.create('post', { + title: 'Pre-existing post', + slug: 'pre-existing-post' + }) + }) + + it('chooses another slug', async () => { + const response = await authenticatedClient.request(`mutation { + CreatePost( + title: "Pre-existing post", + content: "Some content" + ) { slug } + }`) + expect(response).toEqual({ CreatePost: { slug: 'pre-existing-post-1' } }) + }) + + describe('but if the client specifies a slug', () => { + it('rejects CreatePost', async () => { + await expect(authenticatedClient.request(`mutation { + CreatePost( + title: "Pre-existing post", + content: "Some content", + slug: "pre-existing-post" + ) { slug } + }`) + ).rejects.toThrow('already exists') + }) + }) + }) + }) + + describe('CreateUser', () => { + const action = async (mutation, params) => { + return authenticatedClient.request(`mutation { + ${mutation}(password: "yo", ${params}) { slug } + }`) + } + it('generates a slug based on name', async () => { + await expect(action('CreateUser', 'name: "I am a user"')) + .resolves.toEqual({ CreateUser: { slug: 'i-am-a-user' } }) + }) + + describe('if slug exists', () => { + beforeEach(async () => { + await action('CreateUser', 'name: "Pre-existing user", slug: "pre-existing-user"') + }) + + it('chooses another slug', async () => { + await expect(action( + 'CreateUser', + 'name: "pre-existing-user"' + )).resolves.toEqual({ CreateUser: { slug: 'pre-existing-user-1' } }) + }) + + describe('but if the client specifies a slug', () => { + it('rejects CreateUser', async () => { + await expect(action( + 'CreateUser', + 'name: "Pre-existing user", slug: "pre-existing-user"' + )).rejects.toThrow('already exists') + }) + }) + }) + }) +}) diff --git a/src/middleware/userMiddleware.js b/src/middleware/userMiddleware.js index cf42709e9..55b181bc9 100644 --- a/src/middleware/userMiddleware.js +++ b/src/middleware/userMiddleware.js @@ -1,5 +1,4 @@ import createOrUpdateLocations from './nodes/locations' -import find from 'lodash/find' export default { Mutation: { @@ -12,44 +11,6 @@ export default { const result = await resolve(root, args, context, info) await createOrUpdateLocations(args.id, args.locationName, context.driver) return result - }, - CreatePost: async (resolve, root, args, context, info) => { - const result = await resolve(root, args, context, info) - - const session = context.driver.session() - await session.run( - 'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' + - 'MERGE (post)<-[:WROTE]-(author) ' + - 'RETURN author', { - userId: context.user.id, - postId: result.id - }) - session.close() - - return result - } - }, - Query: { - User: async (resolve, root, args, context, info) => { - let isIdPresent - let removeIdFromResult - try { - isIdPresent = find(info.fieldNodes[0].selectionSet.selections, item => item.name.value === 'id') - if (!isIdPresent) { - // add id to request as the user did not ask but we need it - info.fieldNodes[0].selectionSet.selections.unshift({ - kind: 'Field', - name: { kind: 'Name', value: 'id' } - }) - removeIdFromResult = true - } - } catch (err) {} - const result = await resolve(root, args, context, info) - if (!isIdPresent && removeIdFromResult) { - // remove id if the user did not ask for it - info.fieldNodes[0].selectionSet.selections.shift() - } - return result } } } diff --git a/src/seed/data/badges.js b/src/seed/data/badges.js deleted file mode 100644 index bb3ad06c7..000000000 --- a/src/seed/data/badges.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function (data) { - return ` - mutation { - b1: CreateBadge(id: "b1", key: "indiegogo_en_racoon", type: crowdfunding, status: permanent, icon: "/img/badges/indiegogo_en_racoon.svg") { id } - b2: CreateBadge(id: "b2", key: "indiegogo_en_rabbit", type: crowdfunding, status: permanent, icon: "/img/badges/indiegogo_en_rabbit.svg") { id } - b3: CreateBadge(id: "b3", key: "indiegogo_en_wolf", type: crowdfunding, status: permanent, icon: "/img/badges/indiegogo_en_wolf.svg") { id } - b4: CreateBadge(id: "b4", key: "indiegogo_en_bear", type: crowdfunding, status: permanent, icon: "/img/badges/indiegogo_en_bear.svg") { id } - b5: CreateBadge(id: "b5", key: "indiegogo_en_turtle", type: crowdfunding, status: permanent, icon: "/img/badges/indiegogo_en_turtle.svg") { id } - b6: CreateBadge(id: "b6", key: "indiegogo_en_rhino", type: crowdfunding, status: permanent, icon: "/img/badges/indiegogo_en_rhino.svg") { id } - } - ` -} diff --git a/src/seed/data/categories.js b/src/seed/data/categories.js deleted file mode 100644 index a21f0f3d6..000000000 --- a/src/seed/data/categories.js +++ /dev/null @@ -1,22 +0,0 @@ -export default function (data) { - return ` - mutation { - cat1: CreateCategory( id: "cat1", name: "Just For Fun", slug: "justforfun", icon: "smile" ) { name } - cat2: CreateCategory( id: "cat2", name: "Happyness & Values", slug: "happyness-values", icon: "heart-o" ) { name } - cat3: CreateCategory( id: "cat3", name: "Health & Wellbeing", slug: "health-wellbeing", icon: "medkit" ) { name } - cat4: CreateCategory( id: "cat4", name: "Environment & Nature", slug: "environment-nature", icon: "tree" ) { name } - cat5: CreateCategory( id: "cat5", name: "Animal Protection", slug: "animalprotection", icon: "paw" ) { name } - cat6: CreateCategory( id: "cat6", name: "Humanrights Justice", slug: "humanrights-justice", icon: "balance-scale" ) { name } - cat7: CreateCategory( id: "cat7", name: "Education & Sciences", slug: "education-sciences", icon: "graduation-cap" ) { name } - cat8: CreateCategory( id: "cat8", name: "Cooperation & Development", slug: "cooperation-development", icon: "users" ) { name } - cat9: CreateCategory( id: "cat9", name: "Democracy & Politics", slug: "democracy-politics", icon: "university" ) { name } - cat10: CreateCategory( id: "cat10", name: "Economy & Finances", slug: "economy-finances", icon: "money" ) { name } - cat11: CreateCategory( id: "cat11", name: "Energy & Technology", slug: "energy-technology", icon: "flash" ) { name } - cat12: CreateCategory( id: "cat12", name: "IT, Internet & Data Privacy", slug: "it-internet-dataprivacy", icon: "mouse-pointer" ) { name } - cat13: CreateCategory( id: "cat13", name: "Art, Curlure & Sport", slug: "art-culture-sport", icon: "paint-brush" ) { name } - cat14: CreateCategory( id: "cat14", name: "Freedom of Speech", slug: "freedomofspeech", icon: "bullhorn" ) { name } - cat15: CreateCategory( id: "cat15", name: "Consumption & Sustainability", slug: "consumption-sustainability", icon: "shopping-cart" ) { name } - cat16: CreateCategory( id: "cat16", name: "Global Peace & Nonviolence", slug: "globalpeace-nonviolence", icon: "angellist" ) { name } - } - ` -} diff --git a/src/seed/data/comments.js b/src/seed/data/comments.js deleted file mode 100644 index ac1849b86..000000000 --- a/src/seed/data/comments.js +++ /dev/null @@ -1,80 +0,0 @@ -import faker from 'faker' - -/** - * TODO: add a comment automatically to the correct post and relate it to the current user - */ -export default function (data) { - return ` - mutation { - c1: CreateComment( - id: "c1", - content: "

da stimm ich dir zu. Mir ging das auch nie in den kopf, und hatte jesus nie als gott gesehen

" - ) { id } - c1_u1: AddCommentAuthor(from: { id: "u3" }, to: { id: "c1" }) { from { id } } - c1_p1: AddCommentPost( - from: { id: "c1" }, - to: { id: "p1" } - ) { from { id } } - - c2: CreateComment( - id: "c2", - content: "

Schön das es dich gibt ❤️❤️❤️❤️❤️❤️❤️❤️❤️

" - ) { id } - c2_u1: AddCommentAuthor(from: { id: "u1" }, to: { id: "c2" }) { from { id } } - c2_p1: AddCommentPost( - from: { id: "c2" }, - to: { id: "p1" } - ) { from { id } } - - c3: CreateComment( - id: "c3", - content: "

Hi Dieter,

danke für Deine Info. Hast Du mal ein Foto von Deinem Cabrio mit dem Logo drauf?

" - ) { id } - c3_u2: AddCommentAuthor(from: { id: "u1" }, to: { id: "c3" }) { from { id } } - c3_p3: AddCommentPost( - from: { id: "c3" }, - to: { id: "p3" } - ) { from { id } } - - c4: CreateComment( - id: "c4", - content: "

Das Zusammenführen aller Gruppen, die mit uns am gleichen Strang in die gleiche Richtung ziehen, in eine gemeinsame Adressenstruktur sehe ich auch als Haupt - Aufgabe für unsere neue Netzwerkbildung an.

" - ) { id } - c4_u3: AddCommentAuthor(from: { id: "u4" }, to: { id: "c4" }) { from { id } } - c4_p2: AddCommentPost( - from: { id: "c4" }, - to: { id: "p2" } - ) { from { id } } - - c5: CreateComment( - id: "c5", - content: "${faker.lorem.paragraph()}" - ) { id } - c5_u4: AddCommentAuthor(from: { id: "u4" }, to: { id: "c5" }) { from { id } } - c5_p3: AddCommentPost( - from: { id: "c5" }, - to: { id: "p3" } - ) { from { id } } - - c6: CreateComment( - id: "c6", - content: "${faker.lorem.paragraph()}" - ) { id } - c6_u3: AddCommentAuthor(from: { id: "u3" }, to: { id: "c6" }) { from { id } } - c6_p4: AddCommentPost( - from: { id: "c6" }, - to: { id: "p4" } - ) { from { id } } - - c7: CreateComment( - id: "c7", - content: "${faker.lorem.paragraph()}" - ) { id } - c7_u2: AddCommentAuthor(from: { id: "u2" }, to: { id: "c7" }) { from { id } } - c7_p2: AddCommentPost( - from: { id: "c7" }, - to: { id: "p2" } - ) { from { id } } - } - ` -} diff --git a/src/seed/data/index.js b/src/seed/data/index.js deleted file mode 100644 index d3f5d15d7..000000000 --- a/src/seed/data/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import gql from 'graphql-tag' -import asyncForEach from '../../helpers/asyncForEach' - -const seed = { - Badge: require('./badges.js').default, - Category: require('./categories.js').default, - Tags: require('./tags.js').default, - - User: require('./users.js').default, - UserBadges: require('./users-badges.js').default, - UserBlacklist: require('./users-blacklist.js').default, - UserFollows: require('./users-follows.js').default, - UserFriends: require('./users-friends.js').default, - - Organization: require('./organizations.js').default, - Post: require('./posts.js').default, - Comment: require('./comments.js').default, - UserShouts: require('./users-shouts.js').default - - // Reports: require('./reports.js').default -} - -let data = {} - -export default async function (client) { - // iterate through seeds - await asyncForEach(Object.keys(seed), async key => { - const mutations = seed[key] - try { - const res = await client - .mutate({ - mutation: gql(mutations(data)) - }) - data[key] = Object.assign(data[key] || {}, res.data) - } catch (err) { - /* eslint-disable-next-line no-console */ - console.error(err) - process.exit(1) - } - }) - /* eslint-disable-next-line no-console */ - console.log('Seeded Data...') -} diff --git a/src/seed/data/organizations.js b/src/seed/data/organizations.js deleted file mode 100644 index b8b9075d4..000000000 --- a/src/seed/data/organizations.js +++ /dev/null @@ -1,51 +0,0 @@ -export default () => { - return `mutation { - o1: CreateOrganization( - id: "o1", - name: "Democracy Deutschland", - description: "Description for democracy-deutschland.", - disabled: false, - deleted: false - ) { name } - o2: CreateOrganization( - id: "o2", - name: "Human-Connection", - description: "Description for human-connection.", - disabled: false, - deleted: false - ) { name } - o3: CreateOrganization( - id: "o3", - name: "Pro Veg", - description: "Description for pro-veg.", - disabled: false, - deleted: false - ) { name } - o4: CreateOrganization( - id: "o4", - name: "Greenpeace", - description: "Description for greenpeace.", - disabled: false, - deleted: false - ) { name } - - u1_c_o1: AddOrganizationCreatedBy( - from: { id: "u1" }, - to: { id: "o1" } - ) { from { id } } - u1_c_o2: AddOrganizationCreatedBy( - from: { id: "u1" }, - to: { id: "o2" } - ) { from { id } } - - u2_o_o1: AddOrganizationOwnedBy( - from: { id: "u2" }, - to: { id: "o2" } - ) { from { id } } - u2_c_o3: AddOrganizationOwnedBy( - from: { id: "u2" }, - to: { id: "o3" } - ) { from { id } } - } -` -} diff --git a/src/seed/data/posts.js b/src/seed/data/posts.js deleted file mode 100644 index 8624c422d..000000000 --- a/src/seed/data/posts.js +++ /dev/null @@ -1,112 +0,0 @@ -import faker from 'faker' - -export default function (data) { - return ` - mutation { - p1: CreatePost( - id: "p1", - title: "Gedanken eines Polizisten zum Einsatz im Hambacher Forst", - content: "

Diese Zukunftsstadt ist real und keine Computer-Animation – sondern sie ist das Lebenswerk des mittlerweile über 100 Jahre alten Futuristen und Architekten Jacque Fresco aus Florida. In 35 Jahren (seit seinem 13. Lebensjahr) hat dieser zusammen mit seiner Frau seinen futuristischen Traum von einer besonderen Zukunftsstadt auf 85.000 Quadratmetern realisiert. In den Gebäuden und Gärten befinden sich u.a. ein Forschungszentrum, Vortragsräume und unzählige seiner Modelle & Architekturentwürfe.


Sein zentrales Anliegen ist eine resourcenbasierte Wirtschaft und die Abschaffung von Geld und Privatbesitz. Mit Hilfe von Roboterarbeit und dem Bedingungslosen Grundeinkommen (da nach seiner Ansicht in den kommenden Jahren fast alle Jobs automatisiert werden), möchte er eine ökologische Landwirtschaft mit Permakulturen etc. und eine effiziente Energiegewinnung (ausschließlich durch regenerative Energien) schaffen. Wenige kompatible Formen in einer sparsamen Modulbauweise (in die u.a. bereits variable Service- und Reparaturelemente integriert sind) sollen insgesamt eine soziale & ökologische Utopie im Einklang mit der Natur ermöglichen.


Nachfolgend der Direkt-Link auf den interessanten Artikel von Zoltan Istvan, der den Architekten und seine Frau in Florida besuchen durfte und seinen Artikel Ende 2016 auf „MOTHERBOARD“ veröffentlicht hatte:


https://motherboard.vice.com/de/article/vv34nb/ich-habe-die-zukunft-besucht-in-der-wir-ohne-geld-steuern-und-besitz-leben


Da soll noch jemand behaupten, es gäbe keine Utopien mehr bzw. keine Futuristen, die ihre kreativen und zukunftsfähigen Ideen (auch in ganz großem Stil) selbst in die Tat umsetzen. LG @all :)


Wir sind eine Menschheitsfamilie. • Wir sind eins. • Wir sind HUMAN CONNECTION❤️

", - image: "https://picsum.photos/1280/1024?image=352", - visibility: public, - disabled: false, - deleted: false - ) { title } - p1_cat1: AddPostCategories(from: {id: "p1"}, to: {id: "cat1"}) { from { id } } - p1_cat2: AddPostCategories(from: {id: "p1"}, to: {id: "cat2"}) { from { id } } - ur1: AddUserContributions(from: { id: "u1" }, to: { id: "p1" }) { from { id } } - p1_t1: AddPostTags( - from: { id: "p1" } - to: { id: "t1" } - ) { from { id } } - p1_t2: AddPostTags( - from: { id: "p1" } - to: { id: "t2" } - ) { from { id } } - p1_t3: AddPostTags( - from: { id: "p1" } - to: { id: "t3" } - ) { from { id } } - - p2: CreatePost( - id: "p2", - title: "Julian Assange", - content: "

Diese Zukunftsstadt ist real und keine Computer-Animation – sondern sie ist das Lebenswerk des mittlerweile über 100 Jahre alten Futuristen und Architekten Jacque Fresco aus Florida. In 35 Jahren (seit seinem 13. Lebensjahr) hat dieser zusammen mit seiner Frau seinen futuristischen Traum von einer besonderen Zukunftsstadt auf 85.000 Quadratmetern realisiert. In den Gebäuden und Gärten befinden sich u.a. ein Forschungszentrum, Vortragsräume und unzählige seiner Modelle & Architekturentwürfe.


Sein zentrales Anliegen ist eine resourcenbasierte Wirtschaft und die Abschaffung von Geld und Privatbesitz. Mit Hilfe von Roboterarbeit und dem Bedingungslosen Grundeinkommen (da nach seiner Ansicht in den kommenden Jahren fast alle Jobs automatisiert werden), möchte er eine ökologische Landwirtschaft mit Permakulturen etc. und eine effiziente Energiegewinnung (ausschließlich durch regenerative Energien) schaffen. Wenige kompatible Formen in einer sparsamen Modulbauweise (in die u.a. bereits variable Service- und Reparaturelemente integriert sind) sollen insgesamt eine soziale & ökologische Utopie im Einklang mit der Natur ermöglichen.


Nachfolgend der Direkt-Link auf den interessanten Artikel von Zoltan Istvan, der den Architekten und seine Frau in Florida besuchen durfte und seinen Artikel Ende 2016 auf „MOTHERBOARD“ veröffentlicht hatte:


https://motherboard.vice.com/de/article/vv34nb/ich-habe-die-zukunft-besucht-in-der-wir-ohne-geld-steuern-und-besitz-leben


Da soll noch jemand behaupten, es gäbe keine Utopien mehr bzw. keine Futuristen, die ihre kreativen und zukunftsfähigen Ideen (auch in ganz großem Stil) selbst in die Tat umsetzen. LG @all :)


Wir sind eine Menschheitsfamilie. • Wir sind eins. • Wir sind HUMAN CONNECTION❤️

", - image: "https://picsum.photos/1280/1024?image=72", - visibility: public, - disabled: false, - deleted: false - ) { title } - p2_cat1: AddPostCategories(from: {id: "p2"}, to: {id: "cat1"}) { from { id } } - p2_cat16: AddPostCategories(from: {id: "p2"}, to: {id: "cat16"}) { from { id } } - ur2: AddUserContributions(from: { id: "u2" }, to: { id: "p2" }) { from { id } } - p2_t4: AddPostTags( - from: { id: "p2" } - to: { id: "t4" } - ) { from { id } } - - p3: CreatePost( - id: "p3", - title: "Hacker, Freaks und Funktionäre...Der CCC", - content: "${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()}", - image: "https://picsum.photos/1280/1024?image=121", - visibility: public, - disabled: false, - deleted: false - ) { title } - p3_cat1: AddPostCategories(from: {id: "p3"}, to: {id: "cat1"}) { from { id } } - p3_cat3: AddPostCategories(from: {id: "p3"}, to: {id: "cat3"}) { from { id } } - p3_cat14: AddPostCategories(from: {id: "p3"}, to: {id: "cat14"}) { from { id } } - ur3: AddUserContributions(from: { id: "u3" }, to: { id: "p3" }) { from { id } } - p3_t2: AddPostTags( - from: { id: "p3" } - to: { id: "t2" } - ) { from { id } } - p3_t4: AddPostTags( - from: { id: "p3" } - to: { id: "t4" } - ) { from { id } } - - p4: CreatePost( - id: "p4", - title: "Lebensmittel (?)", - content: "${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()}", - image: "https://picsum.photos/1280/1024?image=142", - visibility: public, - disabled: false, - deleted: false - ) { title } - p4_cat1: AddPostCategories(from: {id: "p4"}, to: {id: "cat1"}) { from { id } } - p4_cat9: AddPostCategories(from: {id: "p4"}, to: {id: "cat9"}) { from { id } } - p4_cat4: AddPostCategories(from: {id: "p4"}, to: {id: "cat4"}) { from { id } } - ur4: AddUserContributions(from: { id: "u4" }, to: { id: "p4" }) { from { id } } - - p5: CreatePost( - id: "p5", - title: "${faker.lorem.sentence()}", - content: "${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()}", - image: "https://picsum.photos/1280/1024?image=231", - visibility: public, - disabled: false, - deleted: false - ) { title } - p5_cat8: AddPostCategories(from: {id: "p5"}, to: {id: "cat8"}) { from { id } } - p5_cat12: AddPostCategories(from: {id: "p5"}, to: {id: "cat12"}) { from { id } } - ur5: AddUserContributions(from: { id: "u2" }, to: { id: "p5" }) { from { id } } - - p6: CreatePost( - id: "p6", - title: "${faker.lorem.sentence()}", - content: "${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()} ${faker.lorem.sentence()}", - image: "https://picsum.photos/1280/1024?image=424", - visibility: public, - disabled: false, - deleted: false - ) { title } - p6_cat1: AddPostCategories(from: {id: "p6"}, to: {id: "cat1"}) { from { id } } - p6_cat2: AddPostCategories(from: {id: "p6"}, to: {id: "cat2"}) { from { id } } - p6_cat5: AddPostCategories(from: {id: "p6"}, to: {id: "cat5"}) { from { id } } - ur6: AddUserContributions(from: { id: "u4" }, to: { id: "p6" }) { from { id } } - } - ` -} diff --git a/src/seed/data/reports.js b/src/seed/data/reports.js deleted file mode 100644 index 9cecc4f9a..000000000 --- a/src/seed/data/reports.js +++ /dev/null @@ -1,45 +0,0 @@ -export default function (data) { - return ` - mutation { - r1: CreateReport(id: "r1", type: contribution, description: "Bad Stuff") { - id - } - r2: CreateReport(id: "r2", type: comment, description: "Please remove this sh**") { - id - } - r3: CreateReport(id: "r3", type: user, description: "The user have misbehaved!") { - id - } - ra1: AddReportReporter(from: { id: "u1" }, to: { id: "r1" }) { - from { - id - } - } - ra2: AddReportReporter(from: { id: "u2" }, to: { id: "r2" }) { - from { - id - } - } - ra3: AddReportReporter(from: { id: "u3" }, to: { id: "r3" }) { - from { - id - } - } - rc1: AddReportContribution(from: { id: "r1" }, to: { id: "p2" }) { - from { - id - } - } - rc2: AddReportComment(from: { id: "r2" }, to: { id: "c2" }) { - from { - id - } - } - rc3: AddReportUser(from: { id: "r3" }, to: { id: "u4" }) { - from { - id - } - } - } - ` -} diff --git a/src/seed/data/tags.js b/src/seed/data/tags.js deleted file mode 100644 index be4b3da90..000000000 --- a/src/seed/data/tags.js +++ /dev/null @@ -1,22 +0,0 @@ -export default function (data) { - return ` - mutation { - t1: CreateTag( - id: "t1", - name: "Umwelt" - ) { name } - t2: CreateTag( - id: "t2", - name: "Naturschutz" - ) { name } - t3: CreateTag( - id: "t3", - name: "Demokratie" - ) { name } - t4: CreateTag( - id: "t4", - name: "Freiheit" - ) { name } - } - ` -} diff --git a/src/seed/data/users-badges.js b/src/seed/data/users-badges.js deleted file mode 100644 index 1a8bc1fe1..000000000 --- a/src/seed/data/users-badges.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function (data) { - return ` - mutation { - b1_u1: AddUserBadges(from: {id: "b1"}, to: {id: "u1"}) { from { id } } - b2_u1: AddUserBadges(from: {id: "b2"}, to: {id: "u1"}) { from { id } } - b3_u1: AddUserBadges(from: {id: "b3"}, to: {id: "u1"}) { from { id } } - b6_u2: AddUserBadges(from: {id: "b6"}, to: {id: "u2"}) { from { id } } - b3_u3: AddUserBadges(from: {id: "b3"}, to: {id: "u3"}) { from { id } } - b5_u4: AddUserBadges(from: {id: "b5"}, to: {id: "u4"}) { from { id } } - } - ` -} diff --git a/src/seed/data/users-blacklist.js b/src/seed/data/users-blacklist.js deleted file mode 100644 index e303c6157..000000000 --- a/src/seed/data/users-blacklist.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function (data) { - return ` - mutation { - u1_blacklist_u4: AddUserBlacklisted(from: { id: "u1" }, to: { id: "u4" }) { from { id } } - } - ` -} diff --git a/src/seed/data/users-follows.js b/src/seed/data/users-follows.js deleted file mode 100644 index af6467e7f..000000000 --- a/src/seed/data/users-follows.js +++ /dev/null @@ -1,26 +0,0 @@ -export default function (data) { - return ` - mutation { - u1_follow_u2: AddUserFollowing( - from: { id: "u1" }, - to: { id: "u2" } - ) { from { id } } - u2_follow_u1: AddUserFollowing( - from: { id: "u2" }, - to: { id: "u1" } - ) { from { id } } - u2_follow_u3: AddUserFollowing( - from: { id: "u2" }, - to: { id: "u3" } - ) { from { id } } - u2_follow_u4: AddUserFollowing( - from: { id: "u2" }, - to: { id: "u4" } - ) { from { id } } - u4_follow_u2: AddUserFollowing( - from: { id: "u4" }, - to: { id: "u2" } - ) { from { id } } - } - ` -} diff --git a/src/seed/data/users-friends.js b/src/seed/data/users-friends.js deleted file mode 100644 index 33d7db7e4..000000000 --- a/src/seed/data/users-friends.js +++ /dev/null @@ -1,14 +0,0 @@ -export default function (data) { - return ` - mutation { - u1_friends_u2: AddUserFriends( - from: { id: "u1" }, - to: { id: "u2" } - ) { from { id } } - u1_friends_u3: AddUserFriends( - from: { id: "u1" }, - to: { id: "u3" } - ) { from { id } } - } - ` -} diff --git a/src/seed/data/users-shouts.js b/src/seed/data/users-shouts.js deleted file mode 100644 index 9c0f2d1bf..000000000 --- a/src/seed/data/users-shouts.js +++ /dev/null @@ -1,30 +0,0 @@ -export default function (data) { - return ` - mutation { - u1s2: AddUserShouted( - from: { id: "u1" }, - to: { id: "p2" } - ) { from { id } } - u1s3: AddUserShouted( - from: { id: "u1" }, - to: { id: "p3" } - ) { from { id } } - u2s1: AddUserShouted( - from: { id: "u2" }, - to: { id: "p1" } - ) { from { id } } - u3s1: AddUserShouted( - from: { id: "u3" }, - to: { id: "p1" } - ) { from { id } } - u3s4: AddUserShouted( - from: { id: "u3" }, - to: { id: "p4" } - ) { from { id } } - u4s1: AddUserShouted( - from: { id: "u4" }, - to: { id: "p1" } - ) { from { id } } - } - ` -} diff --git a/src/seed/data/users.js b/src/seed/data/users.js deleted file mode 100644 index 8106a75f4..000000000 --- a/src/seed/data/users.js +++ /dev/null @@ -1,70 +0,0 @@ -import faker from 'faker' - -export default function (data) { - return ` - mutation { - u1: CreateUser( - id: "u1", - name: "Peter Lustig", - password: "1234", - email: "admin@example.org", - avatar: "${faker.internet.avatar()}", - locationName: "Hamburg, Germany", - role: admin, - disabled: false, - deleted: false) { - id - name - email - avatar - role - } - u2: CreateUser( - id: "u2", - name: "Bob der Bausmeister", - password: "1234", - email: "moderator@example.org", - avatar: "${faker.internet.avatar()}", - locationName: "USA", - role: moderator, - disabled: false, - deleted: false) { - id - name - email - avatar - role - } - u3: CreateUser( - id: "u3", - name: "Jenny Rostock", - password: "1234", - email: "user@example.org", - avatar: "${faker.internet.avatar()}", - role: user, - disabled: false, - deleted: false) { - id - name - email - avatar - role - } - u4: CreateUser( - id: "u4", - name: "Angie Banjie", - password: "1234", - email: "angie@example.org", - avatar: "${faker.internet.avatar()}", - role: user, - disabled: false, - deleted: false) { - id - name - email - avatar - role - } - } - ` -} diff --git a/src/seed/factories/badges.js b/src/seed/factories/badges.js new file mode 100644 index 000000000..b34442521 --- /dev/null +++ b/src/seed/factories/badges.js @@ -0,0 +1,23 @@ +import uuid from 'uuid/v4' + +export default function (params) { + const { + id = uuid(), + key, + type = 'crowdfunding', + status = 'permanent', + icon + } = params + + return ` + mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + } + ` +} diff --git a/src/seed/factories/categories.js b/src/seed/factories/categories.js new file mode 100644 index 000000000..a4b448f4b --- /dev/null +++ b/src/seed/factories/categories.js @@ -0,0 +1,21 @@ +import uuid from 'uuid/v4' + +export default function (params) { + const { + id = uuid(), + name, + slug, + icon + } = params + + return ` + mutation { + CreateCategory( + id: "${id}", + name: "${name}", + slug: "${slug}", + icon: "${icon}" + ) { id, name } + } + ` +} diff --git a/src/seed/factories/comments.js b/src/seed/factories/comments.js new file mode 100644 index 000000000..acf493f6d --- /dev/null +++ b/src/seed/factories/comments.js @@ -0,0 +1,37 @@ +import faker from 'faker' +import uuid from 'uuid/v4' + +export default function (params) { + const { + id = uuid(), + content = [ + faker.lorem.sentence(), + faker.lorem.sentence() + ].join('. '), + disabled = false, + deleted = false + } = params + + return ` + mutation { + CreateComment( + id: "${id}", + content: "${content}", + disabled: ${disabled}, + deleted: ${deleted} + ) { id } + } + ` +} + +export function relate (type, params) { + const { from, to } = params + return ` + mutation { + ${from}_${type}_${to}: AddComment${type}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` +} diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index e62e98869..a107fc6b7 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,50 +1,103 @@ -import ApolloClient from 'apollo-client' -import gql from 'graphql-tag' -import dotenv from 'dotenv' -import { HttpLink } from 'apollo-link-http' -import { InMemoryCache } from 'apollo-cache-inmemory' -import neo4j from '../../bootstrap/neo4j' -import fetch from 'node-fetch' +import { GraphQLClient, request } from 'graphql-request' +import { getDriver } from '../../bootstrap/neo4j' -dotenv.config() +export const seedServerHost = 'http://127.0.0.1:4001' -if (process.env.NODE_ENV === 'production') { - throw new Error('YOU CAN`T RUN FACTORIES IN PRODUCTION MODE') -} - -const client = new ApolloClient({ - link: new HttpLink({ uri: 'http://localhost:4001', fetch }), - cache: new InMemoryCache() -}) - -const driver = neo4j().getDriver() - -const builders = { - 'user': require('./users.js').default -} - -const buildMutation = (model, parameters) => { - return builders[model](parameters) -} - -const create = (model, parameters) => { - return client.mutate({ mutation: gql(buildMutation(model, parameters)) }) -} - -const cleanDatabase = async () => { - const session = driver.session() - const cypher = 'MATCH (n) DETACH DELETE n' - try { - const result = await session.run(cypher) - session.close() - return result - } catch (error) { - console.log(error) +const authenticatedHeaders = async ({ email, password }, host) => { + const mutation = ` + mutation { + login(email:"${email}", password:"${password}"){ + token + } + }` + const response = await request(host, mutation) + return { + authorization: `Bearer ${response.login.token}` } } -export { - create, - buildMutation, - cleanDatabase +const factories = { + 'badge': require('./badges.js').default, + 'user': require('./users.js').default, + 'organization': require('./organizations.js').default, + 'post': require('./posts.js').default, + 'comment': require('./comments.js').default, + 'category': require('./categories.js').default, + 'tag': require('./tags.js').default, + 'report': require('./reports.js').default +} + +const relationFactories = { + 'user': require('./users.js').relate, + 'organization': require('./organizations.js').relate, + 'post': require('./posts.js').relate, + 'comment': require('./comments.js').relate +} + +export const create = (model, parameters, options) => { + const graphQLClient = new GraphQLClient(seedServerHost, options) + const mutation = factories[model](parameters) + return graphQLClient.request(mutation) +} + +export const relate = (model, type, parameters, options) => { + const graphQLClient = new GraphQLClient(seedServerHost, options) + const mutation = relationFactories[model](type, parameters) + return graphQLClient.request(mutation) +} + +export const cleanDatabase = async (options = {}) => { + const { + driver = getDriver() + } = options + const session = driver.session() + const cypher = 'MATCH (n) DETACH DELETE n' + try { + return await session.run(cypher) + } catch (error) { + throw (error) + } finally { + session.close() + } +} + +export default function Factory (options = {}) { + const { + neo4jDriver = getDriver(), + seedServerHost = 'http://127.0.0.1:4001' + } = options + + const graphQLClient = new GraphQLClient(seedServerHost) + + const result = { + neo4jDriver, + seedServerHost, + graphQLClient, + lastResponse: null, + async authenticateAs ({ email, password }) { + const headers = await authenticatedHeaders({ email, password }, seedServerHost) + this.lastResponse = headers + this.graphQLClient = new GraphQLClient(seedServerHost, { headers }) + return this + }, + async create (node, properties) { + const mutation = factories[node](properties) + this.lastResponse = await this.graphQLClient.request(mutation) + return this + }, + async relate (node, relationship, properties) { + const mutation = relationFactories[node](relationship, properties) + this.lastResponse = await this.graphQLClient.request(mutation) + return this + }, + async cleanDatabase () { + this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver }) + return this + } + } + result.authenticateAs.bind(result) + result.create.bind(result) + result.relate.bind(result) + result.cleanDatabase.bind(result) + return result } diff --git a/src/seed/factories/organizations.js b/src/seed/factories/organizations.js new file mode 100644 index 000000000..0ab73beb8 --- /dev/null +++ b/src/seed/factories/organizations.js @@ -0,0 +1,36 @@ +import faker from 'faker' +import uuid from 'uuid/v4' + +export default function create (params) { + const { + id = uuid(), + name = faker.comany.companyName(), + description = faker.company.catchPhrase(), + disabled = false, + deleted = false + } = params + + return ` + mutation { + CreateOrganization( + id: "${id}", + name: "${name}", + description: "${description}", + disabled: ${disabled}, + deleted: ${deleted} + ) { name } + } + ` +} + +export function relate (type, params) { + const { from, to } = params + return ` + mutation { + ${from}_${type}_${to}: AddOrganization${type}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` +} diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js new file mode 100644 index 000000000..80f5e289d --- /dev/null +++ b/src/seed/factories/posts.js @@ -0,0 +1,46 @@ +import faker from 'faker' +import uuid from 'uuid/v4' + +export default function (params) { + const { + id = uuid(), + title = faker.lorem.sentence(), + content = [ + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence() + ].join('. '), + image = faker.image.image(), + visibility = 'public', + disabled = false, + deleted = false + } = params + + return ` + mutation { + CreatePost( + id: "${id}", + title: "${title}", + content: "${content}", + image: "${image}", + visibility: ${visibility}, + disabled: ${disabled}, + deleted: ${deleted} + ) { title, content } + } + ` +} + +export function relate (type, params) { + const { from, to } = params + return ` + mutation { + ${from}_${type}_${to}: AddPost${type}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` +} diff --git a/src/seed/factories/reports.js b/src/seed/factories/reports.js new file mode 100644 index 000000000..4dcd479f1 --- /dev/null +++ b/src/seed/factories/reports.js @@ -0,0 +1,23 @@ +import faker from 'faker' + +export default function create (params) { + const { + description = faker.lorem.sentence(), + resource: { id: resourceId, type } + } = params + + return ` + mutation { + report( + description: "${description}", + resource: { + id: "${resourceId}", + type: ${type} + } + ) { + id, + createdAt + } + } + ` +} diff --git a/src/seed/factories/tags.js b/src/seed/factories/tags.js new file mode 100644 index 000000000..c603c5629 --- /dev/null +++ b/src/seed/factories/tags.js @@ -0,0 +1,17 @@ +import uuid from 'uuid/v4' + +export default function (params) { + const { + id = uuid(), + name + } = params + + return ` + mutation { + CreateTag( + id: "${id}", + name: "${name}", + ) { name } + } + ` +} diff --git a/src/seed/factories/users.js b/src/seed/factories/users.js index 452059a73..b3a6e83c1 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -1,25 +1,30 @@ import faker from 'faker' +import uuid from 'uuid/v4' -export default function (params) { +export default function create (params) { const { + id = uuid(), name = faker.name.findName(), email = faker.internet.email(), password = '1234', - avatar = faker.internet.avatar() + role = 'user', + avatar = faker.internet.avatar(), + disabled = false, + deleted = false } = params return ` mutation { - u1: CreateUser( - id: "u1", + CreateUser( + id: "${id}", name: "${name}", password: "${password}", email: "${email}", avatar: "${avatar}", - role: admin, - disabled: false, - deleted: false) { - id + role: ${role}, + disabled: ${disabled}, + deleted: ${deleted} + ) { name email avatar @@ -28,3 +33,15 @@ export default function (params) { } ` } + +export function relate (type, params) { + const { from, to } = params + return ` + mutation { + ${from}_${type}_${to}: AddUser${type}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` +} diff --git a/src/seed/reset-db.js b/src/seed/reset-db.js index 7d7c4f3f9..4075489f9 100644 --- a/src/seed/reset-db.js +++ b/src/seed/reset-db.js @@ -1,30 +1,19 @@ -import { query } from '../graphql-schema' +import { cleanDatabase } from './factories' import dotenv from 'dotenv' -import neo4j from '../bootstrap/neo4j' dotenv.config() if (process.env.NODE_ENV === 'production') { - throw new Error('YOU CAN`T UNSEED IN PRODUCTION MODE') + throw new Error(`YOU CAN'T CLEAN THE DATABASE WITH NODE_ENV=${process.env.NODE_ENV}`) } -const driver = neo4j().getDriver() -const session = driver.session() - -const deleteAll = ` -MATCH (n) -OPTIONAL MATCH (n)-[r]-() -DELETE n,r -` -query(deleteAll, session).then(() => { - /* eslint-disable-next-line no-console */ - console.log('Successfully deleted all nodes and relations!') -}).catch((err) => { - /* eslint-disable-next-line no-console */ - console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) -}).finally(() => { - if (session) { - session.close() +(async function () { + try { + await cleanDatabase() + console.log('Successfully deleted all nodes and relations!') + process.exit(0) + } catch (err) { + console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) + process.exit(1) } - process.exit(0) -}) +})() diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 3e7e6e9c4..58984b12a 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,19 +1,198 @@ -import ApolloClient from 'apollo-client' -import dotenv from 'dotenv' -import fetch from 'node-fetch' -import { HttpLink } from 'apollo-link-http' -import { InMemoryCache } from 'apollo-cache-inmemory' -import Seed from './data/index' +import Factory, { create, relate } from './factories' -dotenv.config() +/* eslint-disable no-multi-spaces */ +(async function () { + try { + await Promise.all([ + create('badge', { id: 'b1', key: 'indiegogo_en_racoon', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_racoon.svg' }), + create('badge', { id: 'b2', key: 'indiegogo_en_rabbit', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_rabbit.svg' }), + create('badge', { id: 'b3', key: 'indiegogo_en_wolf', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_wolf.svg' }), + create('badge', { id: 'b4', key: 'indiegogo_en_bear', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_bear.svg' }), + create('badge', { id: 'b5', key: 'indiegogo_en_turtle', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_turtle.svg' }), + create('badge', { id: 'b6', key: 'indiegogo_en_rhino', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_rhino.svg' }) + ]) -if (process.env.NODE_ENV === 'production') { - throw new Error('YOU CAN`T SEED IN PRODUCTION MODE') -} + await Promise.all([ + create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', email: 'admin@example.org' }), + create('user', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', email: 'moderator@example.org' }), + create('user', { id: 'u3', name: 'Jenny Rostock', role: 'user', email: 'user@example.org' }), + create('user', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }), + create('user', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }), + create('user', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }), + create('user', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }) + ]) -const client = new ApolloClient({ - link: new HttpLink({ uri: process.env.GRAPHQL_URI, fetch }), - cache: new InMemoryCache() -}) + await Promise.all([ + relate('user', 'Badges', { from: 'b6', to: 'u1' }), + relate('user', 'Badges', { from: 'b5', to: 'u2' }), + relate('user', 'Badges', { from: 'b4', to: 'u3' }), + relate('user', 'Badges', { from: 'b3', to: 'u4' }), + relate('user', 'Badges', { from: 'b2', to: 'u5' }), + relate('user', 'Badges', { from: 'b1', to: 'u6' }), + relate('user', 'Following', { from: 'u1', to: 'u2' }), + relate('user', 'Following', { from: 'u2', to: 'u3' }), + relate('user', 'Following', { from: 'u3', to: 'u4' }), + relate('user', 'Following', { from: 'u4', to: 'u5' }), + relate('user', 'Following', { from: 'u5', to: 'u6' }), + relate('user', 'Following', { from: 'u6', to: 'u7' }), + relate('user', 'Friends', { from: 'u1', to: 'u2' }), + relate('user', 'Friends', { from: 'u1', to: 'u3' }), + relate('user', 'Friends', { from: 'u2', to: 'u3' }), + relate('user', 'Blacklisted', { from: 'u7', to: 'u4' }), + relate('user', 'Blacklisted', { from: 'u7', to: 'u5' }), + relate('user', 'Blacklisted', { from: 'u7', to: 'u6' }) + ]) -Seed(client) + await Promise.all([ + create('category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }), + create('category', { id: 'cat2', name: 'Happyness & Values', slug: 'happyness-values', icon: 'heart-o' }), + create('category', { id: 'cat3', name: 'Health & Wellbeing', slug: 'health-wellbeing', icon: 'medkit' }), + create('category', { id: 'cat4', name: 'Environment & Nature', slug: 'environment-nature', icon: 'tree' }), + create('category', { id: 'cat5', name: 'Animal Protection', slug: 'animalprotection', icon: 'paw' }), + create('category', { id: 'cat6', name: 'Humanrights Justice', slug: 'humanrights-justice', icon: 'balance-scale' }), + create('category', { id: 'cat7', name: 'Education & Sciences', slug: 'education-sciences', icon: 'graduation-cap' }), + create('category', { id: 'cat8', name: 'Cooperation & Development', slug: 'cooperation-development', icon: 'users' }), + create('category', { id: 'cat9', name: 'Democracy & Politics', slug: 'democracy-politics', icon: 'university' }), + create('category', { id: 'cat10', name: 'Economy & Finances', slug: 'economy-finances', icon: 'money' }), + create('category', { id: 'cat11', name: 'Energy & Technology', slug: 'energy-technology', icon: 'flash' }), + create('category', { id: 'cat12', name: 'IT, Internet & Data Privacy', slug: 'it-internet-dataprivacy', icon: 'mouse-pointer' }), + create('category', { id: 'cat13', name: 'Art, Curlure & Sport', slug: 'art-culture-sport', icon: 'paint-brush' }), + create('category', { id: 'cat14', name: 'Freedom of Speech', slug: 'freedomofspeech', icon: 'bullhorn' }), + create('category', { id: 'cat15', name: 'Consumption & Sustainability', slug: 'consumption-sustainability', icon: 'shopping-cart' }), + create('category', { id: 'cat16', name: 'Global Peace & Nonviolence', slug: 'globalpeace-nonviolence', icon: 'angellist' }) + ]) + + await Promise.all([ + create('tag', { id: 't1', name: 'Umwelt' }), + create('tag', { id: 't2', name: 'Naturschutz' }), + create('tag', { id: 't3', name: 'Demokratie' }), + create('tag', { id: 't4', name: 'Freiheit' }) + ]) + + const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([ + Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'user@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }), + Factory().authenticateAs({ email: 'track@example.org', password: '1234' }) + ]) + + await Promise.all([ + asAdmin.create('post', { id: 'p0' }), + asModerator.create('post', { id: 'p1' }), + asUser.create('post', { id: 'p2' }), + asTick.create('post', { id: 'p3' }), + asTrick.create('post', { id: 'p4' }), + asTrack.create('post', { id: 'p5' }), + asAdmin.create('post', { id: 'p6' }), + asModerator.create('post', { id: 'p7' }), + asUser.create('post', { id: 'p8' }), + asTick.create('post', { id: 'p9' }), + asTrick.create('post', { id: 'p10' }), + asTrack.create('post', { id: 'p11' }), + asAdmin.create('post', { id: 'p12' }), + asModerator.create('post', { id: 'p13' }), + asUser.create('post', { id: 'p14' }), + asTick.create('post', { id: 'p15' }) + ]) + + await Promise.all([ + relate('post', 'Categories', { from: 'p0', to: 'cat16' }), + relate('post', 'Categories', { from: 'p1', to: 'cat1' }), + relate('post', 'Categories', { from: 'p2', to: 'cat2' }), + relate('post', 'Categories', { from: 'p3', to: 'cat3' }), + relate('post', 'Categories', { from: 'p4', to: 'cat4' }), + relate('post', 'Categories', { from: 'p5', to: 'cat5' }), + relate('post', 'Categories', { from: 'p6', to: 'cat6' }), + relate('post', 'Categories', { from: 'p7', to: 'cat7' }), + relate('post', 'Categories', { from: 'p8', to: 'cat8' }), + relate('post', 'Categories', { from: 'p9', to: 'cat9' }), + relate('post', 'Categories', { from: 'p10', to: 'cat10' }), + relate('post', 'Categories', { from: 'p11', to: 'cat11' }), + relate('post', 'Categories', { from: 'p12', to: 'cat12' }), + relate('post', 'Categories', { from: 'p13', to: 'cat13' }), + relate('post', 'Categories', { from: 'p14', to: 'cat14' }), + relate('post', 'Categories', { from: 'p15', to: 'cat15' }), + + relate('post', 'Tags', { from: 'p0', to: 't4' }), + relate('post', 'Tags', { from: 'p1', to: 't1' }), + relate('post', 'Tags', { from: 'p2', to: 't2' }), + relate('post', 'Tags', { from: 'p3', to: 't3' }), + relate('post', 'Tags', { from: 'p4', to: 't4' }), + relate('post', 'Tags', { from: 'p5', to: 't1' }), + relate('post', 'Tags', { from: 'p6', to: 't2' }), + relate('post', 'Tags', { from: 'p7', to: 't3' }), + relate('post', 'Tags', { from: 'p8', to: 't4' }), + relate('post', 'Tags', { from: 'p9', to: 't1' }), + relate('post', 'Tags', { from: 'p10', to: 't2' }), + relate('post', 'Tags', { from: 'p11', to: 't3' }), + relate('post', 'Tags', { from: 'p12', to: 't4' }), + relate('post', 'Tags', { from: 'p13', to: 't1' }), + relate('post', 'Tags', { from: 'p14', to: 't2' }), + relate('post', 'Tags', { from: 'p15', to: 't3' }) + ]) + await Promise.all([ + relate('user', 'Shouted', { from: 'u1', to: 'p2' }), + relate('user', 'Shouted', { from: 'u1', to: 'p3' }), + relate('user', 'Shouted', { from: 'u2', to: 'p1' }), + relate('user', 'Shouted', { from: 'u3', to: 'p1' }), + relate('user', 'Shouted', { from: 'u3', to: 'p4' }), + relate('user', 'Shouted', { from: 'u4', to: 'p1' }) + ]) + + await Promise.all([ + create('comment', { id: 'c1' }), + create('comment', { id: 'c2' }), + create('comment', { id: 'c3' }), + create('comment', { id: 'c4' }), + create('comment', { id: 'c5' }), + create('comment', { id: 'c6' }), + create('comment', { id: 'c7' }) + ]) + + await Promise.all([ + relate('comment', 'Author', { from: 'u3', to: 'c1' }), + relate('comment', 'Post', { from: 'c1', to: 'p1' }), + relate('comment', 'Author', { from: 'u1', to: 'c2' }), + relate('comment', 'Post', { from: 'c2', to: 'p1' }), + relate('comment', 'Author', { from: 'u1', to: 'c3' }), + relate('comment', 'Post', { from: 'c3', to: 'p3' }), + relate('comment', 'Author', { from: 'u4', to: 'c4' }), + relate('comment', 'Post', { from: 'c4', to: 'p2' }), + relate('comment', 'Author', { from: 'u4', to: 'c5' }), + relate('comment', 'Post', { from: 'c5', to: 'p3' }), + relate('comment', 'Author', { from: 'u3', to: 'c6' }), + relate('comment', 'Post', { from: 'c6', to: 'p4' }), + relate('comment', 'Author', { from: 'u2', to: 'c7' }), + relate('comment', 'Post', { from: 'c7', to: 'p2' }) + ]) + + await Promise.all([ + asTick.create('report', { description: 'I don\'t like this comment', resource: { id: 'c1', type: 'comment' } }), + asTrick.create('report', { description: 'I don\'t like this post', resource: { id: 'p1', type: 'contribution' } }), + asTrack.create('report', { description: 'I don\'t like this user', resource: { id: 'u1', type: 'user' } }) + ]) + + await Promise.all([ + create('organization', { id: 'o1', name: 'Democracy Deutschland', description: 'Description for democracy-deutschland.' }), + create('organization', { id: 'o2', name: 'Human-Connection', description: 'Description for human-connection.' }), + create('organization', { id: 'o3', name: 'Pro Veg', description: 'Description for pro-veg.' }), + create('organization', { id: 'o4', name: 'Greenpeace', description: 'Description for greenpeace.' }) + ]) + + await Promise.all([ + relate('organization', 'CreatedBy', { from: 'u1', to: 'o1' }), + relate('organization', 'CreatedBy', { from: 'u1', to: 'o2' }), + relate('organization', 'OwnedBy', { from: 'u2', to: 'o2' }), + relate('organization', 'OwnedBy', { from: 'u2', to: 'o3' }) + ]) + /* eslint-disable-next-line no-console */ + console.log('Seeded Data...') + } catch (err) { + /* eslint-disable-next-line no-console */ + console.error(err) + process.exit(1) + } +})() +/* eslint-enable no-multi-spaces */ diff --git a/src/server.js b/src/server.js index 1d85cbd00..5867e6952 100644 --- a/src/server.js +++ b/src/server.js @@ -7,7 +7,7 @@ import mocks from './mocks' import middleware from './middleware' import applyDirectives from './bootstrap/directives' import applyScalars from './bootstrap/scalars' -import neo4j from './bootstrap/neo4j' +import { getDriver } from './bootstrap/neo4j' import passport from 'passport' import jwtStrategy from './jwt/strategy' @@ -22,7 +22,7 @@ requiredEnvVars.forEach(env => { } }) -const driver = neo4j().getDriver() +const driver = getDriver() const debug = process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true' let schema = makeAugmentedSchema({