From 31d44fe395908d73e5799dba437ef869284b6fab Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Mon, 28 Jan 2019 10:06:30 +0100 Subject: [PATCH 01/52] Improved xss cleaning --- src/middleware/xssMiddleware.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/middleware/xssMiddleware.js b/src/middleware/xssMiddleware.js index 59ce8800f..31b2110a7 100644 --- a/src/middleware/xssMiddleware.js +++ b/src/middleware/xssMiddleware.js @@ -95,15 +95,22 @@ function clean (dirty) { .replace(/<[a-z]>[\s]*<\/[a-z]>/igm, '') // remove all iframes .replace(/(]*)(>)[^>]*\/*>/igm, '') - // replace all p tags with line breaks (and spaces) only by single linebreaks - .replace(/

[\s]*(
)+[\s]*<\/p>/igm, '
') - // replace multiple linebreaks with single ones - // limit linebreaks to max 2 (equivalent to html "br" linebreak) - .replace(/(
){2,}/igm, '
') .replace(/[\n]{3,}/igm, '\n\n') .replace(/(\r\n|\n\r|\r|\n)/g, '
$1') + + // replace all p tags with line breaks (and spaces) only by single linebreaks + // limit linebreaks to max 2 (equivalent to html "br" linebreak) + .replace(/(
\s*){2,}/gim, '
') + // remove additional linebreaks after p tags + .replace( + /<\/(p|div|th|tr)>\s*(
\s*)+\s*<(p|div|th|tr)>/gim, + '

' + ) // remove additional linebreaks inside p tags - .replace(/


<\/p>/g, '') + .replace( + /<(p|div|th|tr)>\s*(
\s*)+\s*<\/(p|div|th|tr)>/gim, + '' + ) return dirty } From 8c36d9a740a7fe265259eda24c30db076f73757c Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Mon, 28 Jan 2019 11:06:54 +0100 Subject: [PATCH 02/52] Updated XSS --- src/middleware/xssMiddleware.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/middleware/xssMiddleware.js b/src/middleware/xssMiddleware.js index 31b2110a7..ef842497e 100644 --- a/src/middleware/xssMiddleware.js +++ b/src/middleware/xssMiddleware.js @@ -92,15 +92,18 @@ function clean (dirty) { // remove empty html tags and duplicated linebreaks and returns dirty = dirty // remove all tags with "space only" - .replace(/<[a-z]>[\s]*<\/[a-z]>/igm, '') + .replace(/<[a-z-]+>[\s]+<\/[a-z-]+>/gim, '') // remove all iframes - .replace(/(]*)(>)[^>]*\/*>/igm, '') - .replace(/[\n]{3,}/igm, '\n\n') - .replace(/(\r\n|\n\r|\r|\n)/g, '
$1') + .replace( + /(]*)(>)[^>]*\/*>/gim, + '' + ) + .replace(/[\n]{3,}/gim, '\n\n') + .replace(/(\r\n|\n\r|\r|\n)/g, '
') // replace all p tags with line breaks (and spaces) only by single linebreaks // limit linebreaks to max 2 (equivalent to html "br" linebreak) - .replace(/(
\s*){2,}/gim, '
') + .replace(/(
\s*){2,}/gim, '
') // remove additional linebreaks after p tags .replace( /<\/(p|div|th|tr)>\s*(
\s*)+\s*<(p|div|th|tr)>/gim, @@ -108,9 +111,13 @@ function clean (dirty) { ) // remove additional linebreaks inside p tags .replace( - /<(p|div|th|tr)>\s*(
\s*)+\s*<\/(p|div|th|tr)>/gim, + /<[a-z-]+>(<[a-z-]+>)*\s*(
\s*)+\s*(<\/[a-z-]+>)*<\/[a-z-]+>/gim, '' ) + // remove additional linebreaks when first child inside p tags + .replace(/

(\s*
\s*)+/gim, '

') + // remove additional linebreaks when last child inside p tags + .replace(/(\s*
\s*)+<\/p+>/gim, '

') return dirty } From 827687b3bc76fc07d4afc9b5ffcb8bc87e25711b Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Mon, 28 Jan 2019 12:19:35 +0100 Subject: [PATCH 03/52] Add content excerpt also on update --- src/middleware/excerptMiddleware.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/middleware/excerptMiddleware.js b/src/middleware/excerptMiddleware.js index 4b54c5341..544dc3529 100644 --- a/src/middleware/excerptMiddleware.js +++ b/src/middleware/excerptMiddleware.js @@ -7,15 +7,30 @@ export default { const result = await resolve(root, args, context, info) return result }, + UpdatePost: async (resolve, root, args, context, info) => { + args.contentExcerpt = trunc(args.content, 120).html + const result = await resolve(root, args, context, info) + return result + }, CreateComment: async (resolve, root, args, context, info) => { args.contentExcerpt = trunc(args.content, 180).html const result = await resolve(root, args, context, info) return result }, + UpdateComment: async (resolve, root, args, context, info) => { + args.contentExcerpt = trunc(args.content, 180).html + const result = await resolve(root, args, context, info) + return result + }, CreateOrganization: async (resolve, root, args, context, info) => { args.descriptionExcerpt = trunc(args.description, 120).html const result = await resolve(root, args, context, info) return result + }, + UpdateOrganization: async (resolve, root, args, context, info) => { + args.descriptionExcerpt = trunc(args.description, 120).html + const result = await resolve(root, args, context, info) + return result } } } From aba356395b9cb3a211aa93aacce59bcc9677afe3 Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Mon, 28 Jan 2019 12:19:48 +0100 Subject: [PATCH 04/52] allow h3, h4 nad hr tags --- src/middleware/xssMiddleware.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/middleware/xssMiddleware.js b/src/middleware/xssMiddleware.js index ef842497e..c2fee7976 100644 --- a/src/middleware/xssMiddleware.js +++ b/src/middleware/xssMiddleware.js @@ -24,7 +24,7 @@ function clean (dirty) { dirty = embedToAnchor(dirty) dirty = linkifyHtml(dirty) dirty = sanitizeHtml(dirty, { - allowedTags: ['iframe', 'img', 'p', 'br', 'b', 'i', 'em', 'strong', 'a', 'pre', 'ul', 'li', 'ol', 's', 'strike', 'span', 'blockquote'], + allowedTags: ['iframe', 'img', 'p', 'h3', 'h4', 'br', 'hr', 'b', 'i', 'em', 'strong', 'a', 'pre', 'ul', 'li', 'ol', 's', 'strike', 'span', 'blockquote'], allowedAttributes: { a: ['href', 'class', 'target', 'data-*', 'contenteditable'], span: ['contenteditable', 'class', 'data-*'], @@ -47,6 +47,11 @@ function clean (dirty) { } } }, + h1: 'h3', + h2: 'h3', + h3: 'h3', + h4: 'h4', + h5: 'strong', i: 'em', // a: function (tagName, attribs) { // return { @@ -99,7 +104,7 @@ function clean (dirty) { '' ) .replace(/[\n]{3,}/gim, '\n\n') - .replace(/(\r\n|\n\r|\r|\n)/g, '
') + .replace(/(\r\n|\n\r|\r|\n)/g, '
$1') // replace all p tags with line breaks (and spaces) only by single linebreaks // limit linebreaks to max 2 (equivalent to html "br" linebreak) From ca076bbcb9f312a7248479c5a15faed27e38e66a Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Mon, 4 Feb 2019 17:19:26 +0100 Subject: [PATCH 05/52] Assign post to current user on creation --- src/middleware/userMiddleware.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/middleware/userMiddleware.js b/src/middleware/userMiddleware.js index d4648413a..db5595e87 100644 --- a/src/middleware/userMiddleware.js +++ b/src/middleware/userMiddleware.js @@ -11,6 +11,24 @@ export default { UpdateUser: async (resolve, root, args, context, info) => { 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) + + try { + 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() + // eslint-disable-next-line no-empty + } catch (err) {} + return result } }, From cc5701a193e8ce892efce67283b4fedc3e410196 Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Mon, 4 Feb 2019 17:20:04 +0100 Subject: [PATCH 06/52] Do not catch issues on user assign at post create --- src/middleware/userMiddleware.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/middleware/userMiddleware.js b/src/middleware/userMiddleware.js index db5595e87..cf42709e9 100644 --- a/src/middleware/userMiddleware.js +++ b/src/middleware/userMiddleware.js @@ -16,18 +16,15 @@ export default { CreatePost: async (resolve, root, args, context, info) => { const result = await resolve(root, args, context, info) - try { - 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() - // eslint-disable-next-line no-empty - } catch (err) {} + 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 } From ad2bba586e119f8e746d380d424d9a0e811485fa Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Wed, 6 Feb 2019 17:20:38 +0100 Subject: [PATCH 07/52] Fixed link target --- src/middleware/xssMiddleware.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/middleware/xssMiddleware.js b/src/middleware/xssMiddleware.js index c2fee7976..64ff8717d 100644 --- a/src/middleware/xssMiddleware.js +++ b/src/middleware/xssMiddleware.js @@ -53,15 +53,15 @@ function clean (dirty) { h4: 'h4', h5: 'strong', i: 'em', - // a: function (tagName, attribs) { - // return { - // tagName: 'a', - // attribs: { - // href: attribs.href, - // target: '_blank', - // } - // } - // }, + a: function (tagName, attribs) { + return { + tagName: 'a', + attribs: { + href: attribs.href, + target: '_blank' + } + } + }, b: 'strong', s: 'strike', img: function (tagName, attribs) { From 2f2bc5e507b0de71d80b4b76cd6f937309b05408 Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Thu, 7 Feb 2019 08:46:34 +0100 Subject: [PATCH 08/52] Improved link cleaning --- src/middleware/xssMiddleware.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middleware/xssMiddleware.js b/src/middleware/xssMiddleware.js index 64ff8717d..2607f4210 100644 --- a/src/middleware/xssMiddleware.js +++ b/src/middleware/xssMiddleware.js @@ -58,7 +58,8 @@ function clean (dirty) { tagName: 'a', attribs: { href: attribs.href, - target: '_blank' + target: '_blank', + rel: 'noopener noreferrer nofollow' } } }, From f17242b8245c4edc52262aad26a489962144de29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 12:56:38 +0100 Subject: [PATCH 09/52] Transfer relevant changes from branch `neode` --- .travis.yml | 2 ++ README.md | 22 +++++++++++++--------- docker-compose.override.yml | 7 +++++++ docker-compose.prod.yml | 9 +++++++++ docker-compose.travis.yml | 6 ++++++ docker-compose.yml | 7 ------- neo4j/Dockerfile | 1 + neo4j/migrate.sh | 11 +++++++++++ src/bootstrap/neo4j.js | 2 +- src/middleware/slugify/uniqueSlug.js | 15 +++++++++++++++ src/middleware/slugify/uniqueSlug.spec.js | 18 ++++++++++++++++++ 11 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 docker-compose.prod.yml create mode 100755 neo4j/migrate.sh create mode 100644 src/middleware/slugify/uniqueSlug.js create mode 100644 src/middleware/slugify/uniqueSlug.spec.js 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..c659909fa --- /dev/null +++ b/neo4j/migrate.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e +if [[ -z "${NEO4J_PASSWORD}" ]]; then + echo 'CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]);' | cypher-shell +else + echo "CALL dbms.security.changePassword('${NEO4J_PASSWORD}');" | cypher-shell --username neo4j --password neo4j + echo "CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE;" | cypher-shell + echo "CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE;" | cypher-shell + echo "CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE;" | cypher-shell + echo "CREATE CONSTRAINT ON (o:Organization) ASSERT o.slug IS UNIQUE;" | cypher-shell +fi diff --git a/src/bootstrap/neo4j.js b/src/bootstrap/neo4j.js index 766c12065..929e62f23 100644 --- a/src/bootstrap/neo4j.js +++ b/src/bootstrap/neo4j.js @@ -9,7 +9,7 @@ export default function () { driver = neo4j.driver( process.env.NEO4J_URI || 'bolt://localhost:7687', neo4j.auth.basic( - process.env.NEO4J_USER || 'neo4j', + process.env.NEO4J_USERNAME || 'neo4j', process.env.NEO4J_PASSWORD || 'neo4j' ) ) diff --git a/src/middleware/slugify/uniqueSlug.js b/src/middleware/slugify/uniqueSlug.js new file mode 100644 index 000000000..0f5b264a7 --- /dev/null +++ b/src/middleware/slugify/uniqueSlug.js @@ -0,0 +1,15 @@ +import slugify from 'slug' +export default function uniqueSlug(string, isUnique) { + let slug = slugify(string, { + lower: true + }) + if (isUnique(slug)) return slug; + + let count = 0 + let uniqueSlug + do { + count += 1 + uniqueSlug = `${slug}-${count}` + } while(!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..3d379bafb --- /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 = () => true + expect(uniqueSlug(string, isUnique)).toEqual('hello-world') + }) + + it('increments slugified string until unique', () => { + const string = 'Hello World' + const isUnique = jest.fn() + isUnique + .mockReturnValueOnce(false) + .mockReturnValueOnce(true) + expect(uniqueSlug(string, isUnique)).toEqual('hello-world-1') + }) +}) From f024bd3821dadab120c9e483f1a9f10c7902ad09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 13:25:11 +0100 Subject: [PATCH 10/52] Remove redundancy --- src/seed/data/index.js | 27 +----------------------- src/seed/factories/index.js | 10 ++++----- src/seed/seed-db.js | 41 ++++++++++++++++++++++--------------- 3 files changed, 30 insertions(+), 48 deletions(-) diff --git a/src/seed/data/index.js b/src/seed/data/index.js index d3f5d15d7..5df9ee404 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,7 +1,4 @@ -import gql from 'graphql-tag' -import asyncForEach from '../../helpers/asyncForEach' - -const seed = { +export default { Badge: require('./badges.js').default, Category: require('./categories.js').default, Tags: require('./tags.js').default, @@ -19,25 +16,3 @@ const seed = { // 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/factories/index.js b/src/seed/factories/index.js index e62e98869..74076872b 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -8,11 +8,7 @@ import fetch from 'node-fetch' dotenv.config() -if (process.env.NODE_ENV === 'production') { - throw new Error('YOU CAN`T RUN FACTORIES IN PRODUCTION MODE') -} - -const client = new ApolloClient({ +const apolloClient = new ApolloClient({ link: new HttpLink({ uri: 'http://localhost:4001', fetch }), cache: new InMemoryCache() }) @@ -28,7 +24,7 @@ const buildMutation = (model, parameters) => { } const create = (model, parameters) => { - return client.mutate({ mutation: gql(buildMutation(model, parameters)) }) + return apolloClient.mutate({ mutation: gql(buildMutation(model, parameters)) }) } const cleanDatabase = async () => { @@ -44,6 +40,8 @@ const cleanDatabase = async () => { } export { + driver, + apolloClient, create, buildMutation, cleanDatabase diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 3e7e6e9c4..e6adbe1a2 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,19 +1,28 @@ -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 { apolloClient } from './factories' +import gql from 'graphql-tag' +import asyncForEach from '../helpers/asyncForEach' +import seed from './data' -dotenv.config() +(async function () { -if (process.env.NODE_ENV === 'production') { - throw new Error('YOU CAN`T SEED IN PRODUCTION MODE') -} + // prefer factories +let data = {} + // legacy seeds + await asyncForEach(Object.keys(seed), async key => { + const mutations = seed[key] + try { + const res = await apolloClient + .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...') +})() -const client = new ApolloClient({ - link: new HttpLink({ uri: process.env.GRAPHQL_URI, fetch }), - cache: new InMemoryCache() -}) - -Seed(client) From 1734554a88741fae67e343511fa1625e556cf115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 15:35:45 +0100 Subject: [PATCH 11/52] Fix seeds without the use of neode @appinteractive ping --- src/graphql-schema.spec.js | 2 +- src/jest/helpers.js | 4 +-- src/middleware/permissionsMiddleware.spec.js | 2 +- src/seed/data/index.js | 2 -- src/seed/factories/index.js | 14 +++++--- src/seed/factories/posts.js | 34 ++++++++++++++++++++ src/seed/factories/users.js | 16 +++++---- src/seed/seed-db.js | 29 ++++++++++++++++- 8 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/seed/factories/posts.js diff --git a/src/graphql-schema.spec.js b/src/graphql-schema.spec.js index 69073444c..21b3f3d61 100644 --- a/src/graphql-schema.spec.js +++ b/src/graphql-schema.spec.js @@ -1,7 +1,7 @@ import { request } from 'graphql-request' import { create, cleanDatabase } from './seed/factories' import jwt from 'jsonwebtoken' -import { host } from './jest/helpers' +import { testServerHost as host } from './jest/helpers' describe('login', () => { const mutation = (params) => { diff --git a/src/jest/helpers.js b/src/jest/helpers.js index 01a26e9d3..cb86bf785 100644 --- a/src/jest/helpers.js +++ b/src/jest/helpers.js @@ -1,8 +1,8 @@ import { request } from 'graphql-request' -export const host = 'http://127.0.0.1:4123' +export const testServerHost = 'http://127.0.0.1:4123' -export async function authenticatedHeaders ({ email, password }) { +export async function authenticatedHeaders ({ email, password }, host = testServerHost) { const mutation = ` mutation { login(email:"${email}", password:"${password}"){ diff --git a/src/middleware/permissionsMiddleware.spec.js b/src/middleware/permissionsMiddleware.spec.js index cf86d11c9..481e340cb 100644 --- a/src/middleware/permissionsMiddleware.spec.js +++ b/src/middleware/permissionsMiddleware.spec.js @@ -1,5 +1,5 @@ import { create, cleanDatabase } from '../seed/factories' -import { host, authenticatedHeaders } from '../jest/helpers' +import { testServerHost as host, authenticatedHeaders } from '../jest/helpers' import { GraphQLClient } from 'graphql-request' describe('authorization', () => { diff --git a/src/seed/data/index.js b/src/seed/data/index.js index 5df9ee404..483ad065a 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -3,14 +3,12 @@ export 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 diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 74076872b..faf2b93cc 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,3 +1,4 @@ +import { GraphQLClient } from 'graphql-request' import ApolloClient from 'apollo-client' import gql from 'graphql-tag' import dotenv from 'dotenv' @@ -6,25 +7,30 @@ import { InMemoryCache } from 'apollo-cache-inmemory' import neo4j from '../../bootstrap/neo4j' import fetch from 'node-fetch' +export const seedServerHost = 'http://127.0.0.1:4001' + dotenv.config() const apolloClient = new ApolloClient({ - link: new HttpLink({ uri: 'http://localhost:4001', fetch }), + link: new HttpLink({ uri: seedServerHost, fetch }), cache: new InMemoryCache() }) const driver = neo4j().getDriver() const builders = { - 'user': require('./users.js').default + 'user': require('./users.js').default, + 'post': require('./posts.js').default } const buildMutation = (model, parameters) => { return builders[model](parameters) } -const create = (model, parameters) => { - return apolloClient.mutate({ mutation: gql(buildMutation(model, parameters)) }) +const create = (model, parameters, options) => { + const graphQLClient = new GraphQLClient(seedServerHost, options) + const mutation = buildMutation(model, parameters) + return graphQLClient.request(mutation) } const cleanDatabase = async () => { diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js new file mode 100644 index 000000000..3dc240d9f --- /dev/null +++ b/src/seed/factories/posts.js @@ -0,0 +1,34 @@ + +import faker from 'faker' + +export default function (params) { + const { + id = `u${faker.random.number()}`, + title = faker.lorem.sentence(), + content = [ + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), + faker.lorem.sentence(), + ].join('. '), + image = faker.image.imageUrl(), + visibility = 'public', + disabled = false, + deleted = false + } = params + + return ` + mutation { + CreatePost( + id: "${id}", + title: "${title}", + content: "${content}", + image: "${image}", + visibility: ${visibility}, + disabled: ${disabled}, + deleted: ${deleted} + ) { id, title } + } + ` +} diff --git a/src/seed/factories/users.js b/src/seed/factories/users.js index 452059a73..7ead2d9f0 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -2,23 +2,27 @@ import faker from 'faker' export default function (params) { const { + id = `u${faker.random.number()}`, 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) { + role: ${role}, + disabled: ${disabled}, + deleted: ${deleted}) { id name email diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index e6adbe1a2..7b9f3b775 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,4 +1,5 @@ -import { apolloClient } from './factories' +import { create, apolloClient, seedServerHost as host } from './factories' +import { authenticatedHeaders } from '../jest/helpers.js' import gql from 'graphql-tag' import asyncForEach from '../helpers/asyncForEach' import seed from './data' @@ -6,6 +7,32 @@ import seed from './data' (async function () { // prefer factories + try { + const [admin, moderator, user, ...otherUsers] = await Promise.all([ + create('user', {id: 'u1', name: 'Peter Lustig' , role: 'admin' , email: 'admin@example.org', password: '1234'}), + 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: 'Angie Banjie' , role: 'user' , email: 'angie@example.org'}), + ]) + + // TODO: other users, not only admin, are authors of a post + const headers = await authenticatedHeaders({ + email: 'admin@example.org', + password: '1234' + }, host) + await create('post', {id: 'p1'}, { headers } ) + await create('post', {id: 'p2'}, { headers } ) + await create('post', {id: 'p3'}, { headers } ) + await create('post', {id: 'p4'}, { headers } ) + await create('post', {id: 'p5'}, { headers } ) + await create('post', {id: 'p6'}, { headers } ) + } catch(err) { + /* eslint-disable-next-line no-console */ + console.error(err) + process.exit(1) + } + + let data = {} // legacy seeds await asyncForEach(Object.keys(seed), async key => { From 9dba02cf091a36f43c36d0f07713e895a115e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 17:23:12 +0100 Subject: [PATCH 12/52] Create posts by multiple authors --- src/seed/factories/posts.js | 2 +- src/seed/seed-db.js | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index 3dc240d9f..679649c88 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -12,7 +12,7 @@ export default function (params) { faker.lorem.sentence(), faker.lorem.sentence(), ].join('. '), - image = faker.image.imageUrl(), + image = faker.image.image(), visibility = 'public', disabled = false, deleted = false diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 7b9f3b775..981764062 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -15,17 +15,24 @@ import seed from './data' create('user', {id: 'u4', name: 'Angie Banjie' , role: 'user' , email: 'angie@example.org'}), ]) - // TODO: other users, not only admin, are authors of a post - const headers = await authenticatedHeaders({ + const asAdmin = await authenticatedHeaders({ email: 'admin@example.org', password: '1234' }, host) - await create('post', {id: 'p1'}, { headers } ) - await create('post', {id: 'p2'}, { headers } ) - await create('post', {id: 'p3'}, { headers } ) - await create('post', {id: 'p4'}, { headers } ) - await create('post', {id: 'p5'}, { headers } ) - await create('post', {id: 'p6'}, { headers } ) + const asModerator = await authenticatedHeaders({ + email: 'moderator@example.org', + password: '1234' + }, host) + const asUser = await authenticatedHeaders({ + email: 'user@example.org', + password: '1234' + }, host) + await create('post', {id: 'p1'}, { headers: asAdmin } ) + await create('post', {id: 'p2'}, { headers: asModerator } ) + await create('post', {id: 'p3'}, { headers: asUser } ) + await create('post', {id: 'p4'}, { headers: asAdmin } ) + await create('post', {id: 'p5'}, { headers: asModerator } ) + await create('post', {id: 'p6'}, { headers: asUser } ) } catch(err) { /* eslint-disable-next-line no-console */ console.error(err) From 437e8589f877e3a522c828df4fc85990b9d6ed9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 18:07:07 +0100 Subject: [PATCH 13/52] Categories posts in seeding --- src/seed/data/categories.js | 22 --------------------- src/seed/data/index.js | 1 - src/seed/factories/categories.js | 21 ++++++++++++++++++++ src/seed/factories/index.js | 3 ++- src/seed/factories/posts.js | 14 ++++++++++++-- src/seed/seed-db.js | 33 ++++++++++++++++++++++++++------ 6 files changed, 62 insertions(+), 32 deletions(-) delete mode 100644 src/seed/data/categories.js create mode 100644 src/seed/factories/categories.js 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/index.js b/src/seed/data/index.js index 483ad065a..329aa7c03 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,6 +1,5 @@ export default { Badge: require('./badges.js').default, - Category: require('./categories.js').default, Tags: require('./tags.js').default, UserBadges: require('./users-badges.js').default, diff --git a/src/seed/factories/categories.js b/src/seed/factories/categories.js new file mode 100644 index 000000000..287aac5e0 --- /dev/null +++ b/src/seed/factories/categories.js @@ -0,0 +1,21 @@ +import faker from 'faker' + +export default function (params) { + const { + id = `cat${faker.random.number()}`, + name, + slug, + icon + } = params + + return ` + mutation { + CreateCategory( + id: "${id}", + name: "${name}", + slug: "${slug}", + icon: "${icon}" + ) { id, name } + } + ` +} diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index faf2b93cc..c7a00a145 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -20,7 +20,8 @@ const driver = neo4j().getDriver() const builders = { 'user': require('./users.js').default, - 'post': require('./posts.js').default + 'post': require('./posts.js').default, + 'category': require('./categories.js').default } const buildMutation = (model, parameters) => { diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index 679649c88..ca25f69e9 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -15,12 +15,21 @@ export default function (params) { image = faker.image.image(), visibility = 'public', disabled = false, - deleted = false + deleted = false, + tagIds = [], + categoryIds = [] } = params + const categoryRelations = categoryIds.map((categoryId) => { + return `${id}_${categoryId}: AddPostCategories( + from: {id: "${id}"}, + to: {id: "${categoryId}"} + ) { from { id } }` + }) + return ` mutation { - CreatePost( + ${id}: CreatePost( id: "${id}", title: "${title}", content: "${content}", @@ -29,6 +38,7 @@ export default function (params) { disabled: ${disabled}, deleted: ${deleted} ) { id, title } + ${categoryRelations.join('\n')} } ` } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 981764062..187dacec6 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -27,12 +27,33 @@ import seed from './data' email: 'user@example.org', password: '1234' }, host) - await create('post', {id: 'p1'}, { headers: asAdmin } ) - await create('post', {id: 'p2'}, { headers: asModerator } ) - await create('post', {id: 'p3'}, { headers: asUser } ) - await create('post', {id: 'p4'}, { headers: asAdmin } ) - await create('post', {id: 'p5'}, { headers: asModerator } ) - await create('post', {id: 'p6'}, { headers: asUser } ) + + const categories = 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 create('post', {id: 'p1', categoryIds: ['cat1']}, { headers: asAdmin } ) + await create('post', {id: 'p2', categoryIds: ['cat2']}, { headers: asModerator } ) + await create('post', {id: 'p3', categoryIds: ['cat3']}, { headers: asUser } ) + await create('post', {id: 'p4', categoryIds: ['cat4']}, { headers: asAdmin } ) + await create('post', {id: 'p5', categoryIds: ['cat5']}, { headers: asModerator } ) + await create('post', {id: 'p6', categoryIds: ['cat6']}, { headers: asUser } ) } catch(err) { /* eslint-disable-next-line no-console */ console.error(err) From 924a57a7ca021e8b20882d3246aa944276ea1bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 18:33:30 +0100 Subject: [PATCH 14/52] Seed badges and tags --- src/middleware/slugify/uniqueSlug.js | 8 +-- src/seed/data/badges.js | 12 ---- src/seed/data/index.js | 2 - src/seed/factories/badges.js | 22 +++++++ src/seed/factories/index.js | 5 +- src/seed/factories/posts.js | 10 +++- src/seed/factories/tags.js | 17 ++++++ src/seed/seed-db.js | 89 +++++++++++++++++----------- 8 files changed, 111 insertions(+), 54 deletions(-) delete mode 100644 src/seed/data/badges.js create mode 100644 src/seed/factories/badges.js create mode 100644 src/seed/factories/tags.js diff --git a/src/middleware/slugify/uniqueSlug.js b/src/middleware/slugify/uniqueSlug.js index 0f5b264a7..0f66fac5f 100644 --- a/src/middleware/slugify/uniqueSlug.js +++ b/src/middleware/slugify/uniqueSlug.js @@ -1,15 +1,15 @@ import slugify from 'slug' -export default function uniqueSlug(string, isUnique) { +export default function uniqueSlug (string, isUnique) { let slug = slugify(string, { lower: true }) - if (isUnique(slug)) return slug; + if (isUnique(slug)) return slug let count = 0 let uniqueSlug do { count += 1 uniqueSlug = `${slug}-${count}` - } while(!isUnique(uniqueSlug)); - return uniqueSlug; + } while (!isUnique(uniqueSlug)) + return uniqueSlug } 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/index.js b/src/seed/data/index.js index 329aa7c03..d9706a6a9 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,6 +1,4 @@ export default { - Badge: require('./badges.js').default, - Tags: require('./tags.js').default, UserBadges: require('./users-badges.js').default, UserBlacklist: require('./users-blacklist.js').default, diff --git a/src/seed/factories/badges.js b/src/seed/factories/badges.js new file mode 100644 index 000000000..2aabe2014 --- /dev/null +++ b/src/seed/factories/badges.js @@ -0,0 +1,22 @@ +import faker from 'faker' + +export default function (params) { + const { + id = `cat${faker.random.number()}`, + key, + type = 'crowdfunding', + status = 'permanent', + icon + } = params + + return ` + mutation { + ${id}: CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}") { id } + } + ` +} diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index c7a00a145..886dd2ad9 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,6 +1,5 @@ import { GraphQLClient } from 'graphql-request' 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' @@ -19,9 +18,11 @@ const apolloClient = new ApolloClient({ const driver = neo4j().getDriver() const builders = { + 'badge': require('./badges.js').default, 'user': require('./users.js').default, 'post': require('./posts.js').default, - 'category': require('./categories.js').default + 'category': require('./categories.js').default, + 'tag': require('./tags.js').default } const buildMutation = (model, parameters) => { diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index ca25f69e9..4b15ee359 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -10,7 +10,7 @@ export default function (params) { faker.lorem.sentence(), faker.lorem.sentence(), faker.lorem.sentence(), - faker.lorem.sentence(), + faker.lorem.sentence() ].join('. '), image = faker.image.image(), visibility = 'public', @@ -27,6 +27,13 @@ export default function (params) { ) { from { id } }` }) + const tagRelations = tagIds.map((tagId) => { + return `${id}_${tagId}: AddPostTags( + from: {id: "${id}"}, + to: {id: "${tagId}"} + ) { from { id } }` + }) + return ` mutation { ${id}: CreatePost( @@ -39,6 +46,7 @@ export default function (params) { deleted: ${deleted} ) { id, title } ${categoryRelations.join('\n')} + ${tagRelations.join('\n')} } ` } diff --git a/src/seed/factories/tags.js b/src/seed/factories/tags.js new file mode 100644 index 000000000..0854cc0cd --- /dev/null +++ b/src/seed/factories/tags.js @@ -0,0 +1,17 @@ +import faker from 'faker' + +export default function (params) { + const { + id = `t${faker.random.number()}`, + name + } = params + + return ` + mutation { + CreateTag( + id: "${id}", + name: "${name}", + ) { name } + } + ` +} diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 187dacec6..9f6c994ba 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -5,14 +5,13 @@ import asyncForEach from '../helpers/asyncForEach' import seed from './data' (async function () { - // prefer factories try { - const [admin, moderator, user, ...otherUsers] = await Promise.all([ - create('user', {id: 'u1', name: 'Peter Lustig' , role: 'admin' , email: 'admin@example.org', password: '1234'}), - 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: 'Angie Banjie' , role: 'user' , email: 'angie@example.org'}), + await Promise.all([ + create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', email: 'admin@example.org', password: '1234' }), + 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: 'Angie Banjie', role: 'user', email: 'angie@example.org' }) ]) const asAdmin = await authenticatedHeaders({ @@ -28,40 +27,65 @@ import seed from './data' password: '1234' }, host) - const categories = 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('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' }) + ]) - await create('post', {id: 'p1', categoryIds: ['cat1']}, { headers: asAdmin } ) - await create('post', {id: 'p2', categoryIds: ['cat2']}, { headers: asModerator } ) - await create('post', {id: 'p3', categoryIds: ['cat3']}, { headers: asUser } ) - await create('post', {id: 'p4', categoryIds: ['cat4']}, { headers: asAdmin } ) - await create('post', {id: 'p5', categoryIds: ['cat5']}, { headers: asModerator } ) - await create('post', {id: 'p6', categoryIds: ['cat6']}, { headers: asUser } ) - } catch(err) { + 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' }) + ]) + + await Promise.all([ + create('post', { id: 'p0', categoryIds: ['cat1'], tagIds: ['t1', 't4'] }, { headers: asAdmin }), + create('post', { id: 'p1', categoryIds: ['cat2'], tagIds: ['t2', 't3'] }, { headers: asModerator }), + create('post', { id: 'p2', categoryIds: ['cat3'], tagIds: ['t3', 't4'] }, { headers: asUser }), + create('post', { id: 'p3', categoryIds: ['cat4'], tagIds: ['t4', 't2'] }, { headers: asAdmin }), + create('post', { id: 'p4', categoryIds: ['cat5'], tagIds: ['t1', 't2'] }, { headers: asModerator }), + create('post', { id: 'p5', categoryIds: ['cat6'], tagIds: ['t2', 't4'] }, { headers: asUser }), + create('post', { id: 'p6', categoryIds: ['cat7'], tagIds: ['t1', 't4'] }, { headers: asAdmin }), + create('post', { id: 'p7', categoryIds: ['cat8'], tagIds: ['t2', 't3'] }, { headers: asModerator }), + create('post', { id: 'p8', categoryIds: ['cat9'], tagIds: ['t3', 't4'] }, { headers: asUser }), + create('post', { id: 'p10', categoryIds: ['cat11'], tagIds: ['t4', 't2'] }, { headers: asAdmin }), + create('post', { id: 'p11', categoryIds: ['cat12'], tagIds: ['t1', 't2'] }, { headers: asModerator }), + create('post', { id: 'p12', categoryIds: ['cat13'], tagIds: ['t2', 't4'] }, { headers: asUser }), + create('post', { id: 'p13', categoryIds: ['cat14'], tagIds: ['t4', 't2'] }, { headers: asAdmin }), + create('post', { id: 'p14', categoryIds: ['cat15'], tagIds: ['t1', 't2'] }, { headers: asModerator }), + create('post', { id: 'p15', categoryIds: ['cat16'], tagIds: ['t2', 't4'] }, { headers: asUser }) + ]) + } catch (err) { /* eslint-disable-next-line no-console */ console.error(err) process.exit(1) } - -let data = {} + let data = {} // legacy seeds await asyncForEach(Object.keys(seed), async key => { const mutations = seed[key] @@ -80,4 +104,3 @@ let data = {} /* eslint-disable-next-line no-console */ console.log('Seeded Data...') })() - From aa2ce1f639fcb44433f85f9d9d843bf116b24469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 23:02:52 +0100 Subject: [PATCH 15/52] Remove redundancy in database cleaner --- src/seed/factories/index.js | 8 ++++---- src/seed/reset-db.js | 33 +++++++++++---------------------- src/seed/seed-db.js | 3 ++- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 886dd2ad9..33d2e8f44 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -37,13 +37,13 @@ const create = (model, parameters, options) => { const cleanDatabase = async () => { const session = driver.session() - const cypher = 'MATCH (n) DETACH DELETE n' + const cypher = 'MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r' try { - const result = await session.run(cypher) - session.close() - return result + return await session.run(cypher) } catch (error) { console.log(error) + } finally { + session.close() } } 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 9f6c994ba..2b1151e0d 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -4,8 +4,8 @@ import gql from 'graphql-tag' import asyncForEach from '../helpers/asyncForEach' import seed from './data' +/* eslint-disable no-multi-spaces */ (async function () { - // prefer factories try { await Promise.all([ create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', email: 'admin@example.org', password: '1234' }), @@ -104,3 +104,4 @@ import seed from './data' /* eslint-disable-next-line no-console */ console.log('Seeded Data...') })() +/* eslint-enable no-multi-spaces */ From dd8fe73e6d561e02e03e175c906812ac3dc26c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 23:25:43 +0100 Subject: [PATCH 16/52] Create more posts, connect users with badges --- src/seed/data/index.js | 1 - src/seed/data/users-badges.js | 12 ------ src/seed/factories/users.js | 13 +++++- src/seed/seed-db.js | 77 +++++++++++++++++------------------ 4 files changed, 50 insertions(+), 53 deletions(-) delete mode 100644 src/seed/data/users-badges.js diff --git a/src/seed/data/index.js b/src/seed/data/index.js index d9706a6a9..d00d55d9f 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,6 +1,5 @@ export 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, 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/factories/users.js b/src/seed/factories/users.js index 7ead2d9f0..a4320b341 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -8,13 +8,23 @@ export default function (params) { password = '1234', role = 'user', avatar = faker.internet.avatar(), + badgeIds = [], disabled = false, deleted = false } = params + const badgeRelations = badgeIds.map((badgeId) => { + return ` + ${id}_${badgeId}: AddUserBadges( + from: {id: "${badgeId}"}, + to: {id: "${id}"} + ) { from { id } } + ` + }) + return ` mutation { - CreateUser( + ${id}: CreateUser( id: "${id}", name: "${name}", password: "${password}", @@ -29,6 +39,7 @@ export default function (params) { avatar role } + ${badgeRelations.join('\n')} } ` } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 2b1151e0d..32ff7b0ab 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -8,24 +8,31 @@ import seed from './data' (async function () { try { await Promise.all([ - create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', email: 'admin@example.org', password: '1234' }), - 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: 'Angie Banjie', role: 'user', email: 'angie@example.org' }) + 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' }) ]) - const asAdmin = await authenticatedHeaders({ - email: 'admin@example.org', - password: '1234' - }, host) - const asModerator = await authenticatedHeaders({ - email: 'moderator@example.org', - password: '1234' - }, host) - const asUser = await authenticatedHeaders({ - email: 'user@example.org', - password: '1234' - }, host) + await Promise.all([ + create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', badgeIds: ['b6'], email: 'admin@example.org' }), + create('user', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', badgeIds: ['b5'], email: 'moderator@example.org' }), + create('user', { id: 'u3', name: 'Jenny Rostock', role: 'user', badgeIds: ['b4'], email: 'user@example.org' }), + create('user', { id: 'u4', name: 'Tick', role: 'user', badgeIds: ['b3'], email: 'tick@example.org' }), + create('user', { id: 'u5', name: 'Trick', role: 'user', badgeIds: ['b2'], email: 'trick@example.org' }), + create('user', { id: 'u6', name: 'Track', role: 'user', badgeIds: ['b1'], email: 'track@example.org' }) + ]) + + const headers = await Promise.all([ + authenticatedHeaders({ email: 'admin@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'moderator@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'user@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'tick@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'trick@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'track@example.org', password: '1234' }, host), + ]) await Promise.all([ create('category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }), @@ -53,31 +60,23 @@ import seed from './data' create('tag', { id: 't4', name: 'Freiheit' }) ]) - 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' }) - ]) await Promise.all([ - create('post', { id: 'p0', categoryIds: ['cat1'], tagIds: ['t1', 't4'] }, { headers: asAdmin }), - create('post', { id: 'p1', categoryIds: ['cat2'], tagIds: ['t2', 't3'] }, { headers: asModerator }), - create('post', { id: 'p2', categoryIds: ['cat3'], tagIds: ['t3', 't4'] }, { headers: asUser }), - create('post', { id: 'p3', categoryIds: ['cat4'], tagIds: ['t4', 't2'] }, { headers: asAdmin }), - create('post', { id: 'p4', categoryIds: ['cat5'], tagIds: ['t1', 't2'] }, { headers: asModerator }), - create('post', { id: 'p5', categoryIds: ['cat6'], tagIds: ['t2', 't4'] }, { headers: asUser }), - create('post', { id: 'p6', categoryIds: ['cat7'], tagIds: ['t1', 't4'] }, { headers: asAdmin }), - create('post', { id: 'p7', categoryIds: ['cat8'], tagIds: ['t2', 't3'] }, { headers: asModerator }), - create('post', { id: 'p8', categoryIds: ['cat9'], tagIds: ['t3', 't4'] }, { headers: asUser }), - create('post', { id: 'p10', categoryIds: ['cat11'], tagIds: ['t4', 't2'] }, { headers: asAdmin }), - create('post', { id: 'p11', categoryIds: ['cat12'], tagIds: ['t1', 't2'] }, { headers: asModerator }), - create('post', { id: 'p12', categoryIds: ['cat13'], tagIds: ['t2', 't4'] }, { headers: asUser }), - create('post', { id: 'p13', categoryIds: ['cat14'], tagIds: ['t4', 't2'] }, { headers: asAdmin }), - create('post', { id: 'p14', categoryIds: ['cat15'], tagIds: ['t1', 't2'] }, { headers: asModerator }), - create('post', { id: 'p15', categoryIds: ['cat16'], tagIds: ['t2', 't4'] }, { headers: asUser }) + create('post', { id: 'p0', categoryIds: ['cat1'], tagIds: ['t1', 't4'] }, { headers: headers[0]}), + create('post', { id: 'p1', categoryIds: ['cat2'], tagIds: ['t2', 't3'] }, { headers: headers[1]}), + create('post', { id: 'p2', categoryIds: ['cat3'], tagIds: ['t3', 't4'] }, { headers: headers[2]}), + create('post', { id: 'p3', categoryIds: ['cat4'], tagIds: ['t4', 't2'] }, { headers: headers[3]}), + create('post', { id: 'p4', categoryIds: ['cat5'], tagIds: ['t1', 't2'] }, { headers: headers[4]}), + create('post', { id: 'p5', categoryIds: ['cat6'], tagIds: ['t2', 't4'] }, { headers: headers[5]}), + create('post', { id: 'p6', categoryIds: ['cat7'], tagIds: ['t1', 't4'] }, { headers: headers[0]}), + create('post', { id: 'p7', categoryIds: ['cat8'], tagIds: ['t2', 't3'] }, { headers: headers[1]}), + create('post', { id: 'p8', categoryIds: ['cat9'], tagIds: ['t3', 't4'] }, { headers: headers[2]}), + create('post', { id: 'p10', categoryIds: ['cat11'], tagIds: ['t4', 't2'] }, { headers: headers[3]}), + create('post', { id: 'p11', categoryIds: ['cat12'], tagIds: ['t1', 't2'] }, { headers: headers[4]}), + create('post', { id: 'p12', categoryIds: ['cat13'], tagIds: ['t2', 't4'] }, { headers: headers[5]}), + create('post', { id: 'p13', categoryIds: ['cat14'], tagIds: ['t4', 't2'] }, { headers: headers[0]}), + create('post', { id: 'p14', categoryIds: ['cat15'], tagIds: ['t1', 't2'] }, { headers: headers[1]}), + create('post', { id: 'p15', categoryIds: ['cat16'], tagIds: ['t2', 't4'] }, { headers: headers[2]}) ]) } catch (err) { /* eslint-disable-next-line no-console */ From df9b4b9b8b8472ebb631a35e38010624e64eb81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 23:37:44 +0100 Subject: [PATCH 17/52] Seed blacklisted relationships between users --- src/seed/data/index.js | 1 - src/seed/data/users-blacklist.js | 7 ------- src/seed/factories/users.js | 11 +++++++++++ src/seed/seed-db.js | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) delete mode 100644 src/seed/data/users-blacklist.js diff --git a/src/seed/data/index.js b/src/seed/data/index.js index d00d55d9f..12008ccda 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,6 +1,5 @@ export default { - UserBlacklist: require('./users-blacklist.js').default, UserFollows: require('./users-follows.js').default, UserFriends: require('./users-friends.js').default, 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/factories/users.js b/src/seed/factories/users.js index a4320b341..efea9a413 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -9,6 +9,7 @@ export default function (params) { role = 'user', avatar = faker.internet.avatar(), badgeIds = [], + blacklistedUserIds = [], disabled = false, deleted = false } = params @@ -22,6 +23,15 @@ export default function (params) { ` }) + const blacklistedUserRelations = blacklistedUserIds.map((blacklistedUserId) => { + return ` + ${id}_blacklist_${blacklistedUserId}: AddUserBlacklisted( + from: { id: "${id}" }, + to: { id: "${blacklistedUserId}" } + ) { from { id } } + ` + }) + return ` mutation { ${id}: CreateUser( @@ -40,6 +50,7 @@ export default function (params) { role } ${badgeRelations.join('\n')} + ${blacklistedUserRelations.join('\n')} } ` } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 32ff7b0ab..78b26eb21 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -22,7 +22,8 @@ import seed from './data' create('user', { id: 'u3', name: 'Jenny Rostock', role: 'user', badgeIds: ['b4'], email: 'user@example.org' }), create('user', { id: 'u4', name: 'Tick', role: 'user', badgeIds: ['b3'], email: 'tick@example.org' }), create('user', { id: 'u5', name: 'Trick', role: 'user', badgeIds: ['b2'], email: 'trick@example.org' }), - create('user', { id: 'u6', name: 'Track', role: 'user', badgeIds: ['b1'], email: 'track@example.org' }) + create('user', { id: 'u6', name: 'Track', role: 'user', badgeIds: ['b1'], email: 'track@example.org' }), + create('user', { id: 'u7', name: 'Dagobert', role: 'user', badgeIds: ['b1', 'b2'], blacklistedUserIds: ['u4', 'u5', 'u6'], email: 'dagobert@example.org' }) ]) const headers = await Promise.all([ From f9083c8d4b1636abb4758fe8113d4a3e677fa36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 23:39:06 +0100 Subject: [PATCH 18/52] Remove obsolete legacy seeds --- src/seed/data/tags.js | 22 ------------- src/seed/data/users.js | 70 ------------------------------------------ 2 files changed, 92 deletions(-) delete mode 100644 src/seed/data/tags.js delete mode 100644 src/seed/data/users.js 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.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 - } - } - ` -} From e51817c849520892519296f458e92b9ab3585fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 18 Feb 2019 23:49:54 +0100 Subject: [PATCH 19/52] Seed follow relations between users --- src/seed/data/index.js | 1 - src/seed/data/users-follows.js | 26 -------------------------- src/seed/factories/users.js | 11 +++++++++++ src/seed/seed-db.js | 12 ++++++------ 4 files changed, 17 insertions(+), 33 deletions(-) delete mode 100644 src/seed/data/users-follows.js diff --git a/src/seed/data/index.js b/src/seed/data/index.js index 12008ccda..d4a99817e 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,6 +1,5 @@ export default { - UserFollows: require('./users-follows.js').default, UserFriends: require('./users-friends.js').default, Organization: require('./organizations.js').default, 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/factories/users.js b/src/seed/factories/users.js index efea9a413..c9659f72c 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -10,6 +10,7 @@ export default function (params) { avatar = faker.internet.avatar(), badgeIds = [], blacklistedUserIds = [], + followedUserIds = [], disabled = false, deleted = false } = params @@ -32,6 +33,15 @@ export default function (params) { ` }) + const followedUserRelations = followedUserIds.map((followedUserId) => { + return ` + ${id}_follow_${followedUserId}: AddUserFollowing( + from: { id: "${id}" }, + to: { id: "${followedUserId}" } + ) { from { id } } + ` + }); + return ` mutation { ${id}: CreateUser( @@ -51,6 +61,7 @@ export default function (params) { } ${badgeRelations.join('\n')} ${blacklistedUserRelations.join('\n')} + ${followedUserRelations.join('\n')} } ` } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 78b26eb21..8943c1406 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -17,12 +17,12 @@ import seed from './data' ]) await Promise.all([ - create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', badgeIds: ['b6'], email: 'admin@example.org' }), - create('user', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', badgeIds: ['b5'], email: 'moderator@example.org' }), - create('user', { id: 'u3', name: 'Jenny Rostock', role: 'user', badgeIds: ['b4'], email: 'user@example.org' }), - create('user', { id: 'u4', name: 'Tick', role: 'user', badgeIds: ['b3'], email: 'tick@example.org' }), - create('user', { id: 'u5', name: 'Trick', role: 'user', badgeIds: ['b2'], email: 'trick@example.org' }), - create('user', { id: 'u6', name: 'Track', role: 'user', badgeIds: ['b1'], email: 'track@example.org' }), + create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', followedUserIds: [ ], badgeIds: ['b6'], email: 'admin@example.org' }), + create('user', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', followedUserIds: ['u1'], badgeIds: ['b5'], email: 'moderator@example.org' }), + create('user', { id: 'u3', name: 'Jenny Rostock', role: 'user', followedUserIds: ['u2'], badgeIds: ['b4'], email: 'user@example.org' }), + create('user', { id: 'u4', name: 'Tick', role: 'user', followedUserIds: ['u3'], badgeIds: ['b3'], email: 'tick@example.org' }), + create('user', { id: 'u5', name: 'Trick', role: 'user', followedUserIds: ['u4'], badgeIds: ['b2'], email: 'trick@example.org' }), + create('user', { id: 'u6', name: 'Track', role: 'user', followedUserIds: ['u5'], badgeIds: ['b1'], email: 'track@example.org' }), create('user', { id: 'u7', name: 'Dagobert', role: 'user', badgeIds: ['b1', 'b2'], blacklistedUserIds: ['u4', 'u5', 'u6'], email: 'dagobert@example.org' }) ]) From eb22555797c658bf39085ad85619d9043f961a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 00:15:40 +0100 Subject: [PATCH 20/52] Change API: create/relate Separating the relations from mere properties removes boilerplate code. --- src/seed/factories/index.js | 18 +++++++++++++++++- src/seed/factories/users.js | 17 ++++++++++++++++- src/seed/seed-db.js | 8 +++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 33d2e8f44..ed1ecf330 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -25,16 +25,30 @@ const builders = { 'tag': require('./tags.js').default } +const relationBuilders = { + 'user': require('./users.js').relate +} + const buildMutation = (model, parameters) => { return builders[model](parameters) } +const buildRelationMutation = (model, type, parameters) => { + return relationBuilders[model](type, parameters) +} + const create = (model, parameters, options) => { const graphQLClient = new GraphQLClient(seedServerHost, options) const mutation = buildMutation(model, parameters) return graphQLClient.request(mutation) } +const relate = (model, type, parameters, options) => { + const graphQLClient = new GraphQLClient(seedServerHost, options) + const mutation = buildRelationMutation(model, type, parameters) + return graphQLClient.request(mutation) +} + const cleanDatabase = async () => { const session = driver.session() const cypher = 'MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r' @@ -50,7 +64,9 @@ const cleanDatabase = async () => { export { driver, apolloClient, - create, buildMutation, + buildRelationMutation, + create, + relate, cleanDatabase } diff --git a/src/seed/factories/users.js b/src/seed/factories/users.js index c9659f72c..7b0f1316b 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -1,6 +1,6 @@ import faker from 'faker' -export default function (params) { +export default function create (params) { const { id = `u${faker.random.number()}`, name = faker.name.findName(), @@ -65,3 +65,18 @@ export default function (params) { } ` } + +export function relate(type, params) { + switch(type){ + case 'friends': + const { from, to } = params + return ` + mutation { + ${from}_friends_${to}: AddUserFriends( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` + } +} diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 8943c1406..79058f0d4 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,4 +1,4 @@ -import { create, apolloClient, seedServerHost as host } from './factories' +import { create, relate, apolloClient, seedServerHost as host } from './factories' import { authenticatedHeaders } from '../jest/helpers.js' import gql from 'graphql-tag' import asyncForEach from '../helpers/asyncForEach' @@ -26,6 +26,12 @@ import seed from './data' create('user', { id: 'u7', name: 'Dagobert', role: 'user', badgeIds: ['b1', 'b2'], blacklistedUserIds: ['u4', 'u5', 'u6'], email: 'dagobert@example.org' }) ]) + await Promise.all([ + relate('user', 'friends', { from: 'u1', to: 'u2' }), + relate('user', 'friends', { from: 'u1', to: 'u3' }), + relate('user', 'friends', { from: 'u2', to: 'u3' }) + ]) + const headers = await Promise.all([ authenticatedHeaders({ email: 'admin@example.org', password: '1234' }, host), authenticatedHeaders({ email: 'moderator@example.org', password: '1234' }, host), From 62d450ef684ee6bb5670775b653086346353ca76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 00:54:21 +0100 Subject: [PATCH 21/52] Remove boilerplate code with method relate() --- src/seed/factories/index.js | 3 +- src/seed/factories/posts.js | 31 +++++------ src/seed/factories/users.js | 54 ++++--------------- src/seed/seed-db.js | 104 +++++++++++++++++++++++++++--------- 4 files changed, 103 insertions(+), 89 deletions(-) diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index ed1ecf330..849d77dc1 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -26,7 +26,8 @@ const builders = { } const relationBuilders = { - 'user': require('./users.js').relate + 'user': require('./users.js').relate, + 'post': require('./posts.js').relate } const buildMutation = (model, parameters) => { diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index 4b15ee359..e96eb61f2 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -15,24 +15,9 @@ export default function (params) { image = faker.image.image(), visibility = 'public', disabled = false, - deleted = false, - tagIds = [], - categoryIds = [] + deleted = false } = params - const categoryRelations = categoryIds.map((categoryId) => { - return `${id}_${categoryId}: AddPostCategories( - from: {id: "${id}"}, - to: {id: "${categoryId}"} - ) { from { id } }` - }) - - const tagRelations = tagIds.map((tagId) => { - return `${id}_${tagId}: AddPostTags( - from: {id: "${id}"}, - to: {id: "${tagId}"} - ) { from { id } }` - }) return ` mutation { @@ -45,8 +30,18 @@ export default function (params) { disabled: ${disabled}, deleted: ${deleted} ) { id, title } - ${categoryRelations.join('\n')} - ${tagRelations.join('\n')} + } + ` +} + +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/users.js b/src/seed/factories/users.js index 7b0f1316b..f1198cb7a 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -8,40 +8,10 @@ export default function create (params) { password = '1234', role = 'user', avatar = faker.internet.avatar(), - badgeIds = [], - blacklistedUserIds = [], - followedUserIds = [], disabled = false, deleted = false } = params - const badgeRelations = badgeIds.map((badgeId) => { - return ` - ${id}_${badgeId}: AddUserBadges( - from: {id: "${badgeId}"}, - to: {id: "${id}"} - ) { from { id } } - ` - }) - - const blacklistedUserRelations = blacklistedUserIds.map((blacklistedUserId) => { - return ` - ${id}_blacklist_${blacklistedUserId}: AddUserBlacklisted( - from: { id: "${id}" }, - to: { id: "${blacklistedUserId}" } - ) { from { id } } - ` - }) - - const followedUserRelations = followedUserIds.map((followedUserId) => { - return ` - ${id}_follow_${followedUserId}: AddUserFollowing( - from: { id: "${id}" }, - to: { id: "${followedUserId}" } - ) { from { id } } - ` - }); - return ` mutation { ${id}: CreateUser( @@ -59,24 +29,18 @@ export default function create (params) { avatar role } - ${badgeRelations.join('\n')} - ${blacklistedUserRelations.join('\n')} - ${followedUserRelations.join('\n')} } ` } export function relate(type, params) { - switch(type){ - case 'friends': - const { from, to } = params - return ` - mutation { - ${from}_friends_${to}: AddUserFriends( - from: { id: "${from}" }, - to: { id: "${to}" } - ) { from { id } } - } - ` - } + const { from, to } = params + return ` + mutation { + ${from}_${type}_${to}: AddUser${type}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 79058f0d4..4ef55bec6 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -7,6 +7,7 @@ import seed from './data' /* 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' }), @@ -17,19 +18,34 @@ import seed from './data' ]) await Promise.all([ - create('user', { id: 'u1', name: 'Peter Lustig', role: 'admin', followedUserIds: [ ], badgeIds: ['b6'], email: 'admin@example.org' }), - create('user', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', followedUserIds: ['u1'], badgeIds: ['b5'], email: 'moderator@example.org' }), - create('user', { id: 'u3', name: 'Jenny Rostock', role: 'user', followedUserIds: ['u2'], badgeIds: ['b4'], email: 'user@example.org' }), - create('user', { id: 'u4', name: 'Tick', role: 'user', followedUserIds: ['u3'], badgeIds: ['b3'], email: 'tick@example.org' }), - create('user', { id: 'u5', name: 'Trick', role: 'user', followedUserIds: ['u4'], badgeIds: ['b2'], email: 'trick@example.org' }), - create('user', { id: 'u6', name: 'Track', role: 'user', followedUserIds: ['u5'], badgeIds: ['b1'], email: 'track@example.org' }), - create('user', { id: 'u7', name: 'Dagobert', role: 'user', badgeIds: ['b1', 'b2'], blacklistedUserIds: ['u4', 'u5', 'u6'], email: 'dagobert@example.org' }) + 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' }) ]) await Promise.all([ - relate('user', 'friends', { from: 'u1', to: 'u2' }), - relate('user', 'friends', { from: 'u1', to: 'u3' }), - relate('user', 'friends', { from: 'u2', to: 'u3' }) + 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' }) ]) const headers = await Promise.all([ @@ -69,22 +85,60 @@ import seed from './data' await Promise.all([ - create('post', { id: 'p0', categoryIds: ['cat1'], tagIds: ['t1', 't4'] }, { headers: headers[0]}), - create('post', { id: 'p1', categoryIds: ['cat2'], tagIds: ['t2', 't3'] }, { headers: headers[1]}), - create('post', { id: 'p2', categoryIds: ['cat3'], tagIds: ['t3', 't4'] }, { headers: headers[2]}), - create('post', { id: 'p3', categoryIds: ['cat4'], tagIds: ['t4', 't2'] }, { headers: headers[3]}), - create('post', { id: 'p4', categoryIds: ['cat5'], tagIds: ['t1', 't2'] }, { headers: headers[4]}), - create('post', { id: 'p5', categoryIds: ['cat6'], tagIds: ['t2', 't4'] }, { headers: headers[5]}), - create('post', { id: 'p6', categoryIds: ['cat7'], tagIds: ['t1', 't4'] }, { headers: headers[0]}), - create('post', { id: 'p7', categoryIds: ['cat8'], tagIds: ['t2', 't3'] }, { headers: headers[1]}), - create('post', { id: 'p8', categoryIds: ['cat9'], tagIds: ['t3', 't4'] }, { headers: headers[2]}), - create('post', { id: 'p10', categoryIds: ['cat11'], tagIds: ['t4', 't2'] }, { headers: headers[3]}), - create('post', { id: 'p11', categoryIds: ['cat12'], tagIds: ['t1', 't2'] }, { headers: headers[4]}), - create('post', { id: 'p12', categoryIds: ['cat13'], tagIds: ['t2', 't4'] }, { headers: headers[5]}), - create('post', { id: 'p13', categoryIds: ['cat14'], tagIds: ['t4', 't2'] }, { headers: headers[0]}), - create('post', { id: 'p14', categoryIds: ['cat15'], tagIds: ['t1', 't2'] }, { headers: headers[1]}), - create('post', { id: 'p15', categoryIds: ['cat16'], tagIds: ['t2', 't4'] }, { headers: headers[2]}) + create('post', { id: 'p0', tagIds: ['t1', 't4'] }, { headers: headers[0]}), + create('post', { id: 'p1', tagIds: ['t2', 't3'] }, { headers: headers[1]}), + create('post', { id: 'p2', tagIds: ['t3', 't4'] }, { headers: headers[2]}), + create('post', { id: 'p3', tagIds: ['t4', 't2'] }, { headers: headers[3]}), + create('post', { id: 'p4', tagIds: ['t1', 't2'] }, { headers: headers[4]}), + create('post', { id: 'p5', tagIds: ['t2', 't4'] }, { headers: headers[5]}), + create('post', { id: 'p6', tagIds: ['t1', 't4'] }, { headers: headers[0]}), + create('post', { id: 'p7', tagIds: ['t2', 't3'] }, { headers: headers[1]}), + create('post', { id: 'p8', tagIds: ['t3', 't4'] }, { headers: headers[2]}), + create('post', { id: 'p9', tagIds: ['t3', 't4'] }, { headers: headers[3]}), + create('post', { id: 'p10', tagIds: ['t4', 't2'] }, { headers: headers[4]}), + create('post', { id: 'p11', tagIds: ['t1', 't2'] }, { headers: headers[5]}), + create('post', { id: 'p12', tagIds: ['t2', 't4'] }, { headers: headers[0]}), + create('post', { id: 'p13', tagIds: ['t4', 't2'] }, { headers: headers[1]}), + create('post', { id: 'p14', tagIds: ['t1', 't2'] }, { headers: headers[2]}), + create('post', { id: 'p15', tagIds: ['t2', 't4'] }, { headers: headers[3]}) ]) + + 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' }), + ]) + } catch (err) { /* eslint-disable-next-line no-console */ console.error(err) From a981181e94adef6fdb634d18eb55110a9fc29117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 01:04:05 +0100 Subject: [PATCH 22/52] Seed user shouts --- src/seed/data/index.js | 2 -- src/seed/data/users-friends.js | 14 -------------- src/seed/data/users-shouts.js | 30 ------------------------------ src/seed/seed-db.js | 8 ++++++++ 4 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 src/seed/data/users-friends.js delete mode 100644 src/seed/data/users-shouts.js diff --git a/src/seed/data/index.js b/src/seed/data/index.js index d4a99817e..4bf0669ff 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,10 +1,8 @@ export default { - UserFriends: require('./users-friends.js').default, Organization: require('./organizations.js').default, Comment: require('./comments.js').default, - UserShouts: require('./users-shouts.js').default // Reports: require('./reports.js').default } 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/seed-db.js b/src/seed/seed-db.js index 4ef55bec6..917298569 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -138,6 +138,14 @@ import seed from './data' 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' }) + ]) } catch (err) { /* eslint-disable-next-line no-console */ From 1afcb4b0c976ee7f2166abdba5a20a7ecec4047e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 01:17:38 +0100 Subject: [PATCH 23/52] Seed organizations --- src/seed/data/index.js | 2 -- src/seed/data/organizations.js | 51 ----------------------------- src/seed/factories/index.js | 2 ++ src/seed/factories/organizations.js | 35 ++++++++++++++++++++ src/seed/seed-db.js | 15 +++++++++ 5 files changed, 52 insertions(+), 53 deletions(-) delete mode 100644 src/seed/data/organizations.js create mode 100644 src/seed/factories/organizations.js diff --git a/src/seed/data/index.js b/src/seed/data/index.js index 4bf0669ff..a77c6e6fb 100644 --- a/src/seed/data/index.js +++ b/src/seed/data/index.js @@ -1,7 +1,5 @@ export default { - - Organization: require('./organizations.js').default, Comment: require('./comments.js').default, // Reports: require('./reports.js').default 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/factories/index.js b/src/seed/factories/index.js index 849d77dc1..93916f376 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -20,6 +20,7 @@ const driver = neo4j().getDriver() const builders = { 'badge': require('./badges.js').default, 'user': require('./users.js').default, + 'organization': require('./organizations.js').default, 'post': require('./posts.js').default, 'category': require('./categories.js').default, 'tag': require('./tags.js').default @@ -27,6 +28,7 @@ const builders = { const relationBuilders = { 'user': require('./users.js').relate, + 'organization': require('./organizations.js').relate, 'post': require('./posts.js').relate } diff --git a/src/seed/factories/organizations.js b/src/seed/factories/organizations.js new file mode 100644 index 000000000..047398eb0 --- /dev/null +++ b/src/seed/factories/organizations.js @@ -0,0 +1,35 @@ +import faker from 'faker' + +export default function create (params) { + const { + id = `o${faker.random.number()}`, + name = faker.comany.companyName(), + description = faker.company.catchPhrase(), + disabled = false, + deleted = false + } = params + + return ` + mutation { + ${id}: 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/seed-db.js b/src/seed/seed-db.js index 917298569..1a259f4cc 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -147,6 +147,21 @@ import seed from './data' relate('user', 'Shouted', { from: 'u4', to: 'p1' }) ]) + 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'}) + ]) + + } catch (err) { /* eslint-disable-next-line no-console */ console.error(err) From 4488cc9864acad6839b33da84b9fe59f3e72a4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 01:43:11 +0100 Subject: [PATCH 24/52] Seed comments --- src/seed/data/comments.js | 80 ----------------------- src/seed/data/index.js | 6 -- src/seed/data/posts.js | 112 --------------------------------- src/seed/factories/comments.js | 37 +++++++++++ src/seed/factories/index.js | 4 +- src/seed/factories/posts.js | 1 - src/seed/seed-db.js | 52 ++++++++------- 7 files changed, 69 insertions(+), 223 deletions(-) delete mode 100644 src/seed/data/comments.js delete mode 100644 src/seed/data/index.js delete mode 100644 src/seed/data/posts.js create mode 100644 src/seed/factories/comments.js 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 a77c6e6fb..000000000 --- a/src/seed/data/index.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - - Comment: require('./comments.js').default, - - // Reports: require('./reports.js').default -} 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/factories/comments.js b/src/seed/factories/comments.js new file mode 100644 index 000000000..184b06bf0 --- /dev/null +++ b/src/seed/factories/comments.js @@ -0,0 +1,37 @@ +import faker from 'faker' + +export default function (params) { + const { + id = `c${faker.random.number()}`, + content = [ + faker.lorem.sentence(), + faker.lorem.sentence() + ].join('. '), + disabled = false, + deleted = false + } = params + + + return ` + mutation { + ${id}: 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 93916f376..89fa6144e 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -22,6 +22,7 @@ const builders = { '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 } @@ -29,7 +30,8 @@ const builders = { const relationBuilders = { 'user': require('./users.js').relate, 'organization': require('./organizations.js').relate, - 'post': require('./posts.js').relate + 'post': require('./posts.js').relate, + 'comment': require('./comments.js').relate } const buildMutation = (model, parameters) => { diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index e96eb61f2..e547efb01 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -1,4 +1,3 @@ - import faker from 'faker' export default function (params) { diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 1a259f4cc..bab59a966 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,8 +1,5 @@ import { create, relate, apolloClient, seedServerHost as host } from './factories' import { authenticatedHeaders } from '../jest/helpers.js' -import gql from 'graphql-tag' -import asyncForEach from '../helpers/asyncForEach' -import seed from './data' /* eslint-disable no-multi-spaces */ (async function () { @@ -147,6 +144,33 @@ import seed from './data' 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([ create('organization', { id: "o1", name: "Democracy Deutschland", description: "Description for democracy-deutschland."}), create('organization', { id: "o2", name: "Human-Connection", description: "Description for human-connection." }), @@ -160,31 +184,13 @@ import seed from './data' 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) } - let data = {} - // legacy seeds - await asyncForEach(Object.keys(seed), async key => { - const mutations = seed[key] - try { - const res = await apolloClient - .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...') })() /* eslint-enable no-multi-spaces */ From f7831dbbb7966755a30610410635be147d6951fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 01:44:30 +0100 Subject: [PATCH 25/52] Simplify package.json We don't have to wait for a seedServer if we access the database directly. --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index b65292acd..9525c3673 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,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", From 8a7435ebb7f22876ad9c948cfe36f90e67aaf5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 01:51:04 +0100 Subject: [PATCH 26/52] Clean up seed-db.js --- src/seed/factories/comments.js | 3 +- src/seed/factories/index.js | 10 --- src/seed/factories/organizations.js | 2 +- src/seed/factories/posts.js | 3 +- src/seed/factories/users.js | 2 +- src/seed/seed-db.js | 109 ++++++++++++++-------------- 6 files changed, 57 insertions(+), 72 deletions(-) diff --git a/src/seed/factories/comments.js b/src/seed/factories/comments.js index 184b06bf0..b4e25c482 100644 --- a/src/seed/factories/comments.js +++ b/src/seed/factories/comments.js @@ -11,7 +11,6 @@ export default function (params) { deleted = false } = params - return ` mutation { ${id}: CreateComment( @@ -24,7 +23,7 @@ export default function (params) { ` } -export function relate(type, params) { +export function relate (type, params) { const { from, to } = params return ` mutation { diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 89fa6144e..81d07978b 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,20 +1,11 @@ import { GraphQLClient } from 'graphql-request' -import ApolloClient from 'apollo-client' 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' export const seedServerHost = 'http://127.0.0.1:4001' dotenv.config() -const apolloClient = new ApolloClient({ - link: new HttpLink({ uri: seedServerHost, fetch }), - cache: new InMemoryCache() -}) - const driver = neo4j().getDriver() const builders = { @@ -68,7 +59,6 @@ const cleanDatabase = async () => { export { driver, - apolloClient, buildMutation, buildRelationMutation, create, diff --git a/src/seed/factories/organizations.js b/src/seed/factories/organizations.js index 047398eb0..c0329d55d 100644 --- a/src/seed/factories/organizations.js +++ b/src/seed/factories/organizations.js @@ -22,7 +22,7 @@ export default function create (params) { ` } -export function relate(type, params) { +export function relate (type, params) { const { from, to } = params return ` mutation { diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index e547efb01..bd095dd20 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -17,7 +17,6 @@ export default function (params) { deleted = false } = params - return ` mutation { ${id}: CreatePost( @@ -33,7 +32,7 @@ export default function (params) { ` } -export function relate(type, params) { +export function relate (type, params) { const { from, to } = params return ` mutation { diff --git a/src/seed/factories/users.js b/src/seed/factories/users.js index f1198cb7a..2e94b5ebe 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -33,7 +33,7 @@ export default function create (params) { ` } -export function relate(type, params) { +export function relate (type, params) { const { from, to } = params return ` mutation { diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index bab59a966..a8b886455 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,10 +1,9 @@ -import { create, relate, apolloClient, seedServerHost as host } from './factories' +import { create, relate, seedServerHost as host } from './factories' import { authenticatedHeaders } from '../jest/helpers.js' /* 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' }), @@ -46,12 +45,12 @@ import { authenticatedHeaders } from '../jest/helpers.js' ]) const headers = await Promise.all([ - authenticatedHeaders({ email: 'admin@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'moderator@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'user@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'tick@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'trick@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'track@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'admin@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'moderator@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'user@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'tick@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'trick@example.org', password: '1234' }, host), + authenticatedHeaders({ email: 'track@example.org', password: '1234' }, host) ]) await Promise.all([ @@ -80,24 +79,23 @@ import { authenticatedHeaders } from '../jest/helpers.js' create('tag', { id: 't4', name: 'Freiheit' }) ]) - await Promise.all([ - create('post', { id: 'p0', tagIds: ['t1', 't4'] }, { headers: headers[0]}), - create('post', { id: 'p1', tagIds: ['t2', 't3'] }, { headers: headers[1]}), - create('post', { id: 'p2', tagIds: ['t3', 't4'] }, { headers: headers[2]}), - create('post', { id: 'p3', tagIds: ['t4', 't2'] }, { headers: headers[3]}), - create('post', { id: 'p4', tagIds: ['t1', 't2'] }, { headers: headers[4]}), - create('post', { id: 'p5', tagIds: ['t2', 't4'] }, { headers: headers[5]}), - create('post', { id: 'p6', tagIds: ['t1', 't4'] }, { headers: headers[0]}), - create('post', { id: 'p7', tagIds: ['t2', 't3'] }, { headers: headers[1]}), - create('post', { id: 'p8', tagIds: ['t3', 't4'] }, { headers: headers[2]}), - create('post', { id: 'p9', tagIds: ['t3', 't4'] }, { headers: headers[3]}), - create('post', { id: 'p10', tagIds: ['t4', 't2'] }, { headers: headers[4]}), - create('post', { id: 'p11', tagIds: ['t1', 't2'] }, { headers: headers[5]}), - create('post', { id: 'p12', tagIds: ['t2', 't4'] }, { headers: headers[0]}), - create('post', { id: 'p13', tagIds: ['t4', 't2'] }, { headers: headers[1]}), - create('post', { id: 'p14', tagIds: ['t1', 't2'] }, { headers: headers[2]}), - create('post', { id: 'p15', tagIds: ['t2', 't4'] }, { headers: headers[3]}) + create('post', { id: 'p0' }, { headers: headers[0] }), + create('post', { id: 'p1' }, { headers: headers[1] }), + create('post', { id: 'p2' }, { headers: headers[2] }), + create('post', { id: 'p3' }, { headers: headers[3] }), + create('post', { id: 'p4' }, { headers: headers[4] }), + create('post', { id: 'p5' }, { headers: headers[5] }), + create('post', { id: 'p6' }, { headers: headers[0] }), + create('post', { id: 'p7' }, { headers: headers[1] }), + create('post', { id: 'p8' }, { headers: headers[2] }), + create('post', { id: 'p9' }, { headers: headers[3] }), + create('post', { id: 'p10' }, { headers: headers[4] }), + create('post', { id: 'p11' }, { headers: headers[5] }), + create('post', { id: 'p12' }, { headers: headers[0] }), + create('post', { id: 'p13' }, { headers: headers[1] }), + create('post', { id: 'p14' }, { headers: headers[2] }), + create('post', { id: 'p15' }, { headers: headers[3] }) ]) await Promise.all([ @@ -133,7 +131,7 @@ import { authenticatedHeaders } from '../jest/helpers.js' 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' }), + relate('post', 'Tags', { from: 'p15', to: 't3' }) ]) await Promise.all([ relate('user', 'Shouted', { from: 'u1', to: 'p2' }), @@ -145,44 +143,44 @@ import { authenticatedHeaders } from '../jest/helpers.js' ]) 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"}), + 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'}), + 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([ - 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." }) + 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'}) + 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...') @@ -191,6 +189,5 @@ import { authenticatedHeaders } from '../jest/helpers.js' console.error(err) process.exit(1) } - })() /* eslint-enable no-multi-spaces */ From f703164f1db7663f860729e78c7719875b7bdf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 13:36:02 +0100 Subject: [PATCH 27/52] Expose cannot read property id of null error CC @appinteractive --- src/middleware/slugifyMiddleware.spec.js | 65 ++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/middleware/slugifyMiddleware.spec.js diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js new file mode 100644 index 000000000..d1d35d96f --- /dev/null +++ b/src/middleware/slugifyMiddleware.spec.js @@ -0,0 +1,65 @@ +import { create, cleanDatabase } from '../seed/factories' +import { testServerHost as host, authenticatedHeaders } from '../jest/helpers' +import { GraphQLClient } from 'graphql-request' + +let client +let headers +beforeEach(async () => { + await create('user', {email: 'user@example.org', password: '1234'}) + headers = authenticatedHeaders({email: 'user@example.org', password: '1234'}) + client = new GraphQLClient(host, { headers }) +}) + +afterEach(async () => { + await cleanDatabase() +}) + +describe('slugify', () => { + describe('CreatePost', () => { + it('generates a slug based on the title', async () => { + const response = await client.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 () => { + await create('post', { + title: 'Pre-existing post', + slug: 'pre-existing-post' + }, { headers }) + }) + + it('chooses another slug', async () => { + const response = await client.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 requested a slug', () => { + it('rejects CreatePost', async () => { + try { + await client.request(`mutation { + CreatePost( + title: "Pre-existing post", + content: "Some content", + slug: "pre-existing-post" + ) { slug } + }`) + } catch (error) { + expect(error.response.errors[0].message).toEqual('Not Authorised!') + expect(error.response.data).toEqual({ User: [ { email: null } ] }) + } + }) + }) + }) + }) +}) From e6a996e0ddb8bcb3748e98ad56bc149317128bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 22:55:23 +0100 Subject: [PATCH 28/52] Harden permissions spec If we expect an error to be thrown, the test should fail if no error was raised. --- src/middleware/permissionsMiddleware.spec.js | 23 ++++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/middleware/permissionsMiddleware.spec.js b/src/middleware/permissionsMiddleware.spec.js index 481e340cb..ea488df28 100644 --- a/src/middleware/permissionsMiddleware.spec.js +++ b/src/middleware/permissionsMiddleware.spec.js @@ -33,11 +33,14 @@ describe('authorization', () => { } describe('not logged in', async () => { + it('rejects', async () => { + await expect(action(headers)).rejects.toThrow('Not Authorised!') + }) + it('does not expose the owner\'s email address', async () => { - try { + try{ await action(headers) - } catch (error) { - expect(error.response.errors[0].message).toEqual('Not Authorised!') + } catch(error) { expect(error.response.data).toEqual({ User: [ { email: null } ] }) } }) @@ -54,15 +57,21 @@ describe('authorization', () => { }) describe('as someone else', () => { - it('does not expose the owner\'s email address', async () => { + beforeEach(async () => { headers = await authenticatedHeaders({ email: 'someone@example.org', password: 'else' }) - try { + }) + + it('rejects', async () => { + await expect(action(headers)).rejects.toThrow('Not Authorised!') + }) + + it('does not expose the owner\'s email address', async () => { + try{ await action(headers) - } catch (error) { - expect(error.response.errors[0].message).toEqual('Not Authorised!') + } catch(error) { expect(error.response.data).toEqual({ User: [ { email: null } ] }) } }) From 30bb54c6fafc1a852521ed1483832aad2f974f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 23:00:59 +0100 Subject: [PATCH 29/52] Fix slugify test --- src/middleware/slugifyMiddleware.spec.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js index d1d35d96f..31ddc78f7 100644 --- a/src/middleware/slugifyMiddleware.spec.js +++ b/src/middleware/slugifyMiddleware.spec.js @@ -6,7 +6,7 @@ let client let headers beforeEach(async () => { await create('user', {email: 'user@example.org', password: '1234'}) - headers = authenticatedHeaders({email: 'user@example.org', password: '1234'}) + headers = await authenticatedHeaders({email: 'user@example.org', password: '1234'}) client = new GraphQLClient(host, { headers }) }) @@ -22,7 +22,7 @@ describe('slugify', () => { title: "I am a brand new post", content: "Some content" ) { slug } - }`) + }`, { headers }) expect(response).toEqual({ CreatePost: { slug: 'i-am-a-brand-new-post' } }) }) @@ -46,18 +46,14 @@ describe('slugify', () => { describe('but if the client requested a slug', () => { it('rejects CreatePost', async () => { - try { - await client.request(`mutation { + await expect(client.request(`mutation { CreatePost( title: "Pre-existing post", content: "Some content", slug: "pre-existing-post" ) { slug } }`) - } catch (error) { - expect(error.response.errors[0].message).toEqual('Not Authorised!') - expect(error.response.data).toEqual({ User: [ { email: null } ] }) - } + ).rejects.toThrow('Unique constraint error') }) }) }) From 9c4e599ff1485ed3de8adc849d3a1cf2d28c2268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 19 Feb 2019 23:25:28 +0100 Subject: [PATCH 30/52] Separate middleware for ids --- src/middleware/idMiddleware.js | 32 ++++++++++++++++++++ src/middleware/index.js | 4 ++- src/middleware/permissionsMiddleware.spec.js | 8 ++--- src/middleware/slugifyMiddleware.spec.js | 4 +-- src/middleware/userMiddleware.js | 24 --------------- 5 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 src/middleware/idMiddleware.js diff --git a/src/middleware/idMiddleware.js b/src/middleware/idMiddleware.js new file mode 100644 index 000000000..59224bd64 --- /dev/null +++ b/src/middleware/idMiddleware.js @@ -0,0 +1,32 @@ +import find from 'lodash/find' + +const includeId = async (resolve, root, args, context, info) => { + let isIdPresent + let removeIdFromResult + 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 + } + + 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 +} + +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.spec.js b/src/middleware/permissionsMiddleware.spec.js index ea488df28..7a5eacfce 100644 --- a/src/middleware/permissionsMiddleware.spec.js +++ b/src/middleware/permissionsMiddleware.spec.js @@ -38,9 +38,9 @@ describe('authorization', () => { }) it('does not expose the owner\'s email address', async () => { - try{ + try { await action(headers) - } catch(error) { + } catch (error) { expect(error.response.data).toEqual({ User: [ { email: null } ] }) } }) @@ -69,9 +69,9 @@ describe('authorization', () => { }) it('does not expose the owner\'s email address', async () => { - try{ + try { await action(headers) - } catch(error) { + } catch (error) { expect(error.response.data).toEqual({ User: [ { email: null } ] }) } }) diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js index 31ddc78f7..00382ffb9 100644 --- a/src/middleware/slugifyMiddleware.spec.js +++ b/src/middleware/slugifyMiddleware.spec.js @@ -5,8 +5,8 @@ import { GraphQLClient } from 'graphql-request' let client let headers beforeEach(async () => { - await create('user', {email: 'user@example.org', password: '1234'}) - headers = await authenticatedHeaders({email: 'user@example.org', password: '1234'}) + await create('user', { email: 'user@example.org', password: '1234' }) + headers = await authenticatedHeaders({ email: 'user@example.org', password: '1234' }) client = new GraphQLClient(host, { headers }) }) diff --git a/src/middleware/userMiddleware.js b/src/middleware/userMiddleware.js index cf42709e9..3af9ce294 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: { @@ -26,29 +25,6 @@ export default { }) 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 } } From f1dd52f5791c2889cda0ac1f2eace85c4543edc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 00:38:48 +0100 Subject: [PATCH 31/52] Expose yet another bug in the authorization Cannot read property id of null CC @appinteractive --- src/graphql-schema.js | 17 +++++ src/graphql-schema.spec.js | 121 +++++++++++++++++++++---------- src/middleware/userMiddleware.js | 15 ---- 3 files changed, 99 insertions(+), 54 deletions(-) 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 21b3f3d61..e61130fe3 100644 --- a/src/graphql-schema.spec.js +++ b/src/graphql-schema.spec.js @@ -1,7 +1,18 @@ -import { request } from 'graphql-request' import { create, cleanDatabase } from './seed/factories' import jwt from 'jsonwebtoken' -import { testServerHost as host } from './jest/helpers' +import { testServerHost as host, authenticatedHeaders } from './jest/helpers' +import { GraphQLClient, request } from 'graphql-request' + +beforeEach(async () => { + await create('user', { + email: 'test@example.org', + password: '1234' + }) +}) + +afterEach(async () => { + await cleanDatabase() +}) describe('login', () => { const mutation = (params) => { @@ -14,48 +25,80 @@ 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 () => { + 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('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 () => { + 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('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 authenticatedHeaders({ 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/middleware/userMiddleware.js b/src/middleware/userMiddleware.js index 3af9ce294..55b181bc9 100644 --- a/src/middleware/userMiddleware.js +++ b/src/middleware/userMiddleware.js @@ -10,21 +10,6 @@ export default { UpdateUser: async (resolve, root, args, context, info) => { 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 } } From 7a70b9ece4ebee4f862e4c2b5ba953886fdd13a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 00:46:27 +0100 Subject: [PATCH 32/52] Implement authorization on Post mutations --- src/middleware/permissionsMiddleware.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 0dd4a9a86..7db516e11 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 myself = 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: { - report: isAuthenticated + CreatePost: isAuthenticated, + // TODO UpdatePost: isOwner, + // TODO DeletePost: isOwner, + report: isAuthenticated, // addFruitToBasket: isAuthenticated // CreateUser: allow, }, User: { - email: isOwner, - password: isOwner + email: myself, + password: myself } // Post: isAuthenticated }) From 3532b473eeaa2dcfa12e50de5a25cd00c4fda75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 01:30:32 +0100 Subject: [PATCH 33/52] Choose another slug is green --- src/middleware/sluggifyMiddleware.js | 17 ++++++++++++++--- src/middleware/slugify/uniqueSlug.js | 6 +++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/middleware/sluggifyMiddleware.js b/src/middleware/sluggifyMiddleware.js index f059cb680..e187aa797 100644 --- a/src/middleware/sluggifyMiddleware.js +++ b/src/middleware/sluggifyMiddleware.js @@ -1,11 +1,22 @@ +import uniqueSlug from './slugify/uniqueSlug' import slug from 'slug' +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 - }) + args.slug = await uniqueSlug(args.title, isUniqueFor(context, 'Post')) const result = await resolve(root, args, context, info) return result }, diff --git a/src/middleware/slugify/uniqueSlug.js b/src/middleware/slugify/uniqueSlug.js index 0f66fac5f..8b04edc6f 100644 --- a/src/middleware/slugify/uniqueSlug.js +++ b/src/middleware/slugify/uniqueSlug.js @@ -1,15 +1,15 @@ import slugify from 'slug' -export default function uniqueSlug (string, isUnique) { +export default async function uniqueSlug (string, isUnique) { let slug = slugify(string, { lower: true }) - if (isUnique(slug)) return slug + if (await isUnique(slug)) return slug let count = 0 let uniqueSlug do { count += 1 uniqueSlug = `${slug}-${count}` - } while (!isUnique(uniqueSlug)) + } while (!await isUnique(uniqueSlug)) return uniqueSlug } From ba26c0e1887ae9abe1bd159b71fc331b36b3c53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 11:23:03 +0100 Subject: [PATCH 34/52] Seed reported comments, posts and users --- src/middleware/permissionsMiddleware.js | 2 +- src/seed/data/reports.js | 45 ------------------------- src/seed/factories/index.js | 3 +- src/seed/factories/reports.js | 23 +++++++++++++ src/seed/seed-db.js | 6 ++++ 5 files changed, 32 insertions(+), 47 deletions(-) delete mode 100644 src/seed/data/reports.js create mode 100644 src/seed/factories/reports.js diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 7db516e11..0bd88b274 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -31,7 +31,7 @@ const permissions = shield({ CreatePost: isAuthenticated, // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, - report: isAuthenticated, + report: isAuthenticated // addFruitToBasket: isAuthenticated // CreateUser: allow, }, 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/factories/index.js b/src/seed/factories/index.js index 81d07978b..04b1817ca 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -15,7 +15,8 @@ const builders = { 'post': require('./posts.js').default, 'comment': require('./comments.js').default, 'category': require('./categories.js').default, - 'tag': require('./tags.js').default + 'tag': require('./tags.js').default, + 'report': require('./reports.js').default } const relationBuilders = { 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/seed-db.js b/src/seed/seed-db.js index a8b886455..21cb543a8 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -169,6 +169,12 @@ import { authenticatedHeaders } from '../jest/helpers.js' relate('comment', 'Post', { from: 'c7', to: 'p2' }) ]) + await Promise.all([ + create('report', { description: 'I don\'t like this comment', resource: { id: 'c1', type: 'comment' } }, { headers: headers[3] }), + create('report', { description: 'I don\'t like this post', resource: { id: 'p1', type: 'contribution' } }, { headers: headers[4] }), + create('report', { description: 'I don\'t like this user', resource: { id: 'u1', type: 'user' } }, { headers: headers[5] }) + ]) + 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.' }), From a61362b2693c26a303d36dded3fe48a654f0475a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 11:48:06 +0100 Subject: [PATCH 35/52] Fix slugify unit test with async mock functions --- src/middleware/slugify/uniqueSlug.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/middleware/slugify/uniqueSlug.spec.js b/src/middleware/slugify/uniqueSlug.spec.js index 3d379bafb..190899795 100644 --- a/src/middleware/slugify/uniqueSlug.spec.js +++ b/src/middleware/slugify/uniqueSlug.spec.js @@ -3,16 +3,16 @@ import uniqueSlug from './uniqueSlug' describe('uniqueSlug', () => { it('slugifies given string', () => { const string = 'Hello World' - const isUnique = () => true - expect(uniqueSlug(string, isUnique)).toEqual('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() - isUnique - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - expect(uniqueSlug(string, isUnique)).toEqual('hello-world-1') + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(true) + expect(uniqueSlug(string, isUnique)).resolves.toEqual('hello-world-1') }) }) From c30548d1e82365685287057cead029bb2b68448e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 12:53:56 +0100 Subject: [PATCH 36/52] Fix bugs in migration script for neo4j Only change the password, if we are the default user. Everything else should be executed. --- neo4j/migrate.sh | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/neo4j/migrate.sh b/neo4j/migrate.sh index c659909fa..1ec5212ad 100755 --- a/neo4j/migrate.sh +++ b/neo4j/migrate.sh @@ -1,11 +1,18 @@ #!/usr/bin/env bash -set -e -if [[ -z "${NEO4J_PASSWORD}" ]]; then - echo 'CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]);' | cypher-shell -else - echo "CALL dbms.security.changePassword('${NEO4J_PASSWORD}');" | cypher-shell --username neo4j --password neo4j - echo "CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE;" | cypher-shell - echo "CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE;" | cypher-shell - echo "CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE;" | cypher-shell - echo "CREATE CONSTRAINT ON (o:Organization) ASSERT o.slug IS UNIQUE;" | cypher-shell + +# 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 From bfc5603fa8603a72ae017a180438c2b64f1b00fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 12:56:57 +0100 Subject: [PATCH 37/52] All tests are passing! --- src/middleware/sluggifyMiddleware.js | 2 +- src/middleware/slugifyMiddleware.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware/sluggifyMiddleware.js b/src/middleware/sluggifyMiddleware.js index e187aa797..2276e6373 100644 --- a/src/middleware/sluggifyMiddleware.js +++ b/src/middleware/sluggifyMiddleware.js @@ -16,7 +16,7 @@ const isUniqueFor = (context, type) => { export default { Mutation: { CreatePost: async (resolve, root, args, context, info) => { - args.slug = await uniqueSlug(args.title, isUniqueFor(context, 'Post')) + args.slug = args.slug || await uniqueSlug(args.title, isUniqueFor(context, 'Post')) const result = await resolve(root, args, context, info) return result }, diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js index 00382ffb9..0088d7d11 100644 --- a/src/middleware/slugifyMiddleware.spec.js +++ b/src/middleware/slugifyMiddleware.spec.js @@ -53,7 +53,7 @@ describe('slugify', () => { slug: "pre-existing-post" ) { slug } }`) - ).rejects.toThrow('Unique constraint error') + ).rejects.toThrow('already exists') }) }) }) From 7abc1583dc91d9ab43820d4b3576b8d98517dc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 14:03:36 +0100 Subject: [PATCH 38/52] Implement + test unique slugs for user I put the relevant line on categories and organizations too, but I didn't test it separately. I could have tested it but found it unnecessary repeating --- src/middleware/sluggifyMiddleware.js | 30 +++++-------------- src/middleware/slugifyMiddleware.spec.js | 38 ++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/middleware/sluggifyMiddleware.js b/src/middleware/sluggifyMiddleware.js index 2276e6373..50883d216 100644 --- a/src/middleware/sluggifyMiddleware.js +++ b/src/middleware/sluggifyMiddleware.js @@ -17,35 +17,19 @@ export default { Mutation: { CreatePost: async (resolve, root, args, context, info) => { args.slug = args.slug || await uniqueSlug(args.title, isUniqueFor(context, 'Post')) - const result = await resolve(root, args, context, info) - return result + 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/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js index 0088d7d11..b8e74cd7f 100644 --- a/src/middleware/slugifyMiddleware.spec.js +++ b/src/middleware/slugifyMiddleware.spec.js @@ -16,7 +16,7 @@ afterEach(async () => { describe('slugify', () => { describe('CreatePost', () => { - it('generates a slug based on the title', async () => { + it('generates a slug based on title', async () => { const response = await client.request(`mutation { CreatePost( title: "I am a brand new post", @@ -44,7 +44,7 @@ describe('slugify', () => { expect(response).toEqual({ CreatePost: { slug: 'pre-existing-post-1' } }) }) - describe('but if the client requested a slug', () => { + describe('but if the client specifies a slug', () => { it('rejects CreatePost', async () => { await expect(client.request(`mutation { CreatePost( @@ -58,4 +58,38 @@ describe('slugify', () => { }) }) }) + + describe('CreateUser', () => { + const action = async (mutation, params) => { + return client.request(`mutation { + ${mutation}(password: "yo", ${params}) { slug } + }`, { headers }) + } + 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') + }) + }) + }) + }) }) From 4a1e06402aa38205fea2fae541212c9dc54e9bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Feb 2019 15:26:49 +0100 Subject: [PATCH 39/52] Fix lint --- src/middleware/sluggifyMiddleware.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/middleware/sluggifyMiddleware.js b/src/middleware/sluggifyMiddleware.js index 50883d216..1a9177daa 100644 --- a/src/middleware/sluggifyMiddleware.js +++ b/src/middleware/sluggifyMiddleware.js @@ -1,5 +1,4 @@ import uniqueSlug from './slugify/uniqueSlug' -import slug from 'slug' const isUniqueFor = (context, type) => { return async (slug) => { @@ -25,11 +24,11 @@ export default { }, CreateOrganization: async (resolve, root, args, context, info) => { 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) => { args.slug = args.slug || await uniqueSlug(args.name, isUniqueFor(context, 'Category')) - return resolve(root, args, context, info) + return resolve(root, args, context, info) } } } From d4a999ee91d98b592a5ca5093327159fb4c66f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 00:27:26 +0100 Subject: [PATCH 40/52] Configure factories#cleanDatabase easier --- src/bootstrap/neo4j.js | 26 +++++++++--------- src/seed/factories/index.js | 53 ++++++++++++++----------------------- src/server.js | 4 +-- 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/bootstrap/neo4j.js b/src/bootstrap/neo4j.js index 929e62f23..24a0cd4dd 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_USERNAME || '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/seed/factories/index.js b/src/seed/factories/index.js index 04b1817ca..650071f2e 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,67 +1,54 @@ import { GraphQLClient } from 'graphql-request' -import dotenv from 'dotenv' -import neo4j from '../../bootstrap/neo4j' +import { getDriver } from '../../bootstrap/neo4j' export const seedServerHost = 'http://127.0.0.1:4001' -dotenv.config() - -const driver = neo4j().getDriver() - -const builders = { - 'badge': require('./badges.js').default, - 'user': require('./users.js').default, +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 + '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 relationBuilders = { - 'user': require('./users.js').relate, +const relationFactories = { + 'user': require('./users.js').relate, 'organization': require('./organizations.js').relate, - 'post': require('./posts.js').relate, - 'comment': require('./comments.js').relate -} - -const buildMutation = (model, parameters) => { - return builders[model](parameters) -} - -const buildRelationMutation = (model, type, parameters) => { - return relationBuilders[model](type, parameters) + 'post': require('./posts.js').relate, + 'comment': require('./comments.js').relate } const create = (model, parameters, options) => { const graphQLClient = new GraphQLClient(seedServerHost, options) - const mutation = buildMutation(model, parameters) + const mutation = factories[model](parameters) return graphQLClient.request(mutation) } const relate = (model, type, parameters, options) => { const graphQLClient = new GraphQLClient(seedServerHost, options) - const mutation = buildRelationMutation(model, type, parameters) + const mutation = relationFactories[model](type, parameters) return graphQLClient.request(mutation) } -const cleanDatabase = async () => { +const cleanDatabase = async (options = {}) => { + const { + driver = getDriver() + } = options const session = driver.session() const cypher = 'MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r' try { return await session.run(cypher) } catch (error) { - console.log(error) + throw(error) } finally { session.close() } } export { - driver, - buildMutation, - buildRelationMutation, create, relate, cleanDatabase 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({ From b3e476e039d5ec1eaaec6c19280eeed43910a4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 00:30:46 +0100 Subject: [PATCH 41/52] Create a yarn script to the backend for testing --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9525c3673..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", From 91a13593dcb535111821cb69a8d99559a879b2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 02:09:07 +0100 Subject: [PATCH 42/52] Provide a better interface for factories You can now run graphql mutations *from the point of view* of a user. @mattwr18 @Tirokk Do you have a better idea how to name the factory builder? --- src/seed/factories/index.js | 65 +++++++++++++++++++++++++++++++++---- src/seed/factories/posts.js | 4 +-- src/seed/seed-db.js | 59 +++++++++++++++++---------------- 3 files changed, 89 insertions(+), 39 deletions(-) diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 650071f2e..efbf7b248 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,7 +1,22 @@ import { GraphQLClient } from 'graphql-request' import { getDriver } from '../../bootstrap/neo4j' +import { request } from 'graphql-request' export const seedServerHost = 'http://127.0.0.1:4001' +export const testServerHost = 'http://127.0.0.1:4123' + +const authenticatedHeaders = async ({ email, password }, host = testServerHost) => { + const mutation = ` + mutation { + login(email:"${email}", password:"${password}"){ + token + } + }` + const response = await request(host, mutation) + return { + authorization: `Bearer ${response.login.token}` + } +} const factories = { 'badge': require('./badges.js').default, @@ -21,19 +36,19 @@ const relationFactories = { 'comment': require('./comments.js').relate } -const create = (model, parameters, options) => { +export const create = (model, parameters, options) => { const graphQLClient = new GraphQLClient(seedServerHost, options) const mutation = factories[model](parameters) return graphQLClient.request(mutation) } -const relate = (model, type, parameters, options) => { +export const relate = (model, type, parameters, options) => { const graphQLClient = new GraphQLClient(seedServerHost, options) const mutation = relationFactories[model](type, parameters) return graphQLClient.request(mutation) } -const cleanDatabase = async (options = {}) => { +export const cleanDatabase = async (options = {}) => { const { driver = getDriver() } = options @@ -48,8 +63,44 @@ const cleanDatabase = async (options = {}) => { } } -export { - create, - relate, - cleanDatabase +export default function factoryFun (options = {}) { + const { + neo4jDriver = getDriver(), + seedServerHost = 'http://127.0.0.1:4001', + testServerHost = 'http://127.0.0.1:4123' + } = 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/posts.js b/src/seed/factories/posts.js index bd095dd20..eaa616b2b 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -2,7 +2,7 @@ import faker from 'faker' export default function (params) { const { - id = `u${faker.random.number()}`, + id = `p${faker.random.number()}`, title = faker.lorem.sentence(), content = [ faker.lorem.sentence(), @@ -27,7 +27,7 @@ export default function (params) { visibility: ${visibility}, disabled: ${disabled}, deleted: ${deleted} - ) { id, title } + ) { title, content } } ` } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 21cb543a8..1075c61d6 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,5 +1,4 @@ -import { create, relate, seedServerHost as host } from './factories' -import { authenticatedHeaders } from '../jest/helpers.js' +import factoryFun, { create, relate, seedServerHost as host } from './factories' /* eslint-disable no-multi-spaces */ (async function () { @@ -44,15 +43,6 @@ import { authenticatedHeaders } from '../jest/helpers.js' relate('user', 'Blacklisted', { from: 'u7', to: 'u6' }) ]) - const headers = await Promise.all([ - authenticatedHeaders({ email: 'admin@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'moderator@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'user@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'tick@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'trick@example.org', password: '1234' }, host), - authenticatedHeaders({ email: 'track@example.org', password: '1234' }, host) - ]) - 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' }), @@ -79,23 +69,32 @@ import { authenticatedHeaders } from '../jest/helpers.js' create('tag', { id: 't4', name: 'Freiheit' }) ]) + const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([ + factoryFun().authenticateAs({ email: 'admin@example.org', password: '1234' }), + factoryFun().authenticateAs({ email: 'moderator@example.org', password: '1234' }), + factoryFun().authenticateAs({ email: 'user@example.org', password: '1234' }), + factoryFun().authenticateAs({ email: 'tick@example.org', password: '1234' }), + factoryFun().authenticateAs({ email: 'trick@example.org', password: '1234' }), + factoryFun().authenticateAs({ email: 'track@example.org', password: '1234' }) + ]) + await Promise.all([ - create('post', { id: 'p0' }, { headers: headers[0] }), - create('post', { id: 'p1' }, { headers: headers[1] }), - create('post', { id: 'p2' }, { headers: headers[2] }), - create('post', { id: 'p3' }, { headers: headers[3] }), - create('post', { id: 'p4' }, { headers: headers[4] }), - create('post', { id: 'p5' }, { headers: headers[5] }), - create('post', { id: 'p6' }, { headers: headers[0] }), - create('post', { id: 'p7' }, { headers: headers[1] }), - create('post', { id: 'p8' }, { headers: headers[2] }), - create('post', { id: 'p9' }, { headers: headers[3] }), - create('post', { id: 'p10' }, { headers: headers[4] }), - create('post', { id: 'p11' }, { headers: headers[5] }), - create('post', { id: 'p12' }, { headers: headers[0] }), - create('post', { id: 'p13' }, { headers: headers[1] }), - create('post', { id: 'p14' }, { headers: headers[2] }), - create('post', { id: 'p15' }, { headers: headers[3] }) + 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([ @@ -170,9 +169,9 @@ import { authenticatedHeaders } from '../jest/helpers.js' ]) await Promise.all([ - create('report', { description: 'I don\'t like this comment', resource: { id: 'c1', type: 'comment' } }, { headers: headers[3] }), - create('report', { description: 'I don\'t like this post', resource: { id: 'p1', type: 'contribution' } }, { headers: headers[4] }), - create('report', { description: 'I don\'t like this user', resource: { id: 'u1', type: 'user' } }, { headers: headers[5] }) + 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([ From 98983bb575e67c3e5f1dd41cc6485374dda41040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 10:32:07 +0100 Subject: [PATCH 43/52] Fix lint --- src/bootstrap/neo4j.js | 2 +- src/seed/factories/index.js | 43 +++++++++++++++++-------------------- src/seed/seed-db.js | 30 +++++++++++++------------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/bootstrap/neo4j.js b/src/bootstrap/neo4j.js index 24a0cd4dd..935449a0a 100644 --- a/src/bootstrap/neo4j.js +++ b/src/bootstrap/neo4j.js @@ -7,7 +7,7 @@ let driver export function getDriver (options = {}) { const { - uri = process.env.NEO4J_URI || 'bolt://localhost:7687', + uri = process.env.NEO4J_URI || 'bolt://localhost:7687', username = process.env.NEO4J_USERNAME || 'neo4j', password = process.env.NEO4J_PASSWORD || 'neo4j' } = options diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index efbf7b248..6eed8a397 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,11 +1,9 @@ -import { GraphQLClient } from 'graphql-request' +import { GraphQLClient, request } from 'graphql-request' import { getDriver } from '../../bootstrap/neo4j' -import { request } from 'graphql-request' export const seedServerHost = 'http://127.0.0.1:4001' -export const testServerHost = 'http://127.0.0.1:4123' -const authenticatedHeaders = async ({ email, password }, host = testServerHost) => { +const authenticatedHeaders = async ({ email, password }, host) => { const mutation = ` mutation { login(email:"${email}", password:"${password}"){ @@ -19,21 +17,21 @@ const authenticatedHeaders = async ({ email, password }, host = testServerHost) } const factories = { - 'badge': require('./badges.js').default, - 'user': require('./users.js').default, + '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 + '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, + 'user': require('./users.js').relate, 'organization': require('./organizations.js').relate, - 'post': require('./posts.js').relate, - 'comment': require('./comments.js').relate + 'post': require('./posts.js').relate, + 'comment': require('./comments.js').relate } export const create = (model, parameters, options) => { @@ -57,7 +55,7 @@ export const cleanDatabase = async (options = {}) => { try { return await session.run(cypher) } catch (error) { - throw(error) + throw (error) } finally { session.close() } @@ -66,8 +64,7 @@ export const cleanDatabase = async (options = {}) => { export default function factoryFun (options = {}) { const { neo4jDriver = getDriver(), - seedServerHost = 'http://127.0.0.1:4001', - testServerHost = 'http://127.0.0.1:4123' + seedServerHost = 'http://127.0.0.1:4001' } = options const graphQLClient = new GraphQLClient(seedServerHost) @@ -77,24 +74,24 @@ export default function factoryFun (options = {}) { seedServerHost, graphQLClient, lastResponse: null, - async authenticateAs({email, password}) { - const headers = await authenticatedHeaders({email, password}, seedServerHost) + 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) { + async create (node, properties) { const mutation = factories[node](properties) this.lastResponse = await this.graphQLClient.request(mutation) return this }, - async relate(node, relationship, properties) { + 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}) + async cleanDatabase () { + this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver }) return this } } diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 1075c61d6..df5126329 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,4 +1,4 @@ -import factoryFun, { create, relate, seedServerHost as host } from './factories' +import factoryFun, { create, relate } from './factories' /* eslint-disable no-multi-spaces */ (async function () { @@ -79,22 +79,22 @@ import factoryFun, { create, relate, seedServerHost as host } from './factories' ]) await Promise.all([ - asAdmin .create('post', { id: 'p0' }), + 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' }), + 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' }), + 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' }) + asUser.create('post', { id: 'p14' }), + asTick.create('post', { id: 'p15' }) ]) await Promise.all([ @@ -169,7 +169,7 @@ import factoryFun, { create, relate, seedServerHost as host } from './factories' ]) await Promise.all([ - asTick .create('report', { description: 'I don\'t like this comment', resource: { id: 'c1', type: 'comment' } }), + 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' } }) ]) From aa07a2a6167cac5e06d3267745209dc787ec489b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 10:45:34 +0100 Subject: [PATCH 44/52] Refactor Id Middleware I found a way to cleanly request additional attributes in our middleware. We can use this pattern if we e.g. require the author of posts and comments to check if the user is the author and therefore authorized to update or delete the post. CC @mattwr18 @appinteractive @tirokk --- src/middleware/idMiddleware.js | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/middleware/idMiddleware.js b/src/middleware/idMiddleware.js index 59224bd64..d6dac0580 100644 --- a/src/middleware/idMiddleware.js +++ b/src/middleware/idMiddleware.js @@ -1,25 +1,17 @@ -import find from 'lodash/find' +import cloneDeep from 'lodash/cloneDeep' -const includeId = async (resolve, root, args, context, info) => { - let isIdPresent - let removeIdFromResult - 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 - } +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) - 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 + copy.fieldNodes[0].selectionSet.selections.unshift({ + kind: 'Field', + name: { kind: 'Name', value: 'id' } + }) + return resolve(root, args, context, copy) } export default { From 132dba04dd1f28a169c669fb6b318827cad3f775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 15:40:31 +0100 Subject: [PATCH 45/52] Refactor tests with the new factory API --- src/graphql-schema.spec.js | 39 +++++++++------- src/jest/helpers.js | 6 ++- src/middleware/permissionsMiddleware.spec.js | 49 +++++++++++--------- src/middleware/slugifyMiddleware.spec.js | 37 +++++++++------ src/seed/factories/index.js | 2 +- src/seed/seed-db.js | 14 +++--- 6 files changed, 84 insertions(+), 63 deletions(-) diff --git a/src/graphql-schema.spec.js b/src/graphql-schema.spec.js index e61130fe3..463d91af2 100644 --- a/src/graphql-schema.spec.js +++ b/src/graphql-schema.spec.js @@ -1,17 +1,19 @@ -import { create, cleanDatabase } from './seed/factories' +import Factory from './seed/factories' import jwt from 'jsonwebtoken' -import { testServerHost as host, authenticatedHeaders } from './jest/helpers' +import { host, login } from './jest/helpers' import { GraphQLClient, request } from 'graphql-request' +const factory = Factory() + beforeEach(async () => { - await create('user', { + await factory.create('user', { email: 'test@example.org', password: '1234' }) }) afterEach(async () => { - await cleanDatabase() + await factory.cleanDatabase() }) describe('login', () => { @@ -28,7 +30,10 @@ describe('login', () => { 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 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') @@ -39,21 +44,23 @@ describe('login', () => { 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.') - } + await expect( + request(host, mutation({ + email: 'test@example.org', + password: 'wrong' + })) + ).rejects.toThrow('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.') - } + await expect( + request(host, mutation({ + email: 'non-existent@example.org', + password: 'wrong' + })) + ).rejects.toThrow('Incorrect email address or password.') }) }) }) @@ -76,7 +83,7 @@ describe('CreatePost', () => { let headers let response beforeEach(async () => { - headers = await authenticatedHeaders({ email: 'test@example.org', password: '1234' }) + headers = await login({ email: 'test@example.org', password: '1234' }) client = new GraphQLClient(host, { headers }) response = await client.request(`mutation { CreatePost( diff --git a/src/jest/helpers.js b/src/jest/helpers.js index cb86bf785..ff6a535e2 100644 --- a/src/jest/helpers.js +++ b/src/jest/helpers.js @@ -1,8 +1,10 @@ import { request } from 'graphql-request' -export const testServerHost = 'http://127.0.0.1:4123' +// 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 }, host = testServerHost) { +export async function login ({ email, password }) { const mutation = ` mutation { login(email:"${email}", password:"${password}"){ diff --git a/src/middleware/permissionsMiddleware.spec.js b/src/middleware/permissionsMiddleware.spec.js index 7a5eacfce..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 { testServerHost as 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,28 +20,28 @@ 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(headers)).rejects.toThrow('Not Authorised!') + 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.data).toEqual({ User: [ { email: null } ] }) } @@ -47,30 +49,33 @@ describe('authorization', () => { }) 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', () => { + describe('authenticated as another user', () => { beforeEach(async () => { - headers = await authenticatedHeaders({ + loginCredentials = { email: 'someone@example.org', password: 'else' - }) + } }) it('rejects', async () => { - await expect(action(headers)).rejects.toThrow('Not Authorised!') + 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.data).toEqual({ User: [ { email: null } ] }) } diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js index b8e74cd7f..aaa09d29a 100644 --- a/src/middleware/slugifyMiddleware.spec.js +++ b/src/middleware/slugifyMiddleware.spec.js @@ -1,41 +1,48 @@ -import { create, cleanDatabase } from '../seed/factories' -import { testServerHost as host, authenticatedHeaders } from '../jest/helpers' +import Factory from '../seed/factories' +import { host, login } from '../jest/helpers' import { GraphQLClient } from 'graphql-request' -let client +let authenticatedClient let headers +const factory = Factory() + beforeEach(async () => { - await create('user', { email: 'user@example.org', password: '1234' }) - headers = await authenticatedHeaders({ email: 'user@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) + 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 cleanDatabase() + await factory.cleanDatabase() }) describe('slugify', () => { describe('CreatePost', () => { it('generates a slug based on title', async () => { - const response = await client.request(`mutation { + const response = await authenticatedClient.request(`mutation { CreatePost( title: "I am a brand new post", content: "Some content" ) { slug } - }`, { headers }) + }`) expect(response).toEqual({ CreatePost: { slug: 'i-am-a-brand-new-post' } }) }) describe('if slug exists', () => { beforeEach(async () => { - await create('post', { + const asSomeoneElse = await Factory().authenticateAs({ + email: 'someone@example.org', + password: '1234' + }) + await asSomeoneElse.create('post', { title: 'Pre-existing post', slug: 'pre-existing-post' - }, { headers }) + }) }) it('chooses another slug', async () => { - const response = await client.request(`mutation { + const response = await authenticatedClient.request(`mutation { CreatePost( title: "Pre-existing post", content: "Some content" @@ -46,7 +53,7 @@ describe('slugify', () => { describe('but if the client specifies a slug', () => { it('rejects CreatePost', async () => { - await expect(client.request(`mutation { + await expect(authenticatedClient.request(`mutation { CreatePost( title: "Pre-existing post", content: "Some content", @@ -61,9 +68,9 @@ describe('slugify', () => { describe('CreateUser', () => { const action = async (mutation, params) => { - return client.request(`mutation { + return authenticatedClient.request(`mutation { ${mutation}(password: "yo", ${params}) { slug } - }`, { headers }) + }`) } it('generates a slug based on name', async () => { await expect(action('CreateUser', 'name: "I am a user"')) diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 6eed8a397..fef83ff55 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -61,7 +61,7 @@ export const cleanDatabase = async (options = {}) => { } } -export default function factoryFun (options = {}) { +export default function Factory (options = {}) { const { neo4jDriver = getDriver(), seedServerHost = 'http://127.0.0.1:4001' diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index df5126329..58984b12a 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,4 +1,4 @@ -import factoryFun, { create, relate } from './factories' +import Factory, { create, relate } from './factories' /* eslint-disable no-multi-spaces */ (async function () { @@ -70,12 +70,12 @@ import factoryFun, { create, relate } from './factories' ]) const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([ - factoryFun().authenticateAs({ email: 'admin@example.org', password: '1234' }), - factoryFun().authenticateAs({ email: 'moderator@example.org', password: '1234' }), - factoryFun().authenticateAs({ email: 'user@example.org', password: '1234' }), - factoryFun().authenticateAs({ email: 'tick@example.org', password: '1234' }), - factoryFun().authenticateAs({ email: 'trick@example.org', password: '1234' }), - factoryFun().authenticateAs({ email: 'track@example.org', password: '1234' }) + 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([ From eda2ea34fcb00d12f9879a97e9b3baf4dcd67b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 21 Feb 2019 20:00:35 +0100 Subject: [PATCH 46/52] Incorporate @appinteractive's feedback @appinteractive can we merge this soon? --- src/middleware/permissionsMiddleware.js | 6 +++--- src/seed/factories/badges.js | 17 +++++++++-------- src/seed/factories/categories.js | 12 ++++++------ src/seed/factories/comments.js | 5 +++-- src/seed/factories/index.js | 2 +- src/seed/factories/organizations.js | 5 +++-- src/seed/factories/posts.js | 5 +++-- src/seed/factories/tags.js | 4 ++-- src/seed/factories/users.js | 9 +++++---- 9 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 0bd88b274..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 myself = 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 }) @@ -36,8 +36,8 @@ const permissions = shield({ // CreateUser: allow, }, User: { - email: myself, - password: myself + email: isMyOwn, + password: isMyOwn } // Post: isAuthenticated }) diff --git a/src/seed/factories/badges.js b/src/seed/factories/badges.js index 2aabe2014..b34442521 100644 --- a/src/seed/factories/badges.js +++ b/src/seed/factories/badges.js @@ -1,8 +1,8 @@ -import faker from 'faker' +import uuid from 'uuid/v4' export default function (params) { const { - id = `cat${faker.random.number()}`, + id = uuid(), key, type = 'crowdfunding', status = 'permanent', @@ -11,12 +11,13 @@ export default function (params) { return ` mutation { - ${id}: CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}") { id } + 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 index 287aac5e0..a4b448f4b 100644 --- a/src/seed/factories/categories.js +++ b/src/seed/factories/categories.js @@ -1,8 +1,8 @@ -import faker from 'faker' +import uuid from 'uuid/v4' export default function (params) { const { - id = `cat${faker.random.number()}`, + id = uuid(), name, slug, icon @@ -11,10 +11,10 @@ export default function (params) { return ` mutation { CreateCategory( - id: "${id}", - name: "${name}", - slug: "${slug}", - icon: "${icon}" + id: "${id}", + name: "${name}", + slug: "${slug}", + icon: "${icon}" ) { id, name } } ` diff --git a/src/seed/factories/comments.js b/src/seed/factories/comments.js index b4e25c482..acf493f6d 100644 --- a/src/seed/factories/comments.js +++ b/src/seed/factories/comments.js @@ -1,8 +1,9 @@ import faker from 'faker' +import uuid from 'uuid/v4' export default function (params) { const { - id = `c${faker.random.number()}`, + id = uuid(), content = [ faker.lorem.sentence(), faker.lorem.sentence() @@ -13,7 +14,7 @@ export default function (params) { return ` mutation { - ${id}: CreateComment( + CreateComment( id: "${id}", content: "${content}", disabled: ${disabled}, diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index fef83ff55..a107fc6b7 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -51,7 +51,7 @@ export const cleanDatabase = async (options = {}) => { driver = getDriver() } = options const session = driver.session() - const cypher = 'MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r' + const cypher = 'MATCH (n) DETACH DELETE n' try { return await session.run(cypher) } catch (error) { diff --git a/src/seed/factories/organizations.js b/src/seed/factories/organizations.js index c0329d55d..0ab73beb8 100644 --- a/src/seed/factories/organizations.js +++ b/src/seed/factories/organizations.js @@ -1,8 +1,9 @@ import faker from 'faker' +import uuid from 'uuid/v4' export default function create (params) { const { - id = `o${faker.random.number()}`, + id = uuid(), name = faker.comany.companyName(), description = faker.company.catchPhrase(), disabled = false, @@ -11,7 +12,7 @@ export default function create (params) { return ` mutation { - ${id}: CreateOrganization( + CreateOrganization( id: "${id}", name: "${name}", description: "${description}", diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index eaa616b2b..80f5e289d 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -1,8 +1,9 @@ import faker from 'faker' +import uuid from 'uuid/v4' export default function (params) { const { - id = `p${faker.random.number()}`, + id = uuid(), title = faker.lorem.sentence(), content = [ faker.lorem.sentence(), @@ -19,7 +20,7 @@ export default function (params) { return ` mutation { - ${id}: CreatePost( + CreatePost( id: "${id}", title: "${title}", content: "${content}", diff --git a/src/seed/factories/tags.js b/src/seed/factories/tags.js index 0854cc0cd..c603c5629 100644 --- a/src/seed/factories/tags.js +++ b/src/seed/factories/tags.js @@ -1,8 +1,8 @@ -import faker from 'faker' +import uuid from 'uuid/v4' export default function (params) { const { - id = `t${faker.random.number()}`, + id = uuid(), name } = params diff --git a/src/seed/factories/users.js b/src/seed/factories/users.js index 2e94b5ebe..b3a6e83c1 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -1,8 +1,9 @@ import faker from 'faker' +import uuid from 'uuid/v4' export default function create (params) { const { - id = `u${faker.random.number()}`, + id = uuid(), name = faker.name.findName(), email = faker.internet.email(), password = '1234', @@ -14,7 +15,7 @@ export default function create (params) { return ` mutation { - ${id}: CreateUser( + CreateUser( id: "${id}", name: "${name}", password: "${password}", @@ -22,8 +23,8 @@ export default function create (params) { avatar: "${avatar}", role: ${role}, disabled: ${disabled}, - deleted: ${deleted}) { - id + deleted: ${deleted} + ) { name email avatar From 889818cd2de5ae5bca1bced8dcf4335045af4cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 22 Feb 2019 15:21:23 +0100 Subject: [PATCH 47/52] Expose bug in Query { isLoggedin } The idMiddleware was adding selection fields to the resolveInfo even if the selection fields were empty. This caused a bug for each resolve function including ``` { isLoggedin } ``` which does not have any additional curly braces after the query. --- src/graphql-schema.spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/graphql-schema.spec.js b/src/graphql-schema.spec.js index 463d91af2..2211a514a 100644 --- a/src/graphql-schema.spec.js +++ b/src/graphql-schema.spec.js @@ -16,6 +16,15 @@ afterEach(async () => { await factory.cleanDatabase() }) +describe('isLoggedIn', () => { + describe('unauthenticated', () => { + it('returns false', async () => { + const query = '{ isLoggedIn }' + await expect(request(host, query)).resolves.toEqual({ isLoggedIn: false }) + }) + }) +}) + describe('login', () => { const mutation = (params) => { const { email, password } = params From 2e7e0579e46b8bbded5827402d6c83e43bb68248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 22 Feb 2019 16:11:07 +0100 Subject: [PATCH 48/52] Fix bug with id middleware Ids are queried only for well known queries and mutations --- src/graphql-schema.js | 1 - src/middleware/idMiddleware.js | 12 ++++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/graphql-schema.js b/src/graphql-schema.js index 521ac97bd..ce84dde36 100644 --- a/src/graphql-schema.js +++ b/src/graphql-schema.js @@ -1,4 +1,3 @@ -// import { neo4jgraphql } from "neo4j-graphql-js" import fs from 'fs' import path from 'path' import bcrypt from 'bcryptjs' diff --git a/src/middleware/idMiddleware.js b/src/middleware/idMiddleware.js index d6dac0580..2f1854fa6 100644 --- a/src/middleware/idMiddleware.js +++ b/src/middleware/idMiddleware.js @@ -15,10 +15,14 @@ const includeId = async (resolve, root, args, context, resolveInfo) => { } export default { - Query: (resolve, root, args, context, info) => { - return includeId(resolve, root, args, context, info) + Query: { + User: (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) + Mutation: { + CreatePost: (resolve, root, args, context, info) => { + return includeId(resolve, root, args, context, info) + } } } From aef367b210b6ed51cb3cdb3ba72f1c6c96c57bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 25 Feb 2019 19:33:51 +0100 Subject: [PATCH 49/52] Backend runs test- and seed-server on travis This is necessary for fullstack testing in the frontend repository --- docker-compose.travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.travis.yml b/docker-compose.travis.yml index e1998f6dd..3d577e638 100644 --- a/docker-compose.travis.yml +++ b/docker-compose.travis.yml @@ -8,7 +8,11 @@ services: - 7687:7687 - 7474:7474 backend: + ports: + - 4001:4001 + - 4123:4123 image: humanconnection/nitro-backend:builder build: context: . target: builder + command: yarn run test:cypress From ec2b1050bccc3bd1d1e821a624450e0ee4a85311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 25 Feb 2019 21:23:45 +0100 Subject: [PATCH 50/52] Less redundancy and more consistency in factories * use the same `relate` method for now * use a capital letter for the model `user` instead of `User` --- src/graphql-schema.spec.js | 6 +- src/middleware/permissionsMiddleware.spec.js | 4 +- src/middleware/slugifyMiddleware.spec.js | 6 +- src/seed/factories/comments.js | 12 - src/seed/factories/index.js | 58 ++-- src/seed/factories/organizations.js | 12 - src/seed/factories/posts.js | 12 - src/seed/factories/users.js | 12 - src/seed/seed-db.js | 277 ++++++++++--------- 9 files changed, 175 insertions(+), 224 deletions(-) diff --git a/src/graphql-schema.spec.js b/src/graphql-schema.spec.js index 3d259ab42..7aa47835f 100644 --- a/src/graphql-schema.spec.js +++ b/src/graphql-schema.spec.js @@ -6,7 +6,7 @@ import { host, login } from './jest/helpers' const factory = Factory() beforeEach(async () => { - await factory.create('user', { + await factory.create('User', { email: 'test@example.org', password: '1234' }) @@ -122,11 +122,11 @@ describe('CreatePost', () => { describe('report', () => { beforeEach(async () => { - await factory.create('user', { + await factory.create('User', { email: 'test@example.org', password: '1234' }) - await factory.create('user', { + await factory.create('User', { id: 'u2', name: 'abusive-user', role: 'user', diff --git a/src/middleware/permissionsMiddleware.spec.js b/src/middleware/permissionsMiddleware.spec.js index a22f2bb72..78766aad2 100644 --- a/src/middleware/permissionsMiddleware.spec.js +++ b/src/middleware/permissionsMiddleware.spec.js @@ -7,12 +7,12 @@ const factory = Factory() describe('authorization', () => { describe('given two existing users', () => { beforeEach(async () => { - await factory.create('user', { + await factory.create('User', { email: 'owner@example.org', name: 'Owner', password: 'iamtheowner' }) - await factory.create('user', { + await factory.create('User', { email: 'someone@example.org', name: 'Someone else', password: 'else' diff --git a/src/middleware/slugifyMiddleware.spec.js b/src/middleware/slugifyMiddleware.spec.js index aaa09d29a..c7cd9806f 100644 --- a/src/middleware/slugifyMiddleware.spec.js +++ b/src/middleware/slugifyMiddleware.spec.js @@ -7,8 +7,8 @@ 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' }) + 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 }) }) @@ -35,7 +35,7 @@ describe('slugify', () => { email: 'someone@example.org', password: '1234' }) - await asSomeoneElse.create('post', { + await asSomeoneElse.create('Post', { title: 'Pre-existing post', slug: 'pre-existing-post' }) diff --git a/src/seed/factories/comments.js b/src/seed/factories/comments.js index acf493f6d..92dca5b14 100644 --- a/src/seed/factories/comments.js +++ b/src/seed/factories/comments.js @@ -23,15 +23,3 @@ export default function (params) { } ` } - -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 a107fc6b7..752ae3369 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,6 +1,15 @@ import { GraphQLClient, request } from 'graphql-request' import { getDriver } from '../../bootstrap/neo4j' +import createBadge from './badges.js' +import createUser from './users.js' +import createOrganization from './organizations.js' +import createPost from './posts.js' +import createComment from './comments.js' +import createCategory from './categories.js' +import createTag from './tags.js' +import createReport from './reports.js' + export const seedServerHost = 'http://127.0.0.1:4001' const authenticatedHeaders = async ({ email, password }, host) => { @@ -15,35 +24,15 @@ const authenticatedHeaders = async ({ email, password }, host) => { authorization: `Bearer ${response.login.token}` } } - 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) + 'Badge': createBadge, + 'User': createUser, + 'Organization': createOrganization, + 'Post': createPost, + 'Comment': createComment, + 'Category': createCategory, + 'Tag': createTag, + 'Report': createReport } export const cleanDatabase = async (options = {}) => { @@ -73,6 +62,7 @@ export default function Factory (options = {}) { neo4jDriver, seedServerHost, graphQLClient, + factories, lastResponse: null, async authenticateAs ({ email, password }) { const headers = await authenticatedHeaders({ email, password }, seedServerHost) @@ -81,12 +71,20 @@ export default function Factory (options = {}) { return this }, async create (node, properties) { - const mutation = factories[node](properties) + const mutation = this.factories[node](properties) this.lastResponse = await this.graphQLClient.request(mutation) return this }, async relate (node, relationship, properties) { - const mutation = relationFactories[node](relationship, properties) + const { from, to } = properties + const mutation = ` + mutation { + Add${node}${relationship}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + ` this.lastResponse = await this.graphQLClient.request(mutation) return this }, diff --git a/src/seed/factories/organizations.js b/src/seed/factories/organizations.js index 0ab73beb8..783edac26 100644 --- a/src/seed/factories/organizations.js +++ b/src/seed/factories/organizations.js @@ -22,15 +22,3 @@ export default function create (params) { } ` } - -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 index 80f5e289d..d96cf4f73 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -32,15 +32,3 @@ export default function (params) { } ` } - -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/users.js b/src/seed/factories/users.js index b3a6e83c1..8e0ee693c 100644 --- a/src/seed/factories/users.js +++ b/src/seed/factories/users.js @@ -33,15 +33,3 @@ export default function create (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/seed-db.js b/src/seed/seed-db.js index 58984b12a..b2ee8fbdb 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -1,72 +1,73 @@ -import Factory, { create, relate } from './factories' +import Factory from './factories' /* eslint-disable no-multi-spaces */ (async function () { try { + const f = Factory() 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' }) + f.create('Badge', { id: 'b1', key: 'indiegogo_en_racoon', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_racoon.svg' }), + f.create('Badge', { id: 'b2', key: 'indiegogo_en_rabbit', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_rabbit.svg' }), + f.create('Badge', { id: 'b3', key: 'indiegogo_en_wolf', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_wolf.svg' }), + f.create('Badge', { id: 'b4', key: 'indiegogo_en_bear', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_bear.svg' }), + f.create('Badge', { id: 'b5', key: 'indiegogo_en_turtle', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_turtle.svg' }), + f.create('Badge', { id: 'b6', key: 'indiegogo_en_rhino', type: 'crowdfunding', status: 'permanent', icon: '/img/badges/indiegogo_en_rhino.svg' }) ]) 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' }) + f.create('User', { id: 'u1', name: 'Peter Lustig', role: 'admin', email: 'admin@example.org' }), + f.create('User', { id: 'u2', name: 'Bob der Baumeister', role: 'moderator', email: 'moderator@example.org' }), + f.create('User', { id: 'u3', name: 'Jenny Rostock', role: 'user', email: 'user@example.org' }), + f.create('User', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }), + f.create('User', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }), + f.create('User', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }), + f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }) ]) 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' }) + f.relate('User', 'Badges', { from: 'b6', to: 'u1' }), + f.relate('User', 'Badges', { from: 'b5', to: 'u2' }), + f.relate('User', 'Badges', { from: 'b4', to: 'u3' }), + f.relate('User', 'Badges', { from: 'b3', to: 'u4' }), + f.relate('User', 'Badges', { from: 'b2', to: 'u5' }), + f.relate('User', 'Badges', { from: 'b1', to: 'u6' }), + f.relate('User', 'Following', { from: 'u1', to: 'u2' }), + f.relate('User', 'Following', { from: 'u2', to: 'u3' }), + f.relate('User', 'Following', { from: 'u3', to: 'u4' }), + f.relate('User', 'Following', { from: 'u4', to: 'u5' }), + f.relate('User', 'Following', { from: 'u5', to: 'u6' }), + f.relate('User', 'Following', { from: 'u6', to: 'u7' }), + f.relate('User', 'Friends', { from: 'u1', to: 'u2' }), + f.relate('User', 'Friends', { from: 'u1', to: 'u3' }), + f.relate('User', 'Friends', { from: 'u2', to: 'u3' }), + f.relate('User', 'Blacklisted', { from: 'u7', to: 'u4' }), + f.relate('User', 'Blacklisted', { from: 'u7', to: 'u5' }), + f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' }) ]) 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' }) + f.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }), + f.create('Category', { id: 'cat2', name: 'Happyness & Values', slug: 'happyness-values', icon: 'heart-o' }), + f.create('Category', { id: 'cat3', name: 'Health & Wellbeing', slug: 'health-wellbeing', icon: 'medkit' }), + f.create('Category', { id: 'cat4', name: 'Environment & Nature', slug: 'environment-nature', icon: 'tree' }), + f.create('Category', { id: 'cat5', name: 'Animal Protection', slug: 'animalprotection', icon: 'paw' }), + f.create('Category', { id: 'cat6', name: 'Humanrights Justice', slug: 'humanrights-justice', icon: 'balance-scale' }), + f.create('Category', { id: 'cat7', name: 'Education & Sciences', slug: 'education-sciences', icon: 'graduation-cap' }), + f.create('Category', { id: 'cat8', name: 'Cooperation & Development', slug: 'cooperation-development', icon: 'users' }), + f.create('Category', { id: 'cat9', name: 'Democracy & Politics', slug: 'democracy-politics', icon: 'university' }), + f.create('Category', { id: 'cat10', name: 'Economy & Finances', slug: 'economy-finances', icon: 'money' }), + f.create('Category', { id: 'cat11', name: 'Energy & Technology', slug: 'energy-technology', icon: 'flash' }), + f.create('Category', { id: 'cat12', name: 'IT, Internet & Data Privacy', slug: 'it-internet-dataprivacy', icon: 'mouse-pointer' }), + f.create('Category', { id: 'cat13', name: 'Art, Curlure & Sport', slug: 'art-culture-sport', icon: 'paint-brush' }), + f.create('Category', { id: 'cat14', name: 'Freedom of Speech', slug: 'freedomofspeech', icon: 'bullhorn' }), + f.create('Category', { id: 'cat15', name: 'Consumption & Sustainability', slug: 'consumption-sustainability', icon: 'shopping-cart' }), + f.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' }) + f.create('Tag', { id: 't1', name: 'Umwelt' }), + f.create('Tag', { id: 't2', name: 'Naturschutz' }), + f.create('Tag', { id: 't3', name: 'Demokratie' }), + f.create('Tag', { id: 't4', name: 'Freiheit' }) ]) const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([ @@ -79,113 +80,113 @@ import Factory, { create, relate } from './factories' ]) 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' }) + 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' }), + f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }), + f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }), + f.relate('Post', 'Categories', { from: 'p2', to: 'cat2' }), + f.relate('Post', 'Categories', { from: 'p3', to: 'cat3' }), + f.relate('Post', 'Categories', { from: 'p4', to: 'cat4' }), + f.relate('Post', 'Categories', { from: 'p5', to: 'cat5' }), + f.relate('Post', 'Categories', { from: 'p6', to: 'cat6' }), + f.relate('Post', 'Categories', { from: 'p7', to: 'cat7' }), + f.relate('Post', 'Categories', { from: 'p8', to: 'cat8' }), + f.relate('Post', 'Categories', { from: 'p9', to: 'cat9' }), + f.relate('Post', 'Categories', { from: 'p10', to: 'cat10' }), + f.relate('Post', 'Categories', { from: 'p11', to: 'cat11' }), + f.relate('Post', 'Categories', { from: 'p12', to: 'cat12' }), + f.relate('Post', 'Categories', { from: 'p13', to: 'cat13' }), + f.relate('Post', 'Categories', { from: 'p14', to: 'cat14' }), + f.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' }) + f.relate('Post', 'Tags', { from: 'p0', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p1', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p2', to: 't2' }), + f.relate('Post', 'Tags', { from: 'p3', to: 't3' }), + f.relate('Post', 'Tags', { from: 'p4', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p5', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p6', to: 't2' }), + f.relate('Post', 'Tags', { from: 'p7', to: 't3' }), + f.relate('Post', 'Tags', { from: 'p8', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p9', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p10', to: 't2' }), + f.relate('Post', 'Tags', { from: 'p11', to: 't3' }), + f.relate('Post', 'Tags', { from: 'p12', to: 't4' }), + f.relate('Post', 'Tags', { from: 'p13', to: 't1' }), + f.relate('Post', 'Tags', { from: 'p14', to: 't2' }), + f.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' }) + f.relate('User', 'Shouted', { from: 'u1', to: 'p2' }), + f.relate('User', 'Shouted', { from: 'u1', to: 'p3' }), + f.relate('User', 'Shouted', { from: 'u2', to: 'p1' }), + f.relate('User', 'Shouted', { from: 'u3', to: 'p1' }), + f.relate('User', 'Shouted', { from: 'u3', to: 'p4' }), + f.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' }) + f.create('Comment', { id: 'c1' }), + f.create('Comment', { id: 'c2' }), + f.create('Comment', { id: 'c3' }), + f.create('Comment', { id: 'c4' }), + f.create('Comment', { id: 'c5' }), + f.create('Comment', { id: 'c6' }), + f.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' }) + f.relate('Comment', 'Author', { from: 'u3', to: 'c1' }), + f.relate('Comment', 'Post', { from: 'c1', to: 'p1' }), + f.relate('Comment', 'Author', { from: 'u1', to: 'c2' }), + f.relate('Comment', 'Post', { from: 'c2', to: 'p1' }), + f.relate('Comment', 'Author', { from: 'u1', to: 'c3' }), + f.relate('Comment', 'Post', { from: 'c3', to: 'p3' }), + f.relate('Comment', 'Author', { from: 'u4', to: 'c4' }), + f.relate('Comment', 'Post', { from: 'c4', to: 'p2' }), + f.relate('Comment', 'Author', { from: 'u4', to: 'c5' }), + f.relate('Comment', 'Post', { from: 'c5', to: 'p3' }), + f.relate('Comment', 'Author', { from: 'u3', to: 'c6' }), + f.relate('Comment', 'Post', { from: 'c6', to: 'p4' }), + f.relate('Comment', 'Author', { from: 'u2', to: 'c7' }), + f.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' } }) + 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.' }) + f.create('Organization', { id: 'o1', name: 'Democracy Deutschland', description: 'Description for democracy-deutschland.' }), + f.create('Organization', { id: 'o2', name: 'Human-Connection', description: 'Description for human-connection.' }), + f.create('Organization', { id: 'o3', name: 'Pro Veg', description: 'Description for pro-veg.' }), + f.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' }) + f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o1' }), + f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o2' }), + f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o2' }), + f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o3' }) ]) /* eslint-disable-next-line no-console */ console.log('Seeded Data...') From c4f15e626d433ca2f740d2bf5497909407b604a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 26 Feb 2019 00:46:14 +0100 Subject: [PATCH 51/52] Fix lint and unblock port 4001 We have to specify the `yarn run test:cypress` in `.travis.yml` in the Nitro-Web repo. --- docker-compose.travis.yml | 1 - src/seed/factories/index.js | 28 ++++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docker-compose.travis.yml b/docker-compose.travis.yml index 3d577e638..27f53dfef 100644 --- a/docker-compose.travis.yml +++ b/docker-compose.travis.yml @@ -15,4 +15,3 @@ services: build: context: . target: builder - command: yarn run test:cypress diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index 752ae3369..d9bbd700c 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,14 +1,14 @@ import { GraphQLClient, request } from 'graphql-request' import { getDriver } from '../../bootstrap/neo4j' -import createBadge from './badges.js' -import createUser from './users.js' +import createBadge from './badges.js' +import createUser from './users.js' import createOrganization from './organizations.js' -import createPost from './posts.js' -import createComment from './comments.js' -import createCategory from './categories.js' -import createTag from './tags.js' -import createReport from './reports.js' +import createPost from './posts.js' +import createComment from './comments.js' +import createCategory from './categories.js' +import createTag from './tags.js' +import createReport from './reports.js' export const seedServerHost = 'http://127.0.0.1:4001' @@ -25,14 +25,14 @@ const authenticatedHeaders = async ({ email, password }, host) => { } } const factories = { - 'Badge': createBadge, - 'User': createUser, + 'Badge': createBadge, + 'User': createUser, 'Organization': createOrganization, - 'Post': createPost, - 'Comment': createComment, - 'Category': createCategory, - 'Tag': createTag, - 'Report': createReport + 'Post': createPost, + 'Comment': createComment, + 'Category': createCategory, + 'Tag': createTag, + 'Report': createReport } export const cleanDatabase = async (options = {}) => { From 706fccc7332e07babac0a077dc25d28eb107306f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 26 Feb 2019 12:16:27 +0100 Subject: [PATCH 52/52] Fix Cypress with a dedicated docker-compose.yml --- docker-compose.cypress.yml | 18 ++++++++++++++++++ docker-compose.travis.yml | 3 --- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 docker-compose.cypress.yml diff --git a/docker-compose.cypress.yml b/docker-compose.cypress.yml new file mode 100644 index 000000000..3d577e638 --- /dev/null +++ b/docker-compose.cypress.yml @@ -0,0 +1,18 @@ +version: "3.7" + +services: + neo4j: + environment: + - NEO4J_AUTH=none + ports: + - 7687:7687 + - 7474:7474 + backend: + ports: + - 4001:4001 + - 4123:4123 + image: humanconnection/nitro-backend:builder + build: + context: . + target: builder + command: yarn run test:cypress diff --git a/docker-compose.travis.yml b/docker-compose.travis.yml index 27f53dfef..e1998f6dd 100644 --- a/docker-compose.travis.yml +++ b/docker-compose.travis.yml @@ -8,9 +8,6 @@ services: - 7687:7687 - 7474:7474 backend: - ports: - - 4001:4001 - - 4123:4123 image: humanconnection/nitro-backend:builder build: context: .