From d4ce1ef80baac3e91f7e3864e5b920f812909c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 12 Apr 2019 16:00:44 +0200 Subject: [PATCH 001/124] Localized HC link on Login page --- webapp/locales/de.json | 1 + webapp/locales/en.json | 1 + webapp/pages/login.vue | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 6e47d7122..ef9600c0e 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -6,6 +6,7 @@ "email": "Deine E-Mail", "password": "Dein Passwort", "moreInfo": "Was ist Human Connection?", + "moreInfoHint": "zur Präsentationsseite", "hello": "Hallo" }, "profile": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 62c8f3e19..7c0b747c8 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -6,6 +6,7 @@ "email": "Your Email", "password": "Your Password", "moreInfo": "What is Human Connection?", + "moreInfoHint": "to the presentation page", "hello": "Hello" }, "profile": { diff --git a/webapp/pages/login.vue b/webapp/pages/login.vue index 846191c40..be2215fab 100644 --- a/webapp/pages/login.vue +++ b/webapp/pages/login.vue @@ -80,7 +80,7 @@ {{ $t('login.moreInfo') }} From 26be224464771b835fe062f9e3eddc0119d82f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 12 Apr 2019 16:08:39 +0200 Subject: [PATCH 002/124] Localised HC URL on Login page --- webapp/locales/de.json | 1 + webapp/locales/en.json | 1 + webapp/pages/login.vue | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index ef9600c0e..43885ea17 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -6,6 +6,7 @@ "email": "Deine E-Mail", "password": "Dein Passwort", "moreInfo": "Was ist Human Connection?", + "moreInfoURL": "https://human-connection.org", "moreInfoHint": "zur Präsentationsseite", "hello": "Hallo" }, diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 7c0b747c8..9ea58225f 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -6,6 +6,7 @@ "email": "Your Email", "password": "Your Password", "moreInfo": "What is Human Connection?", + "moreInfoURL": "https://human-connection.org/en/", "moreInfoHint": "to the presentation page", "hello": "Hello" }, diff --git a/webapp/pages/login.vue b/webapp/pages/login.vue index be2215fab..92269143b 100644 --- a/webapp/pages/login.vue +++ b/webapp/pages/login.vue @@ -79,7 +79,7 @@ From 674cdddfc3365fa91a6ced93f030be1840a9b2c8 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 16 Apr 2019 13:45:07 -0300 Subject: [PATCH 003/124] Add Editor, cancel/submit buttons for comments - to PagesPost_id_slugIndex.vue - handle simple comment creation using auto-generated resolver Co-authored-by: Joseph "Kachulio" Ngugi --- webapp/locales/en.json | 3 +- webapp/pages/post/_id/_slug/index.vue | 105 +++++++++++++++++++------- 2 files changed, 79 insertions(+), 29 deletions(-) diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 40cc766d0..a6ceae9c9 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -106,7 +106,8 @@ }, "takeAction": { "name": "Take action" - } + }, + "submitComment": "Submit Comment" }, "quotes": { "african": { diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index b72faa850..8af07bc26 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -96,34 +96,67 @@ -

- - - {{ post.commentsCount }}  Comments - -

- -
- + +

+ + + {{ post.commentsCount }}  Comments + +

+
+ + + + + + + + {{ $t('actions.cancel') }} + + + + + {{ $t('post.submitComment') }} + + + + + +
+ +
+ -
- +
@@ -139,6 +172,7 @@ import HcUser from '~/components/User' import HcShoutButton from '~/components/ShoutButton.vue' import HcEmpty from '~/components/Empty.vue' import Comment from '~/components/Comment.vue' +import HcEditor from '~/components/Editor/Editor.vue' export default { transition: { @@ -152,7 +186,8 @@ export default { HcShoutButton, HcEmpty, Comment, - ContentMenu + ContentMenu, + HcEditor }, head() { return { @@ -278,6 +313,20 @@ export default { methods: { isAuthor(id) { return this.$store.getters['auth/user'].id === id + }, + handleSubmit() { + this.$apollo.mutate({ + mutation: gql` + mutation($content: String!) { + CreateComment(content: $content) { + content + } + } + `, + variables: { + content: this.value + } + }) } } } From dfef4fe05fa74bad1ea8e2c91ffb1681538c3e42 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 16 Apr 2019 19:48:59 -0300 Subject: [PATCH 004/124] Add custom resolver, update factories --- backend/src/graphql-schema.js | 4 +++- backend/src/resolvers/comments.js | 28 ++++++++++++++++++++++++++ backend/src/resolvers/socialMedia.js | 2 +- backend/src/schema.graphql | 1 + backend/src/seed/factories/comments.js | 12 +++++------ 5 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 backend/src/resolvers/comments.js diff --git a/backend/src/graphql-schema.js b/backend/src/graphql-schema.js index 1e13c95f4..7befd0507 100644 --- a/backend/src/graphql-schema.js +++ b/backend/src/graphql-schema.js @@ -9,6 +9,7 @@ import moderation from './resolvers/moderation.js' import rewards from './resolvers/rewards.js' import socialMedia from './resolvers/socialMedia.js' import notifications from './resolvers/notifications' +import comments from './resolvers/comments' export const typeDefs = fs .readFileSync( @@ -29,6 +30,7 @@ export const resolvers = { ...moderation.Mutation, ...rewards.Mutation, ...socialMedia.Mutation, - ...notifications.Mutation + ...notifications.Mutation, + ...comments.Mutation } } diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js new file mode 100644 index 000000000..74454322d --- /dev/null +++ b/backend/src/resolvers/comments.js @@ -0,0 +1,28 @@ +import { neo4jgraphql } from 'neo4j-graphql-js' + +export default { + Mutation: { + CreateComment: async (object, params, context, resolveInfo) => { + const { postId } = params + + const result = await neo4jgraphql(object, params, context, resolveInfo, true) + + const session = context.driver.session() + const transactionRes = await session.run(` + MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}) + MERGE (post)<-[:COMMENTS]-(comment) + RETURN comment {.id, .content}`, { + postId, + commentId: result.id + } + ) + const [comment] = transactionRes.records.map(record => { + return record.get('comment') + }) + + session.close() + + return comment + } + } +} diff --git a/backend/src/resolvers/socialMedia.js b/backend/src/resolvers/socialMedia.js index 3adf0e2d0..310375820 100644 --- a/backend/src/resolvers/socialMedia.js +++ b/backend/src/resolvers/socialMedia.js @@ -3,7 +3,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js' export default { Mutation: { CreateSocialMedia: async (object, params, context, resolveInfo) => { - const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, true) + const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false) const session = context.driver.session() await session.run( `MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId}) diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 94e28d0d7..6c35e082d 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -27,6 +27,7 @@ type Mutation { enable(id: ID!): ID reward(fromBadgeId: ID!, toUserId: ID!): ID unreward(fromBadgeId: ID!, toUserId: ID!): ID + CreateComment(id: ID!, postId: ID!, content: String!) : Comment "Shout the given Type and ID" shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """ MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId}) diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index 92dca5b14..29d0722b7 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -4,22 +4,20 @@ import uuid from 'uuid/v4' export default function (params) { const { id = uuid(), + postId = uuid(), content = [ faker.lorem.sentence(), faker.lorem.sentence() - ].join('. '), - disabled = false, - deleted = false + ].join('. ') } = params return ` mutation { CreateComment( id: "${id}", - content: "${content}", - disabled: ${disabled}, - deleted: ${deleted} - ) { id } + postId: "${postId}", + content: "${content}" + ) { id, content } } ` } From 9bc0c0f92cb95b6b0b7a86eac090ba98d76c1c31 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 16 Apr 2019 20:31:34 -0300 Subject: [PATCH 005/124] Add tests, create comments only when authenticated --- .../src/middleware/permissionsMiddleware.js | 3 +- backend/src/resolvers/comments.js | 7 +- backend/src/resolvers/comments.spec.js | 64 +++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 backend/src/resolvers/comments.spec.js diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 3ac43a6e2..3688aec16 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -86,7 +86,8 @@ const permissions = shield({ unshout: isAuthenticated, changePassword: isAuthenticated, enable: isModerator, - disable: isModerator + disable: isModerator, + CreateComment: isAuthenticated // CreateUser: allow, }, User: { diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 74454322d..5ab20cf46 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -5,7 +5,7 @@ export default { CreateComment: async (object, params, context, resolveInfo) => { const { postId } = params - const result = await neo4jgraphql(object, params, context, resolveInfo, true) + const comment = await neo4jgraphql(object, params, context, resolveInfo, true) const session = context.driver.session() const transactionRes = await session.run(` @@ -13,12 +13,9 @@ export default { MERGE (post)<-[:COMMENTS]-(comment) RETURN comment {.id, .content}`, { postId, - commentId: result.id + commentId: comment.id } ) - const [comment] = transactionRes.records.map(record => { - return record.get('comment') - }) session.close() diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js new file mode 100644 index 000000000..e17f94ec9 --- /dev/null +++ b/backend/src/resolvers/comments.spec.js @@ -0,0 +1,64 @@ +import Factory from '../seed/factories' +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../jest/helpers' + +const factory = Factory() +let client +let variables + +beforeEach(async () => { + await factory.create('User', { + email: 'test@example.org', + password: '1234' + }) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('CreateComment', () => { + const mutation = ` + mutation($id: ID!, $postId: ID!, $content: String!) { + CreateComment(id: $id, postId: $postId, content: $content) { + id + content + } + } + ` + describe('unauthenticated', () => { + it('throws authorization error', async () => { + variables = { + id: 'c1', + postId: 'p1', + content: "I'm not authorised to comment" + } + client = new GraphQLClient(host) + await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated', () => { + let headers + beforeEach(async () => { + headers = await login({ email: 'test@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('creates a post', async () => { + variables = { + id: 'c1', + postId: 'p1', + content: "I'm authorised to comment" + } + const expected = { + CreateComment: { + id: 'c1', + content: "I'm authorised to comment" + } + } + + await expect(client.request(mutation, variables)).resolves.toMatchObject(expected) + }) + }) +}) \ No newline at end of file From dd9383ef40fab2b4b616cde39e5e5bca4c1c72d0 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 16 Apr 2019 23:02:57 -0300 Subject: [PATCH 006/124] Create two custom resolvers, get working with front end - Had difficulty adding a relationship with one custom resolver, if id for comment was not passed in, the comment was not created, hard coding it in also wasn't a good solution --- backend/src/resolvers/comments.js | 23 ++----- backend/src/resolvers/comments.spec.js | 12 ++-- backend/src/schema.graphql | 1 - backend/src/seed/factories/comments.js | 2 - webapp/graphql/AddPostComments.js | 18 ++++++ webapp/pages/post/_id/_slug/index.vue | 90 +++++++++++++++++--------- 6 files changed, 89 insertions(+), 57 deletions(-) create mode 100644 webapp/graphql/AddPostComments.js diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 5ab20cf46..4542c075a 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -2,24 +2,11 @@ import { neo4jgraphql } from 'neo4j-graphql-js' export default { Mutation: { - CreateComment: async (object, params, context, resolveInfo) => { - const { postId } = params - - const comment = await neo4jgraphql(object, params, context, resolveInfo, true) - - const session = context.driver.session() - const transactionRes = await session.run(` - MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}) - MERGE (post)<-[:COMMENTS]-(comment) - RETURN comment {.id, .content}`, { - postId, - commentId: comment.id - } - ) - - session.close() - - return comment + CreateComment: (object, params, context, resolveInfo) => { + return neo4jgraphql(object, params, context, resolveInfo, false) + }, + AddPostComments: (object, params, context, resolveInfo) => { + return neo4jgraphql(object, params, context, resolveInfo, false) } } } diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index e17f94ec9..2fcd7245f 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -19,8 +19,8 @@ afterEach(async () => { describe('CreateComment', () => { const mutation = ` - mutation($id: ID!, $postId: ID!, $content: String!) { - CreateComment(id: $id, postId: $postId, content: $content) { + mutation($id: ID!, $content: String!) { + CreateComment(id: $id, content: $content) { id content } @@ -29,8 +29,7 @@ describe('CreateComment', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { variables = { - id: 'c1', - postId: 'p1', + id: 'c1', content: "I'm not authorised to comment" } client = new GraphQLClient(host) @@ -46,9 +45,8 @@ describe('CreateComment', () => { }) it('creates a post', async () => { - variables = { - id: 'c1', - postId: 'p1', + variables = { + id: 'c1', content: "I'm authorised to comment" } const expected = { diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 6c35e082d..94e28d0d7 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -27,7 +27,6 @@ type Mutation { enable(id: ID!): ID reward(fromBadgeId: ID!, toUserId: ID!): ID unreward(fromBadgeId: ID!, toUserId: ID!): ID - CreateComment(id: ID!, postId: ID!, content: String!) : Comment "Shout the given Type and ID" shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """ MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId}) diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index 29d0722b7..b6ada7ac9 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -4,7 +4,6 @@ import uuid from 'uuid/v4' export default function (params) { const { id = uuid(), - postId = uuid(), content = [ faker.lorem.sentence(), faker.lorem.sentence() @@ -15,7 +14,6 @@ export default function (params) { mutation { CreateComment( id: "${id}", - postId: "${postId}", content: "${content}" ) { id, content } } diff --git a/webapp/graphql/AddPostComments.js b/webapp/graphql/AddPostComments.js new file mode 100644 index 000000000..ab9ef8ca5 --- /dev/null +++ b/webapp/graphql/AddPostComments.js @@ -0,0 +1,18 @@ +import gql from 'graphql-tag' + +export default app => { + return { + AddPostComments: gql(` + mutation($from: _CommentInput!, $to: _PostInput!) { + AddPostComments(from: $from, to: $to) { + from { + id + } + to { + id + } + } + } + `) + } +} diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 8af07bc26..ef1121031 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -117,11 +117,11 @@ v-model="value" /> + - + {{ $t('actions.cancel') }} @@ -140,23 +140,23 @@ - -
- -
- + +
+ +
+ @@ -198,7 +198,9 @@ export default { return { post: null, ready: false, - title: 'loading' + title: 'loading', + loading: false, + disabled: false } }, watch: { @@ -315,18 +317,48 @@ export default { return this.$store.getters['auth/user'].id === id }, handleSubmit() { - this.$apollo.mutate({ - mutation: gql` - mutation($content: String!) { - CreateComment(content: $content) { - content + this.loading = true + this.$apollo + .mutate({ + mutation: gql` + mutation($content: String!) { + CreateComment(content: $content) { + id + content + } } + `, + variables: { + content: this.value } - `, - variables: { - content: this.value - } - }) + }) + .then(res => { + this.disabled = true + this.loading = false + const { id } = res.data.CreateComment + const commentId = { id: id } + const postId = { id: this.post.id } + const AddPostComments = require('~/graphql/AddPostComments.js').default( + this + ) + + this.$apollo + .mutate({ + mutation: AddPostComments.AddPostComments, + variables: { + from: commentId, + to: postId + } + }) + .then(res => { + this.$toast.success('Saved!') + }) + }) + .catch(err => { + this.$toast.error(err.message) + this.loading = false + this.disabled = false + }) } } } From c519bff74340e3381f017dfc20ff638e5e3aabe5 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 16 Apr 2019 23:37:11 -0300 Subject: [PATCH 007/124] Fix lint --- backend/src/resolvers/comments.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index 2fcd7245f..a70dfd974 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -28,9 +28,9 @@ describe('CreateComment', () => { ` describe('unauthenticated', () => { it('throws authorization error', async () => { - variables = { + variables = { id: 'c1', - content: "I'm not authorised to comment" + content: 'I\'m not authorised to comment' } client = new GraphQLClient(host) await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') @@ -47,16 +47,16 @@ describe('CreateComment', () => { it('creates a post', async () => { variables = { id: 'c1', - content: "I'm authorised to comment" + content: 'I\'m authorised to comment' } const expected = { CreateComment: { id: 'c1', - content: "I'm authorised to comment" + content: 'I\'m authorised to comment' } } - + await expect(client.request(mutation, variables)).resolves.toMatchObject(expected) }) }) -}) \ No newline at end of file +}) From f46e5ee58c69095a8421ac3e8eb803715b6cd58d Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 17 Apr 2019 10:18:39 -0300 Subject: [PATCH 008/124] Use custom resolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add postId to type Comment - remove it from params to create node without postId - fix tests Co-authored-by: Robert Schäfer Co-Authored-By: Tirokk Co-Authored-By: Mike Aono --- backend/src/resolvers/comments.js | 23 ++++++++++++++++++----- backend/src/resolvers/comments.spec.js | 9 ++++----- backend/src/schema.graphql | 1 + 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 4542c075a..5709b52e1 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -2,11 +2,24 @@ import { neo4jgraphql } from 'neo4j-graphql-js' export default { Mutation: { - CreateComment: (object, params, context, resolveInfo) => { - return neo4jgraphql(object, params, context, resolveInfo, false) - }, - AddPostComments: (object, params, context, resolveInfo) => { - return neo4jgraphql(object, params, context, resolveInfo, false) + CreateComment: async (object, params, context, resolveInfo) => { + const { postId } = params + delete params.postId + const comment = await neo4jgraphql(object, params, context, resolveInfo, true) + + const session = context.driver.session() + + const transactionRes = await session.run(` + MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}) + MERGE (post)<-[:COMMENTS]-(comment) + RETURN comment {.id, .content}`, { + postId, + commentId: comment.id + } + ) + session.close() + + return comment } } } diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index a70dfd974..9918038a7 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -19,8 +19,8 @@ afterEach(async () => { describe('CreateComment', () => { const mutation = ` - mutation($id: ID!, $content: String!) { - CreateComment(id: $id, content: $content) { + mutation($postId: ID, $content: String!) { + CreateComment(postId: $postId, content: $content) { id content } @@ -29,7 +29,7 @@ describe('CreateComment', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { variables = { - id: 'c1', + postId: 'p1', content: 'I\'m not authorised to comment' } client = new GraphQLClient(host) @@ -46,12 +46,11 @@ describe('CreateComment', () => { it('creates a post', async () => { variables = { - id: 'c1', + postId: 'p1', content: 'I\'m authorised to comment' } const expected = { CreateComment: { - id: 'c1', content: 'I\'m authorised to comment' } } diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index ff8b04dfc..99bcd4533 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -210,6 +210,7 @@ type Post { type Comment { id: ID! activityId: String + postId: ID author: User @relation(name: "WROTE", direction: "IN") content: String! contentExcerpt: String From 4dc39a97a87d09bc3f3a26f5d464f692bb82e172 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 17 Apr 2019 12:51:30 -0300 Subject: [PATCH 009/124] Fix seeds --- backend/src/seed/factories/comments.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index b6ada7ac9..2a131d0d9 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -3,7 +3,7 @@ import uuid from 'uuid/v4' export default function (params) { const { - id = uuid(), + postId = 'p6', content = [ faker.lorem.sentence(), faker.lorem.sentence() @@ -13,7 +13,7 @@ export default function (params) { return ` mutation { CreateComment( - id: "${id}", + postId: "${postId}", content: "${content}" ) { id, content } } From 86d075292c332e383d171236ebe3050746eab625 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 17 Apr 2019 12:51:50 -0300 Subject: [PATCH 010/124] Update frontend - to work with new backend implementation --- webapp/graphql/AddPostComments.js | 18 ------------------ webapp/pages/post/_id/_slug/index.vue | 24 ++++-------------------- 2 files changed, 4 insertions(+), 38 deletions(-) delete mode 100644 webapp/graphql/AddPostComments.js diff --git a/webapp/graphql/AddPostComments.js b/webapp/graphql/AddPostComments.js deleted file mode 100644 index ab9ef8ca5..000000000 --- a/webapp/graphql/AddPostComments.js +++ /dev/null @@ -1,18 +0,0 @@ -import gql from 'graphql-tag' - -export default app => { - return { - AddPostComments: gql(` - mutation($from: _CommentInput!, $to: _PostInput!) { - AddPostComments(from: $from, to: $to) { - from { - id - } - to { - id - } - } - } - `) - } -} diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index ef1121031..95295d6bf 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -321,38 +321,22 @@ export default { this.$apollo .mutate({ mutation: gql` - mutation($content: String!) { - CreateComment(content: $content) { + mutation($postId: ID, $content: String!) { + CreateComment(postId: $postId, content: $content) { id content } } `, variables: { + postId: this.post.id, content: this.value } }) .then(res => { this.disabled = true this.loading = false - const { id } = res.data.CreateComment - const commentId = { id: id } - const postId = { id: this.post.id } - const AddPostComments = require('~/graphql/AddPostComments.js').default( - this - ) - - this.$apollo - .mutate({ - mutation: AddPostComments.AddPostComments, - variables: { - from: commentId, - to: postId - } - }) - .then(res => { - this.$toast.success('Saved!') - }) + this.$toast.success('Saved!') }) .catch(err => { this.$toast.error(err.message) From f4744fa513e1137418bf3190903a6822e988c547 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 17 Apr 2019 18:37:44 -0300 Subject: [PATCH 011/124] Remove unused import, variables, set debug to false --- backend/src/resolvers/comments.js | 16 ++++++++-------- backend/src/seed/factories/comments.js | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 5709b52e1..5446278d1 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -5,20 +5,20 @@ export default { CreateComment: async (object, params, context, resolveInfo) => { const { postId } = params delete params.postId - const comment = await neo4jgraphql(object, params, context, resolveInfo, true) - + const comment = await neo4jgraphql(object, params, context, resolveInfo, false) + const session = context.driver.session() - const transactionRes = await session.run(` + await session.run(` MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}) MERGE (post)<-[:COMMENTS]-(comment) RETURN comment {.id, .content}`, { - postId, - commentId: comment.id - } + postId, + commentId: comment.id + } ) - session.close() - + session.close() + return comment } } diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index 2a131d0d9..3c042f0b2 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -1,5 +1,4 @@ import faker from 'faker' -import uuid from 'uuid/v4' export default function (params) { const { From 0149f30f5fa466291fba95e3059919732e6ebbff Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 17 Apr 2019 20:27:35 -0300 Subject: [PATCH 012/124] Fix failing tests, refactor - due to changes made to comments factories, which caused failures - to use new custom CreateComment resolver syntax to relate a comment to post by passing in the postId --- .../middleware/softDeleteMiddleware.spec.js | 10 ++--- backend/src/resolvers/moderation.spec.js | 9 ++--- backend/src/seed/factories/comments.js | 3 ++ backend/src/seed/seed-db.js | 38 +++++++------------ 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/backend/src/middleware/softDeleteMiddleware.spec.js b/backend/src/middleware/softDeleteMiddleware.spec.js index 46005a4ff..f007888ed 100644 --- a/backend/src/middleware/softDeleteMiddleware.spec.js +++ b/backend/src/middleware/softDeleteMiddleware.spec.js @@ -23,21 +23,19 @@ beforeAll(async () => { ]) await Promise.all([ - factory.create('Comment', { id: 'c2', content: 'Enabled comment on public post' }) + factory.create('Comment', { id: 'c2', postId: 'p3', content: 'Enabled comment on public post' }) ]) await Promise.all([ - factory.relate('Comment', 'Author', { from: 'u1', to: 'c2' }), - factory.relate('Comment', 'Post', { from: 'c2', to: 'p3' }) + factory.relate('Comment', 'Author', { from: 'u1', to: 'c2' }) ]) const asTroll = Factory() await asTroll.authenticateAs({ email: 'troll@example.org', password: '1234' }) await asTroll.create('Post', { id: 'p2', title: 'Disabled post', content: 'This is an offensive post content', image: '/some/offensive/image.jpg', deleted: false }) - await asTroll.create('Comment', { id: 'c1', content: 'Disabled comment' }) + await asTroll.create('Comment', { id: 'c1', postId: 'p3', content: 'Disabled comment' }) await Promise.all([ - asTroll.relate('Comment', 'Author', { from: 'u2', to: 'c1' }), - asTroll.relate('Comment', 'Post', { from: 'c1', to: 'p3' }) + asTroll.relate('Comment', 'Author', { from: 'u2', to: 'c1' }) ]) const asModerator = Factory() diff --git a/backend/src/resolvers/moderation.spec.js b/backend/src/resolvers/moderation.spec.js index dfbcac80f..f8aa6e10b 100644 --- a/backend/src/resolvers/moderation.spec.js +++ b/backend/src/resolvers/moderation.spec.js @@ -109,11 +109,11 @@ describe('disable', () => { await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' }) await Promise.all([ factory.create('Post', { id: 'p3' }), - factory.create('Comment', { id: 'c47' }) + factory.create('Comment', { id: 'c47', postId: 'p3', content: 'this comment was created for this post' }) ]) + await Promise.all([ - factory.relate('Comment', 'Author', { from: 'u45', to: 'c47' }), - factory.relate('Comment', 'Post', { from: 'c47', to: 'p3' }) + factory.relate('Comment', 'Author', { from: 'u45', to: 'c47' }) ]) } }) @@ -286,8 +286,7 @@ describe('enable', () => { factory.create('Comment', { id: 'c456' }) ]) await Promise.all([ - factory.relate('Comment', 'Author', { from: 'u123', to: 'c456' }), - factory.relate('Comment', 'Post', { from: 'c456', to: 'p9' }) + factory.relate('Comment', 'Author', { from: 'u123', to: 'c456' }) ]) const disableMutation = ` diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index 3c042f0b2..66f2338b5 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -1,7 +1,9 @@ import faker from 'faker' +import uuid from 'uuid/v4' export default function (params) { const { + id = uuid(), postId = 'p6', content = [ faker.lorem.sentence(), @@ -12,6 +14,7 @@ export default function (params) { return ` mutation { CreateComment( + id: "${id}" postId: "${postId}", content: "${content}" ) { id, content } diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index 3de70e643..ec0184b9c 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -185,45 +185,33 @@ import Factory from './factories' ]) await Promise.all([ - 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' }), - f.create('Comment', { id: 'c8' }), - f.create('Comment', { id: 'c9' }), - f.create('Comment', { id: 'c10' }), - f.create('Comment', { id: 'c11' }), - f.create('Comment', { id: 'c12' }) + f.create('Comment', { id: 'c1', postId: 'p1' }), + f.create('Comment', { id: 'c2', postId: 'p1' }), + f.create('Comment', { id: 'c3', postId: 'p3' }), + f.create('Comment', { id: 'c4', postId: 'p2' }), + f.create('Comment', { id: 'c5', postId: 'p3' }), + f.create('Comment', { id: 'c6', postId: 'p4' }), + f.create('Comment', { id: 'c7', postId: 'p2' }), + f.create('Comment', { id: 'c8', postId: 'p15' }), + f.create('Comment', { id: 'c9', postId: 'p15' }), + f.create('Comment', { id: 'c10', postId: 'p15' }), + f.create('Comment', { id: 'c11', postId: 'p15' }), + f.create('Comment', { id: 'c12', postId: 'p15' }) ]) await Promise.all([ 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' }), f.relate('Comment', 'Author', { from: 'u5', to: 'c8' }), - f.relate('Comment', 'Post', { from: 'c8', to: 'p15' }), f.relate('Comment', 'Author', { from: 'u6', to: 'c9' }), - f.relate('Comment', 'Post', { from: 'c9', to: 'p15' }), f.relate('Comment', 'Author', { from: 'u7', to: 'c10' }), - f.relate('Comment', 'Post', { from: 'c10', to: 'p15' }), f.relate('Comment', 'Author', { from: 'u5', to: 'c11' }), - f.relate('Comment', 'Post', { from: 'c11', to: 'p15' }), - f.relate('Comment', 'Author', { from: 'u6', to: 'c12' }), - f.relate('Comment', 'Post', { from: 'c12', to: 'p15' }) + f.relate('Comment', 'Author', { from: 'u6', to: 'c12' }) ]) const disableMutation = 'mutation($id: ID!) { disable(id: $id) }' From 4f7d5ee24fb49b285d5b0813d8b4cd7d97b34a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 18 Apr 2019 19:05:36 +0200 Subject: [PATCH 013/124] Fix? flaky cypress by waiting for content Content menu is a client-side rendered component. Maybe an extra assertion will wait for it? That's how you can flix flaky cucumbers if you use capybara. --- cypress/integration/common/report.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js index 78abf5378..a6c13c3e6 100644 --- a/cypress/integration/common/report.js +++ b/cypress/integration/common/report.js @@ -52,10 +52,11 @@ When('I click on "Report Post" from the content menu of the post', () => { When( 'I click on "Report User" from the content menu in the user info box', () => { - cy.contains('.ds-card', davidIrvingName) - .find('.content-menu-trigger') - .first() - .click({force: true}) + // wait client-side-rendered content + cy.get('.ds-card-content .content-menu button').should('exist') + + cy.contains('.ds-card-content', davidIrvingName) + .find('.content-menu button').click() cy.get('.popover .ds-menu-item-link') .contains('Report User') From 3d2a982d3fe47e26e2a2e9706d6b3be9a2e14446 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 18 Apr 2019 22:14:23 -0300 Subject: [PATCH 014/124] Attempt to updated comments - by pushing to the post.comments array, which updates the array, but doesn't rerender the comments component - by updating the apollo store as seen here https://akryum.github.io/vue-apollo/guide/apollo/mutations.html#server-side-example and https://github.com/Akryum/vue-apollo-todos --- backend/src/seed/factories/comments.js | 2 +- webapp/graphql/PostQuery.gql | 66 ++++++++++++++++++++++++++ webapp/pages/post/_id/_slug/index.vue | 39 +++++++++++++-- 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 webapp/graphql/PostQuery.gql diff --git a/backend/src/seed/factories/comments.js b/backend/src/seed/factories/comments.js index b30cede36..ba3a85840 100644 --- a/backend/src/seed/factories/comments.js +++ b/backend/src/seed/factories/comments.js @@ -19,6 +19,6 @@ export default function (params) { } } `, - variables: { id, content } + variables: { id, postId, content } } } diff --git a/webapp/graphql/PostQuery.gql b/webapp/graphql/PostQuery.gql new file mode 100644 index 000000000..87021b7ee --- /dev/null +++ b/webapp/graphql/PostQuery.gql @@ -0,0 +1,66 @@ +query Post($slug: String!) { + Post(slug: $slug) { + id + title + content + createdAt + disabled + deleted + slug + image + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + badges { + id + key + icon + } + } + tags { + name + } + commentsCount + comments(orderBy: createdAt_desc) { + id + contentExcerpt + createdAt + disabled + deleted + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + badges { + id + key + icon + } + } + } + categories { + id + name + icon + } + shoutedCount + shoutedByCurrentUser + } +} diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 95295d6bf..fc0056e0b 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -131,7 +131,7 @@ @@ -172,7 +172,8 @@ import HcUser from '~/components/User' import HcShoutButton from '~/components/ShoutButton.vue' import HcEmpty from '~/components/Empty.vue' import Comment from '~/components/Comment.vue' -import HcEditor from '~/components/Editor/Editor.vue' +import HcEditor from '~/components/Editor' +import POST_INFO from '~/graphql/PostQuery.gql' export default { transition: { @@ -316,7 +317,12 @@ export default { isAuthor(id) { return this.$store.getters['auth/user'].id === id }, + addComment(comment) { + this.post.comments.push(comment) + }, handleSubmit() { + const value = this.value + this.value = '' this.loading = true this.$apollo .mutate({ @@ -330,10 +336,33 @@ export default { `, variables: { postId: this.post.id, - content: this.value - } - }) + content: value + }, + update: (store, { data: { CreateComment } }) => { + const data = store.readQuery({ query: POST_INFO }) + // data.Post.push(CreateComment) + // store.writeQuery({ query: POST_INFO, data }) + // // Add to Todo tasks list + // const todoQuery = { + // query: POST_INFO, + // variables: { filter: { done: false } }, + // } + // const todoData = store.readQuery(todoQuery) + // todoData.allTasks.push(createTask) + // store.writeQuery({ ...todoQuery, data: todoData }) + }, + optimisticResponse: { + __typename: 'Mutation', + CreateComment: { + __typename: 'Comment', + id: null, + content: value + }, + }, + }) .then(res => { + console.log(res.data.CreateComment) + this.addComment(res.data.CreateComment) this.disabled = true this.loading = false this.$toast.success('Saved!') From d1bc3847afec584343592c1be46968f69e2152a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 19 Apr 2019 04:47:45 +0000 Subject: [PATCH 015/124] Bump graphql-shield from 5.3.3 to 5.3.4 in /backend Bumps [graphql-shield](https://github.com/maticzav/graphql-shield) from 5.3.3 to 5.3.4. - [Release notes](https://github.com/maticzav/graphql-shield/releases) - [Commits](https://github.com/maticzav/graphql-shield/compare/v5.3.3...v5.3.4) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/package.json b/backend/package.json index 1aff25145..942cd8023 100644 --- a/backend/package.json +++ b/backend/package.json @@ -50,7 +50,7 @@ "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", "graphql-middleware": "~3.0.2", - "graphql-shield": "~5.3.3", + "graphql-shield": "~5.3.4", "graphql-tag": "~2.10.1", "graphql-yoga": "~1.17.4", "helmet": "~3.16.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index ec11aad7e..980111644 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -3752,13 +3752,13 @@ graphql-request@~1.8.2: dependencies: cross-fetch "2.2.2" -graphql-shield@~5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.3.tgz#e3fbdb2a5f927fe1bb660ccf60c614defcc3aed4" - integrity sha512-9Hdmp71ewi9w7Tj1x8CSl3arWvtQOYKpZrsSBid2Vpr6BISAKe/2edEfgP4xYIKAkmpclG0Gl7ID5+qt1RJu7A== +graphql-shield@~5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.4.tgz#bd126d7d39adc6ae5b91d93ab5f65ae25f93ce80" + integrity sha512-YasNfKk7d0hiSU9eh0zvJmRmUMDLZrfVTwSke/4y46cBRXFiI9fv6OA12Ux+1DB4TyDAjGGnqx8d92ptL7ZN3w== dependencies: "@types/yup" "0.26.12" - lightercollective "^0.2.0" + lightercollective "^0.3.0" object-hash "^1.3.1" yup "^0.27.0" @@ -5184,10 +5184,10 @@ libphonenumber-js@^1.6.4: semver-compare "^1.0.0" xml2js "^0.4.17" -lightercollective@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.2.0.tgz#4f10cd53ec50405d7da03ee81233067993ca8e67" - integrity sha512-zgFCDiUQQOjislj+1tX7zDxZbgVB6Qi9BSmos41oZcxHdkTveVDzHW0Y3TisNZCWuBN1h0e0xrjkNoLtPkLsUg== +lightercollective@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.3.0.tgz#1f07638642ec645d70bdb69ab2777676f35a28f0" + integrity sha512-RFOLSUVvwdK3xA0P8o6G7QGXLIyy1L2qv5caEI7zXN5ciaEjbAriRF182kbsoJ1S1TgvpyGcN485fMky6qxOPw== linkifyjs@~2.1.8: version "2.1.8" From 9ff194f3d67f35b19cff69e0c499128b6270bad7 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 19 Apr 2019 09:46:46 -0300 Subject: [PATCH 016/124] Refetch the post to update the comments - I'm not happy with this as it is expensive, but seems to be used in other places in the code base, where I found the idea, @appinteractive left a comment suggesting a better approach would be to use subscriptions, but I haven't had time to research/try this option Co-authored-by: Mike Aono Co-authored-by: Joseph Ngugi --- webapp/pages/post/_id/_slug/index.vue | 42 +++++++++++---------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index fc0056e0b..1803541d4 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -318,7 +318,8 @@ export default { return this.$store.getters['auth/user'].id === id }, addComment(comment) { - this.post.comments.push(comment) + this.$apollo.queries.Post.refetch() + // this.post = { ...this.post, comments: [...this.post.comments, comment] } }, handleSubmit() { const value = this.value @@ -337,31 +338,9 @@ export default { variables: { postId: this.post.id, content: value - }, - update: (store, { data: { CreateComment } }) => { - const data = store.readQuery({ query: POST_INFO }) - // data.Post.push(CreateComment) - // store.writeQuery({ query: POST_INFO, data }) - // // Add to Todo tasks list - // const todoQuery = { - // query: POST_INFO, - // variables: { filter: { done: false } }, - // } - // const todoData = store.readQuery(todoQuery) - // todoData.allTasks.push(createTask) - // store.writeQuery({ ...todoQuery, data: todoData }) - }, - optimisticResponse: { - __typename: 'Mutation', - CreateComment: { - __typename: 'Comment', - id: null, - content: value - }, - }, - }) + } + }) .then(res => { - console.log(res.data.CreateComment) this.addComment(res.data.CreateComment) this.disabled = true this.loading = false @@ -373,6 +352,19 @@ export default { this.disabled = false }) } + }, + apollo: { + Post: { + query() { + return require('~/graphql/PostQuery.gql') + }, + variables() { + return { + slug: this.$route.params.slug + } + }, + fetchPolicy: 'cache-and-network' + } } } From 4d8148297e125abe01259a0603049c79755b65bf Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 19 Apr 2019 12:53:59 -0300 Subject: [PATCH 017/124] Fix styling of hc-editor, localize toast message - validations are not working for hc-editor, neither in this PR nor in ContributionForm --- webapp/locales/en.json | 3 +- webapp/pages/post/_id/_slug/index.vue | 90 +++++++++++++++++---------- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/webapp/locales/en.json b/webapp/locales/en.json index af4e50c11..c74cbed52 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -112,7 +112,8 @@ "takeAction": { "name": "Take action" }, - "submitComment": "Submit Comment" + "submitComment": "Submit Comment", + "commentSubmitted": "Comment Submitted" }, "quotes": { "african": { diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 1803541d4..60ed15c20 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -112,33 +112,46 @@ - - - - - - - - {{ $t('actions.cancel') }} - - - - - {{ $t('post.submitComment') }} - - - + + + @@ -201,7 +214,13 @@ export default { ready: false, title: 'loading', loading: false, - disabled: false + disabled: false, + form: { + content: '' + }, + formSchema: { + content: { required: true, min: 3 } + } } }, watch: { @@ -317,14 +336,17 @@ export default { isAuthor(id) { return this.$store.getters['auth/user'].id === id }, + updateEditorContent(value) { + // this.form.content = value + this.$refs.contributionForm.update('content', value) + }, addComment(comment) { this.$apollo.queries.Post.refetch() // this.post = { ...this.post, comments: [...this.post.comments, comment] } }, handleSubmit() { - const value = this.value - this.value = '' - this.loading = true + const content = this.form.content + this.form.content = '' this.$apollo .mutate({ mutation: gql` @@ -337,14 +359,14 @@ export default { `, variables: { postId: this.post.id, - content: value + content: content } }) .then(res => { this.addComment(res.data.CreateComment) - this.disabled = true this.loading = false - this.$toast.success('Saved!') + this.disabled = false + this.$toast.success(this.$t('post.commentSubmitted')) }) .catch(err => { this.$toast.error(err.message) From 980fab01b3bef986325d77b7830586971f399c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sat, 20 Apr 2019 13:55:32 +0200 Subject: [PATCH 018/124] Add documentation for neo4j backups in kubernetes --- SUMMARY.md | 5 +-- deployment/README.md | 2 +- deployment/backup.md | 74 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 deployment/backup.md diff --git a/SUMMARY.md b/SUMMARY.md index c993fe120..e1cf09126 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -5,7 +5,6 @@ * [Installation](installation.md) * [Backend](backend/README.md) * [GraphQL](backend/graphql.md) - * [Legacy Migration](backend/db-migration-worker/README.md) * [Webapp](webapp/README.md) * [COMPONENTS](webapp/components.md) * [PLUGINS](webapp/plugins.md) @@ -21,7 +20,9 @@ * [Frontend tests](webapp/testing.md) * [Backend tests](backend/testing.md) * [Contributing](CONTRIBUTING.md) -* [Deployment](deployment/README.md) +* [Kubernetes Deployment](deployment/README.md) + * [Neo4J DB Backup](deployment/backup.md) +* [Maintenance](maintenance/README.md) * [Feature Specification](cypress/features.md) * [Code of conduct](CODE_OF_CONDUCT.md) * [License](LICENSE.md) diff --git a/deployment/README.md b/deployment/README.md index 982a886ac..84912d2a5 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -181,7 +181,7 @@ This setup is completely optional and only required if you have data on a server Create a configmap with the specific connection data of your legacy server: ```bash -$ kubectl create configmap db-migration-worker \ +$ kubectl create configmap maintenance-worker \ --namespace=human-connection \ --from-literal=SSH_USERNAME=someuser \ --from-literal=SSH_HOST=yourhost \ diff --git a/deployment/backup.md b/deployment/backup.md new file mode 100644 index 000000000..556d257db --- /dev/null +++ b/deployment/backup.md @@ -0,0 +1,74 @@ +# Backup (offline) + +This tutorial explains how to carry out an offline backup of your Neo4J +database in a kubernetes cluster. + +An offline backup requires the Neo4J database to be stopped. Read +[the docs](https://neo4j.com/docs/operations-manual/current/tools/dump-load/). +Neo4J also offers online backups but this is available in enterprise edition +only. + +The tricky part is to stop the Neo4J database *without* stopping the container. +Neo4J's docker container image starts `neo4j` by default, so we have to override +this command with sth. that keeps the container spinning but does not terminate +it. + +## Stop and Restart Neo4J Database in Kubernetes + +[This tutorial](http://bigdatums.net/2017/11/07/how-to-keep-docker-containers-running/) +explains how to keep a docker container running. For kubernetes, the way how to +override the docker image `CMD` is explained [here](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#define-a-command-and-arguments-when-you-create-a-pod). + +So, all we have to do is to edit the kubernetes deployment of our Neo4J database +and set a custom `command` every time we have to carry out tasks like backup, +restore, seed etc. + +{% hint style="info" %} TODO: implement maintenance mode {% endhint %} +First bring the application into maintenance mode to ensure there are no +database connections left and nobody can access the application. + +Run the following: + +```sh +kubectl --namespace=human-connection edit deployment nitro-neo4j +``` + +Add the following to `spec.template.spec.containers`: +``` +["tail", "-f", "/dev/null"] +``` +and write the file which will update the deployment. + +Then perform your tasks! + +When you're done, edit the deployment again and remove the `command`. Write the +file and trigger an update of the deployment. + +## Create a Backup in Kubernetes + +First stop your Neo4J database, see above. Then: +```sh +kubectl --namespace=human-connection get pods +# copy the ID of the pod running Neo4J +kubectl --namespace=human-connection exec -it bash +# once you're in the pod +neo4j-admin dump --to=/root/neo4j-backup +exit +# download the file + kubectl cp human-connection/:/root/neo4j-backup ./neo4j-backup +``` +Restart your Neo4J database. + +## Restore a Backup in Kubernetes + +First stop your Neo4J database. Then: +```sh +kubectl --namespace=human-connection get pods +# copy the ID of the pod running Neo4J +kubectl cp ./neo4j-backup human-connection/:/root/ +kubectl --namespace=human-connection exec -it bash +# once you're in the pod +neo4j-admin load --from=/root/neo4j-backup --force +exit +``` +Restart your Neo4J database. From 49c03c92c8695205cb76e14fc6086eb40a01833b Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Sat, 20 Apr 2019 10:49:02 -0300 Subject: [PATCH 019/124] Extract PostQuery/convert to js, try to get clear working Co-authored-by: Mike Aono --- webapp/graphql/PostQuery.gql | 66 --------------- webapp/graphql/PostQuery.js | 79 ++++++++++++++++++ webapp/pages/post/_id/_slug/index.vue | 113 +++----------------------- 3 files changed, 89 insertions(+), 169 deletions(-) delete mode 100644 webapp/graphql/PostQuery.gql create mode 100644 webapp/graphql/PostQuery.js diff --git a/webapp/graphql/PostQuery.gql b/webapp/graphql/PostQuery.gql deleted file mode 100644 index 87021b7ee..000000000 --- a/webapp/graphql/PostQuery.gql +++ /dev/null @@ -1,66 +0,0 @@ -query Post($slug: String!) { - Post(slug: $slug) { - id - title - content - createdAt - disabled - deleted - slug - image - author { - id - slug - name - avatar - disabled - deleted - shoutedCount - contributionsCount - commentsCount - followedByCount - followedByCurrentUser - badges { - id - key - icon - } - } - tags { - name - } - commentsCount - comments(orderBy: createdAt_desc) { - id - contentExcerpt - createdAt - disabled - deleted - author { - id - slug - name - avatar - disabled - deleted - shoutedCount - contributionsCount - commentsCount - followedByCount - followedByCurrentUser - badges { - id - key - icon - } - } - } - categories { - id - name - icon - } - shoutedCount - shoutedByCurrentUser - } -} diff --git a/webapp/graphql/PostQuery.js b/webapp/graphql/PostQuery.js new file mode 100644 index 000000000..d45624616 --- /dev/null +++ b/webapp/graphql/PostQuery.js @@ -0,0 +1,79 @@ +import gql from 'graphql-tag' + +export default app => { + const lang = app.$i18n.locale().toUpperCase() + return gql(` + query Post($slug: String!) { + Post(slug: $slug) { + id + title + content + createdAt + disabled + deleted + slug + image + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + location { + name: name${lang} + } + badges { + id + key + icon + } + } + tags { + name + } + commentsCount + comments(orderBy: createdAt_desc) { + id + contentExcerpt + createdAt + disabled + deleted + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + location { + name: name${lang} + } + badges { + id + key + icon + } + } + } + categories { + id + name + icon + } + shoutedCount + shoutedByCurrentUser + } + } + `) +} diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 60ed15c20..58f254273 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -113,7 +113,7 @@ - @@ -133,7 +134,7 @@ {{ $t('actions.cancel') }} @@ -186,7 +187,6 @@ import HcShoutButton from '~/components/ShoutButton.vue' import HcEmpty from '~/components/Empty.vue' import Comment from '~/components/Comment.vue' import HcEditor from '~/components/Editor' -import POST_INFO from '~/graphql/PostQuery.gql' export default { transition: { @@ -229,102 +229,6 @@ export default { this.title = this.post.title } }, - async asyncData(context) { - const { - params, - error, - app: { apolloProvider, $i18n } - } = context - const client = apolloProvider.defaultClient - const query = gql(` - query Post($slug: String!) { - Post(slug: $slug) { - id - title - content - createdAt - disabled - deleted - slug - image - author { - id - slug - name - avatar - disabled - deleted - shoutedCount - contributionsCount - commentsCount - followedByCount - followedByCurrentUser - location { - name: name${$i18n.locale().toUpperCase()} - } - badges { - id - key - icon - } - } - tags { - name - } - commentsCount - comments(orderBy: createdAt_desc) { - id - contentExcerpt - createdAt - disabled - deleted - author { - id - slug - name - avatar - disabled - deleted - shoutedCount - contributionsCount - commentsCount - followedByCount - followedByCurrentUser - location { - name: name${$i18n.locale().toUpperCase()} - } - badges { - id - key - icon - } - } - } - categories { - id - name - icon - } - shoutedCount - shoutedByCurrentUser - } - } - `) - const variables = { slug: params.slug } - const { - data: { Post } - } = await client.query({ query, variables }) - if (Post.length <= 0) { - // TODO: custom 404 error page with translations - const message = 'This post could not be found' - return error({ statusCode: 404, message }) - } - const [post] = Post - return { - post, - title: post.title - } - }, mounted() { setTimeout(() => { // NOTE: quick fix for jumping flexbox implementation @@ -337,8 +241,11 @@ export default { return this.$store.getters['auth/user'].id === id }, updateEditorContent(value) { - // this.form.content = value - this.$refs.contributionForm.update('content', value) + this.$refs.commentForm.update('content', value) + }, + clearEditor() { + this.$emit('clear') + this.$refs.commentForm.update('content', '') }, addComment(comment) { this.$apollo.queries.Post.refetch() @@ -378,7 +285,7 @@ export default { apollo: { Post: { query() { - return require('~/graphql/PostQuery.gql') + return require('~/graphql/PostQuery.js').default(this) }, variables() { return { From 52e82d277e3e943debb9e6f7dbcbdb3392faf479 Mon Sep 17 00:00:00 2001 From: aonomike Date: Sat, 20 Apr 2019 16:59:47 +0300 Subject: [PATCH 020/124] Add Post Comment feature file --- cypress/integration/post/Comment.feature | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 cypress/integration/post/Comment.feature diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature new file mode 100644 index 000000000..e82b7ee86 --- /dev/null +++ b/cypress/integration/post/Comment.feature @@ -0,0 +1,15 @@ +Feature: Post Comment + As a user + I want to comment on contributions of others + To be able to express my thoughts and emotions about these, discuss and add give further information. + + Background: + Given we have the following posts in our database: + | id | title | slug | + | bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | + And I have a user account + And I am logged in + + Scenario: + Given I get to the post page of "101-essays" + Then I should be able to post a comment to the post "101-essays" \ No newline at end of file From 54fe7e0029a5546e952e6f67688be644585750ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 22 Apr 2019 04:46:05 +0000 Subject: [PATCH 021/124] Bump tiptap from 1.15.0 to 1.16.2 in /webapp Bumps [tiptap](https://github.com/scrumpy/tiptap) from 1.15.0 to 1.16.2. - [Release notes](https://github.com/scrumpy/tiptap/releases) - [Commits](https://github.com/scrumpy/tiptap/compare/tiptap@1.15.0...tiptap@1.16.2) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 4e3305ca2..50b41b65b 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -51,7 +51,7 @@ "nuxt-env": "~0.1.0", "stack-utils": "^1.0.2", "string-hash": "^1.1.3", - "tiptap": "^1.14.0", + "tiptap": "^1.16.2", "tiptap-extensions": "^1.15.0", "v-tooltip": "~2.0.1", "vue-count-to": "~1.0.13", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index ecc1955e6..c5a985d2b 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -8881,10 +8881,10 @@ prosemirror-utils@^0.7.6: resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.7.6.tgz#c462ddfbf2452e56e4b25d1f02b34caccddb0f33" integrity sha512-vzsCBTiJ56R3nRDpIJnKOJzsZP7KFO8BkXk7zvQgQiXpml2o/djPCRhuyaFc7VTqSHlLPQHVI1feTLAwHp+prQ== -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.8.8: - version "1.8.8" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.8.8.tgz#e61f60ed716d4f943be2fbc3f3be765322ff891f" - integrity sha512-rBDmBKRPmWhF4R2k9vW7CkGo+bafjj278lFxEGVpCHlnLhhhYX1XHU59KgMCsDnNhxh8Oexup1yIPbfg99eynA== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.8.8, prosemirror-view@^1.8.9: + version "1.8.9" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.8.9.tgz#9303def925eba0a8ce4589e64b4a011eaccfc1e0" + integrity sha512-K3/z7qDR6rEMH/gKXqu5pmjmtyhtuTWeQyselK6HMp3jbn1UANU4CfdU/awQT+zbRjv4ZJXGb9lxnuNmdUQS3Q== dependencies: prosemirror-model "^1.1.0" prosemirror-state "^1.0.0" @@ -10362,10 +10362,10 @@ tippy.js@^4.2.1: dependencies: popper.js "^1.14.7" -tiptap-commands@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.7.0.tgz#d15cec2cb09264b5c1f6f712dab8819bb9ab7e13" - integrity sha512-JhgvBPIhGnisEdxD6gmM3U76BUlKF9n1LW1X/dO1AUOsm3Xc9tQB5BIOV/DpZTvrjntLP3AUTfd+yJeRIz5CPA== +tiptap-commands@^1.7.0, tiptap-commands@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.7.1.tgz#88f615e623836c49f3c4ad1b1cdd95ebed7cf0e1" + integrity sha512-HQVFgfBeeOe9mJFYUDkYxiX1D+aYjyO9rO0SeYdumqv9rNHTF3GFfx7qJSk4/d2+GgXDRyoiO35B25EkxGjJJw== dependencies: prosemirror-commands "^1.0.7" prosemirror-inputrules "^1.0.1" @@ -10397,10 +10397,10 @@ tiptap-utils@^1.3.0: prosemirror-tables "^0.7.9" prosemirror-utils "^0.7.6" -tiptap@^1.14.0, tiptap@^1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.15.0.tgz#198b6a3b477a10a25de79674a4d8bb58dad56743" - integrity sha512-qQfcK9Vs0QzUgw1x9oKYXimX8+m1TckivTrD/0als095qrq+fFQpQWkce++8kBW+2lGkM6nXsogZvHoV6Dzl4Q== +tiptap@^1.15.0, tiptap@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.16.2.tgz#0a94ea8f58cccafd56439bcbd29fa9c6071ed90a" + integrity sha512-krMpbrQDvSwcp0FIUFjZgmlZUc65kvno+ASecp2G13DJUoAeYHTUW7AKng53xJP/rMm9ubc3q9XA6e6B5Bf+mA== dependencies: prosemirror-commands "^1.0.7" prosemirror-dropcursor "^1.1.1" @@ -10409,8 +10409,8 @@ tiptap@^1.14.0, tiptap@^1.15.0: prosemirror-keymap "^1.0.1" prosemirror-model "^1.7.0" prosemirror-state "^1.2.1" - prosemirror-view "^1.8.8" - tiptap-commands "^1.7.0" + prosemirror-view "^1.8.9" + tiptap-commands "^1.7.1" tiptap-utils "^1.3.0" tmp@^0.0.33: From c6b11319fef76e455c3ccce7c8c6263624d05e46 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Mon, 22 Apr 2019 11:51:29 -0300 Subject: [PATCH 022/124] Clear editor, write cypress test - the editor only clears once, also there are some other bugs associated with clearing it this way - according to https://github.com/scrumpy/tiptap/issues/21 there should be a clearContent(), but haven't been able to get it to work - cypress test for some reason is with a weird bug where I need to submit the form, then click on the submit button, otherwise it doesn't call the handleSubmit method --- cypress/integration/common/post.js | 18 ++++++++++++++++++ cypress/integration/post/Comment.feature | 7 ++++--- webapp/pages/post/_id/_slug/index.vue | 18 +++++++++--------- 3 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 cypress/integration/common/post.js diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js new file mode 100644 index 000000000..fc03b20b3 --- /dev/null +++ b/cypress/integration/common/post.js @@ -0,0 +1,18 @@ +import { When, Then } from 'cypress-cucumber-preprocessor/steps' + +When('I should be able to post a comment', () => { + cy.get('.ProseMirror') + .type('This is a comment') + .get('.ds-form') + .submit() + .get('button') + .contains('Submit Comment') + .click() + .get('.iziToast-message') + .contains('Comment Submitted') + }) + +Then('I should see my comment', () => { + cy.get('div.comment p') + .should('contain', 'This is a comment') +}) diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature index e82b7ee86..164c13bd6 100644 --- a/cypress/integration/post/Comment.feature +++ b/cypress/integration/post/Comment.feature @@ -1,7 +1,7 @@ Feature: Post Comment As a user I want to comment on contributions of others - To be able to express my thoughts and emotions about these, discuss and add give further information. + To be able to express my thoughts and emotions about these, discuss, and add give further information. Background: Given we have the following posts in our database: @@ -11,5 +11,6 @@ Feature: Post Comment And I am logged in Scenario: - Given I get to the post page of "101-essays" - Then I should be able to post a comment to the post "101-essays" \ No newline at end of file + Given I visit "post/bWBjpkTKZp/101-essays" + Then I should be able to post a comment + And I should see my comment \ No newline at end of file diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 58f254273..27b552608 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -97,7 +97,7 @@ - +

@@ -111,7 +111,7 @@

- + - + {{ $t('actions.cancel') }} @@ -244,8 +243,9 @@ export default { this.$refs.commentForm.update('content', value) }, clearEditor() { - this.$emit('clear') - this.$refs.commentForm.update('content', '') + this.loading = false + this.disabled = false + this.form.content = ' ' }, addComment(comment) { this.$apollo.queries.Post.refetch() @@ -253,7 +253,7 @@ export default { }, handleSubmit() { const content = this.form.content - this.form.content = '' + this.form.content = ' ' this.$apollo .mutate({ mutation: gql` @@ -266,7 +266,7 @@ export default { `, variables: { postId: this.post.id, - content: content + content } }) .then(res => { From 074155da7338296fe4e64e2e90546d0184c24108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 23 Apr 2019 15:08:08 +0000 Subject: [PATCH 023/124] Bump tiptap-extensions from 1.15.0 to 1.16.2 in /webapp Bumps [tiptap-extensions](https://github.com/scrumpy/tiptap) from 1.15.0 to 1.16.2. - [Release notes](https://github.com/scrumpy/tiptap/releases) - [Commits](https://github.com/scrumpy/tiptap/compare/tiptap-extensions@1.15.0...tiptap-extensions@1.16.2) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 50b41b65b..58adc74a2 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -52,7 +52,7 @@ "stack-utils": "^1.0.2", "string-hash": "^1.1.3", "tiptap": "^1.16.2", - "tiptap-extensions": "^1.15.0", + "tiptap-extensions": "^1.16.2", "v-tooltip": "~2.0.1", "vue-count-to": "~1.0.13", "vue-izitoast": "1.1.2", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index c5a985d2b..1059cfb53 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -8881,7 +8881,7 @@ prosemirror-utils@^0.7.6: resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.7.6.tgz#c462ddfbf2452e56e4b25d1f02b34caccddb0f33" integrity sha512-vzsCBTiJ56R3nRDpIJnKOJzsZP7KFO8BkXk7zvQgQiXpml2o/djPCRhuyaFc7VTqSHlLPQHVI1feTLAwHp+prQ== -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.8.8, prosemirror-view@^1.8.9: +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.8.9: version "1.8.9" resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.8.9.tgz#9303def925eba0a8ce4589e64b4a011eaccfc1e0" integrity sha512-K3/z7qDR6rEMH/gKXqu5pmjmtyhtuTWeQyselK6HMp3jbn1UANU4CfdU/awQT+zbRjv4ZJXGb9lxnuNmdUQS3Q== @@ -10362,7 +10362,7 @@ tippy.js@^4.2.1: dependencies: popper.js "^1.14.7" -tiptap-commands@^1.7.0, tiptap-commands@^1.7.1: +tiptap-commands@^1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.7.1.tgz#88f615e623836c49f3c4ad1b1cdd95ebed7cf0e1" integrity sha512-HQVFgfBeeOe9mJFYUDkYxiX1D+aYjyO9rO0SeYdumqv9rNHTF3GFfx7qJSk4/d2+GgXDRyoiO35B25EkxGjJJw== @@ -10373,19 +10373,19 @@ tiptap-commands@^1.7.0, tiptap-commands@^1.7.1: prosemirror-state "^1.2.2" tiptap-utils "^1.3.0" -tiptap-extensions@^1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.15.0.tgz#72768ba4c1d016ce752468466c91b33c87699e60" - integrity sha512-BqrHw5ZiF1WJVDw1r/9Xbta+ln1rITeQZHhA2p5ZaTi9ZRM7y9Bp44oSn2Pwzvb3bwCz+TO1Jv1MwASRKDMhug== +tiptap-extensions@^1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.16.2.tgz#deea16318b6f1d2b080d3d240ff9bfe4940f5261" + integrity sha512-b6/uv56eMTdmUPTobZpS2aJFmZd9h7FTi+LcVYfbytcw8iow/p6Rzf/gGny5c1L9x0Tvk93fvUQE+wuKg38DKw== dependencies: lowlight "^1.11.0" prosemirror-history "^1.0.4" prosemirror-state "^1.2.2" prosemirror-tables "^0.7.10" prosemirror-utils "^0.7.6" - prosemirror-view "^1.8.8" - tiptap "^1.15.0" - tiptap-commands "^1.7.0" + prosemirror-view "^1.8.9" + tiptap "^1.16.2" + tiptap-commands "^1.7.1" tiptap-utils@^1.3.0: version "1.3.0" @@ -10397,7 +10397,7 @@ tiptap-utils@^1.3.0: prosemirror-tables "^0.7.9" prosemirror-utils "^0.7.6" -tiptap@^1.15.0, tiptap@^1.16.2: +tiptap@^1.16.2: version "1.16.2" resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.16.2.tgz#0a94ea8f58cccafd56439bcbd29fa9c6071ed90a" integrity sha512-krMpbrQDvSwcp0FIUFjZgmlZUc65kvno+ASecp2G13DJUoAeYHTUW7AKng53xJP/rMm9ubc3q9XA6e6B5Bf+mA== From 718b2da6e755e5921059f8449955fc94e4c026bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 23 Apr 2019 17:21:45 +0200 Subject: [PATCH 024/124] Improve the docs with @datenbrei --- deployment/backup.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/deployment/backup.md b/deployment/backup.md index 556d257db..482b77175 100644 --- a/deployment/backup.md +++ b/deployment/backup.md @@ -39,7 +39,12 @@ Add the following to `spec.template.spec.containers`: ``` and write the file which will update the deployment. -Then perform your tasks! +The command `tail -f /dev/null` is the equivalent of *sleep forever*. It is a +hack to keep the container busy and to prevent its shutdown. It will also +override the default `neo4j` command and the kubernetes pod will not start the +database. + +Now perform your tasks! When you're done, edit the deployment again and remove the `command`. Write the file and trigger an update of the deployment. @@ -49,26 +54,30 @@ file and trigger an update of the deployment. First stop your Neo4J database, see above. Then: ```sh kubectl --namespace=human-connection get pods -# copy the ID of the pod running Neo4J +# Copy the ID of the pod running Neo4J. kubectl --namespace=human-connection exec -it bash -# once you're in the pod +# Once you're in the pod, dump the db to a file e.g. `/root/neo4j-backup`. neo4j-admin dump --to=/root/neo4j-backup exit -# download the file +# Download the file from the pod to your computer. kubectl cp human-connection/:/root/neo4j-backup ./neo4j-backup ``` -Restart your Neo4J database. +Revert your changes to deployment `nitro-neo4j` which will restart the database. ## Restore a Backup in Kubernetes First stop your Neo4J database. Then: ```sh kubectl --namespace=human-connection get pods -# copy the ID of the pod running Neo4J +# Copy the ID of the pod running Neo4J. +# Then upload your local backup to the pod. Note that once the pod gets deleted +# e.g. if you change the deployment, the backup file is gone with it. kubectl cp ./neo4j-backup human-connection/:/root/ kubectl --namespace=human-connection exec -it bash -# once you're in the pod +# Once you're in the pod restore the backup and overwrite the default database +# called `graph.db` with `--force`. +# This will delete all existing data in database `graph.db`! neo4j-admin load --from=/root/neo4j-backup --force exit ``` -Restart your Neo4J database. +Revert your changes to deployment `nitro-neo4j` which will restart the database. From 43d6254f3ebebbbebfc98f509c87bd2342b3178b Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 23 Apr 2019 12:23:21 -0300 Subject: [PATCH 025/124] Add custom CommentByPost query --- backend/src/graphql-schema.js | 3 ++- backend/src/resolvers/comments.js | 20 ++++++++++++++++++++ backend/src/schema.graphql | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql-schema.js b/backend/src/graphql-schema.js index aff10729f..bad277721 100644 --- a/backend/src/graphql-schema.js +++ b/backend/src/graphql-schema.js @@ -23,7 +23,8 @@ export const resolvers = { Query: { ...statistics.Query, ...userManagement.Query, - ...notifications.Query + ...notifications.Query, + ...comments.Query }, Mutation: { ...userManagement.Mutation, diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 5446278d1..b3350ec8e 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -1,6 +1,26 @@ import { neo4jgraphql } from 'neo4j-graphql-js' export default { + Query: { + CommentByPost: async (object, params, context, resolveInfo) => { + const { postId } = params + + const session = context.driver.session() + const transactionRes = await session.run(` + MATCH (comment:Comment)-[:COMMENTS]->(post:Post {id: $postId}) + RETURN comment {.id, .contentExcerpt, .createdAt}`, { + postId + }) + + session.close() + let comments = [] + transactionRes.records.map(record => { + comments.push(record.get('comment')) + }) + + return comments + } + }, Mutation: { CreateComment: async (object, params, context, resolveInfo) => { const { postId } = params diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 99bcd4533..4638fbd0d 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -16,6 +16,7 @@ type Query { LIMIT $limit """ ) + CommentByPost(postId: ID!): [Comment]! } type Mutation { # Get a JWT Token for the given Email and password From 20112bdd9b9e79247bacc8687948a4b99c00af47 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 23 Apr 2019 12:24:27 -0300 Subject: [PATCH 026/124] Add back asyncData, use CommentByPost query --- webapp/graphql/CommentQuery.js | 13 +++ webapp/pages/post/_id/_slug/index.vue | 116 ++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 webapp/graphql/CommentQuery.js diff --git a/webapp/graphql/CommentQuery.js b/webapp/graphql/CommentQuery.js new file mode 100644 index 000000000..1c3f9be20 --- /dev/null +++ b/webapp/graphql/CommentQuery.js @@ -0,0 +1,13 @@ +import gql from 'graphql-tag' + +export default app => { + return gql(` + query CommentByPost($postId: ID!) { + CommentByPost(postId: $postId, orderBy: createdAt_desc) { + id + contentExcerpt + createdAt + } + } + `) +} diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 27b552608..0ecd98c4e 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -102,7 +102,7 @@ - +
@@ -210,6 +210,7 @@ export default { data() { return { post: null, + comments: null, ready: false, title: 'loading', loading: false, @@ -226,6 +227,105 @@ export default { Post(post) { this.post = post[0] || {} this.title = this.post.title + }, + CommentByPost(comments) { + this.comments = comments || [] + } + }, + async asyncData(context) { + const { + params, + error, + app: { apolloProvider, $i18n } + } = context + const client = apolloProvider.defaultClient + const query = gql(` + query Post($slug: String!) { + Post(slug: $slug) { + id + title + content + createdAt + disabled + deleted + slug + image + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + location { + name: name${$i18n.locale().toUpperCase()} + } + badges { + id + key + icon + } + } + tags { + name + } + commentsCount + comments(orderBy: createdAt_desc) { + id + contentExcerpt + createdAt + disabled + deleted + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + location { + name: name${$i18n.locale().toUpperCase()} + } + badges { + id + key + icon + } + } + } + categories { + id + name + icon + } + shoutedCount + shoutedByCurrentUser + } + } + `) + const variables = { slug: params.slug } + const { + data: { Post } + } = await client.query({ query, variables }) + if (Post.length <= 0) { + // TODO: custom 404 error page with translations + const message = 'This post could not be found' + return error({ statusCode: 404, message }) + } + const [post] = Post + return { + post, + title: post.title } }, mounted() { @@ -248,7 +348,7 @@ export default { this.form.content = ' ' }, addComment(comment) { - this.$apollo.queries.Post.refetch() + this.$apollo.queries.CommentByPost.refetch() // this.post = { ...this.post, comments: [...this.post.comments, comment] } }, handleSubmit() { @@ -283,13 +383,13 @@ export default { } }, apollo: { - Post: { + CommentByPost: { query() { - return require('~/graphql/PostQuery.js').default(this) + return require('~/graphql/CommentQuery.js').default(this) }, variables() { return { - slug: this.$route.params.slug + postId: this.post.id } }, fetchPolicy: 'cache-and-network' From 500221314a4b8f7f355b6f2f4a962613911e5784 Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Tue, 23 Apr 2019 12:29:34 -0300 Subject: [PATCH 027/124] Update backup.md --- deployment/backup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/backup.md b/deployment/backup.md index 482b77175..5d6d61866 100644 --- a/deployment/backup.md +++ b/deployment/backup.md @@ -16,10 +16,10 @@ it. ## Stop and Restart Neo4J Database in Kubernetes [This tutorial](http://bigdatums.net/2017/11/07/how-to-keep-docker-containers-running/) -explains how to keep a docker container running. For kubernetes, the way how to +explains how to keep a docker container running. For kubernetes, the way to override the docker image `CMD` is explained [here](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#define-a-command-and-arguments-when-you-create-a-pod). -So, all we have to do is to edit the kubernetes deployment of our Neo4J database +So, all we have to do is edit the kubernetes deployment of our Neo4J database and set a custom `command` every time we have to carry out tasks like backup, restore, seed etc. From 84f672ed2d6a15e1619e896feb3896117b2c748e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 23 Apr 2019 16:06:44 +0000 Subject: [PATCH 028/124] [Security] Bump jquery from 3.3.1 to 3.4.0 in /backend Bumps [jquery](https://github.com/jquery/jquery) from 3.3.1 to 3.4.0. **This update includes security fixes.** - [Release notes](https://github.com/jquery/jquery/releases) - [Commits](https://github.com/jquery/jquery/compare/3.3.1...3.4.0) Signed-off-by: dependabot[bot] --- backend/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/yarn.lock b/backend/yarn.lock index 980111644..b7cf8099b 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -4926,9 +4926,9 @@ joi@^13.0.0: topo "3.x.x" jquery@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" - integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== + version "3.4.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.0.tgz#8de513fa0fa4b2c7d2e48a530e26f0596936efdf" + integrity sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ== js-levenshtein@^1.1.3: version "1.1.4" From 77ac0ce7a64825ff89e3388fc2ab91ab3b12ac2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 18 Apr 2019 20:14:17 +0200 Subject: [PATCH 029/124] Rename db-migration-worker to maintenance-worker --- backend/.dockerignore | 2 +- backend/db-migration-worker/.ssh/known_hosts | 3 --- ...se.db-migration.yml => docker-compose.maintenance.yml} | 8 ++++---- .../.dockerignore | 0 .../.gitignore | 0 .../Dockerfile | 0 .../{db-migration-worker => maintenance-worker}/README.md | 2 +- .../migrate.sh | 0 .../migration/mongo/import.sh | 0 .../migration/neo4j/badges.cql | 0 .../migration/neo4j/categories.cql | 0 .../migration/neo4j/comments.cql | 0 .../migration/neo4j/contributions.cql | 0 .../migration/neo4j/follows.cql | 0 .../migration/neo4j/import.sh | 0 .../migration/neo4j/shouts.cql | 0 .../migration/neo4j/users.cql | 0 .../sync_uploads.sh | 0 deployment/db-migration-worker.yaml | 8 ++++---- deployment/legacy-migration/deployment-backend.yaml | 6 +++--- deployment/legacy-migration/deployment-neo4j.yaml | 6 +++--- 21 files changed, 16 insertions(+), 19 deletions(-) delete mode 100644 backend/db-migration-worker/.ssh/known_hosts rename backend/{docker-compose.db-migration.yml => docker-compose.maintenance.yml} (82%) rename backend/{db-migration-worker => maintenance-worker}/.dockerignore (100%) rename backend/{db-migration-worker => maintenance-worker}/.gitignore (100%) rename backend/{db-migration-worker => maintenance-worker}/Dockerfile (100%) rename backend/{db-migration-worker => maintenance-worker}/README.md (96%) rename backend/{db-migration-worker => maintenance-worker}/migrate.sh (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/mongo/import.sh (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/badges.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/categories.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/comments.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/contributions.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/follows.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/import.sh (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/shouts.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/migration/neo4j/users.cql (100%) rename backend/{db-migration-worker => maintenance-worker}/sync_uploads.sh (100%) diff --git a/backend/.dockerignore b/backend/.dockerignore index 31f5b28f3..25a941824 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -15,7 +15,7 @@ node_modules/ scripts/ dist/ -db-migration-worker/ +maintenance-worker/ neo4j/ public/uploads/* diff --git a/backend/db-migration-worker/.ssh/known_hosts b/backend/db-migration-worker/.ssh/known_hosts deleted file mode 100644 index 947840cb2..000000000 --- a/backend/db-migration-worker/.ssh/known_hosts +++ /dev/null @@ -1,3 +0,0 @@ -|1|GuOYlVEhTowidPs18zj9p5F2j3o=|sDHJYLz9Ftv11oXeGEjs7SpVyg0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM5N29bI5CeKu1/RBPyM2fwyf7fuajOO+tyhKe1+CC2sZ1XNB5Ff6t6MtCLNRv2mUuvzTbW/HkisDiA5tuXUHOk= -|1|2KP9NV+Q5g2MrtjAeFSVcs8YeOI=|nf3h4wWVwC4xbBS1kzgzE2tBldk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E= -|1|HonYIRNhKyroUHPKU1HSZw0+Qzs=|5T1btfwFBz2vNSldhqAIfTbfIgQ= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E= diff --git a/backend/docker-compose.db-migration.yml b/backend/docker-compose.maintenance.yml similarity index 82% rename from backend/docker-compose.db-migration.yml rename to backend/docker-compose.maintenance.yml index 02f054d1b..943540351 100644 --- a/backend/docker-compose.db-migration.yml +++ b/backend/docker-compose.maintenance.yml @@ -9,14 +9,14 @@ services: - mongo-export:/mongo-export environment: - NEO4J_apoc_import_file_enabled=true - db-migration-worker: + maintenance-worker: build: - context: db-migration-worker + context: maintenance-worker volumes: - mongo-export:/mongo-export - uploads:/uploads - - ./db-migration-worker/migration/:/migration - - ./db-migration-worker/.ssh/:/root/.ssh/ + - ./maintenance-worker/migration/:/migration + - ./maintenance-worker/.ssh/:/root/.ssh/ networks: - hc-network depends_on: diff --git a/backend/db-migration-worker/.dockerignore b/backend/maintenance-worker/.dockerignore similarity index 100% rename from backend/db-migration-worker/.dockerignore rename to backend/maintenance-worker/.dockerignore diff --git a/backend/db-migration-worker/.gitignore b/backend/maintenance-worker/.gitignore similarity index 100% rename from backend/db-migration-worker/.gitignore rename to backend/maintenance-worker/.gitignore diff --git a/backend/db-migration-worker/Dockerfile b/backend/maintenance-worker/Dockerfile similarity index 100% rename from backend/db-migration-worker/Dockerfile rename to backend/maintenance-worker/Dockerfile diff --git a/backend/db-migration-worker/README.md b/backend/maintenance-worker/README.md similarity index 96% rename from backend/db-migration-worker/README.md rename to backend/maintenance-worker/README.md index 3d0f86edd..4b93686ac 100644 --- a/backend/db-migration-worker/README.md +++ b/backend/maintenance-worker/README.md @@ -29,7 +29,7 @@ SSH_USERNAME=username SSH_HOST=some.server.com MONGODB_USERNAME='hc-api' MONGODB Download the remote mongo database: ```bash -docker-compose exec db-migration-worker ./import.sh +docker-compose exec maintenance-worker ./import.sh ``` Import the local download into Neo4J: diff --git a/backend/db-migration-worker/migrate.sh b/backend/maintenance-worker/migrate.sh similarity index 100% rename from backend/db-migration-worker/migrate.sh rename to backend/maintenance-worker/migrate.sh diff --git a/backend/db-migration-worker/migration/mongo/import.sh b/backend/maintenance-worker/migration/mongo/import.sh similarity index 100% rename from backend/db-migration-worker/migration/mongo/import.sh rename to backend/maintenance-worker/migration/mongo/import.sh diff --git a/backend/db-migration-worker/migration/neo4j/badges.cql b/backend/maintenance-worker/migration/neo4j/badges.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/badges.cql rename to backend/maintenance-worker/migration/neo4j/badges.cql diff --git a/backend/db-migration-worker/migration/neo4j/categories.cql b/backend/maintenance-worker/migration/neo4j/categories.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/categories.cql rename to backend/maintenance-worker/migration/neo4j/categories.cql diff --git a/backend/db-migration-worker/migration/neo4j/comments.cql b/backend/maintenance-worker/migration/neo4j/comments.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/comments.cql rename to backend/maintenance-worker/migration/neo4j/comments.cql diff --git a/backend/db-migration-worker/migration/neo4j/contributions.cql b/backend/maintenance-worker/migration/neo4j/contributions.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/contributions.cql rename to backend/maintenance-worker/migration/neo4j/contributions.cql diff --git a/backend/db-migration-worker/migration/neo4j/follows.cql b/backend/maintenance-worker/migration/neo4j/follows.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/follows.cql rename to backend/maintenance-worker/migration/neo4j/follows.cql diff --git a/backend/db-migration-worker/migration/neo4j/import.sh b/backend/maintenance-worker/migration/neo4j/import.sh similarity index 100% rename from backend/db-migration-worker/migration/neo4j/import.sh rename to backend/maintenance-worker/migration/neo4j/import.sh diff --git a/backend/db-migration-worker/migration/neo4j/shouts.cql b/backend/maintenance-worker/migration/neo4j/shouts.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/shouts.cql rename to backend/maintenance-worker/migration/neo4j/shouts.cql diff --git a/backend/db-migration-worker/migration/neo4j/users.cql b/backend/maintenance-worker/migration/neo4j/users.cql similarity index 100% rename from backend/db-migration-worker/migration/neo4j/users.cql rename to backend/maintenance-worker/migration/neo4j/users.cql diff --git a/backend/db-migration-worker/sync_uploads.sh b/backend/maintenance-worker/sync_uploads.sh similarity index 100% rename from backend/db-migration-worker/sync_uploads.sh rename to backend/maintenance-worker/sync_uploads.sh diff --git a/deployment/db-migration-worker.yaml b/deployment/db-migration-worker.yaml index 55743e360..8ee86f11c 100644 --- a/deployment/db-migration-worker.yaml +++ b/deployment/db-migration-worker.yaml @@ -2,7 +2,7 @@ kind: Pod apiVersion: v1 metadata: - name: nitro-db-migration-worker + name: nitro-maintenance-worker namespace: human-connection spec: volumes: @@ -14,11 +14,11 @@ persistentVolumeClaim: claimName: mongo-export-claim containers: - - name: nitro-db-migration-worker - image: humanconnection/db-migration-worker:latest + - name: nitro-maintenance-worker + image: humanconnection/maintenance-worker:latest envFrom: - configMapRef: - name: db-migration-worker + name: maintenance-worker volumeMounts: - name: secret-volume readOnly: false diff --git a/deployment/legacy-migration/deployment-backend.yaml b/deployment/legacy-migration/deployment-backend.yaml index 1adeb0665..1bcb380aa 100644 --- a/deployment/legacy-migration/deployment-backend.yaml +++ b/deployment/legacy-migration/deployment-backend.yaml @@ -8,12 +8,12 @@ template: spec: containers: - - name: nitro-db-migration-worker - image: humanconnection/db-migration-worker:latest + - name: nitro-maintenance-worker + image: humanconnection/maintenance-worker:latest imagePullPolicy: Always envFrom: - configMapRef: - name: db-migration-worker + name: maintenance-worker volumeMounts: - name: secret-volume readOnly: false diff --git a/deployment/legacy-migration/deployment-neo4j.yaml b/deployment/legacy-migration/deployment-neo4j.yaml index 2852b90cb..0f3252984 100644 --- a/deployment/legacy-migration/deployment-neo4j.yaml +++ b/deployment/legacy-migration/deployment-neo4j.yaml @@ -8,12 +8,12 @@ template: spec: containers: - - name: nitro-db-migration-worker - image: humanconnection/db-migration-worker:latest + - name: nitro-maintenance-worker + image: humanconnection/maintenance-worker:latest imagePullPolicy: Always envFrom: - configMapRef: - name: db-migration-worker + name: maintenance-worker env: - name: COMMIT value: From 03521547429a8c4c7dae726b106c1b552eedc408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 18 Apr 2019 22:17:41 +0200 Subject: [PATCH 030/124] Removing unused docker-compose.yml in backend/ --- backend/docker-compose.cypress.yml | 18 ---------- backend/docker-compose.override.yml | 23 ------------- backend/docker-compose.travis.yml | 14 -------- backend/docker-compose.yml | 34 ------------------- ...ance.yml => docker-compose.maintenance.yml | 10 +++--- 5 files changed, 5 insertions(+), 94 deletions(-) delete mode 100644 backend/docker-compose.cypress.yml delete mode 100644 backend/docker-compose.override.yml delete mode 100644 backend/docker-compose.travis.yml delete mode 100644 backend/docker-compose.yml rename backend/docker-compose.maintenance.yml => docker-compose.maintenance.yml (79%) diff --git a/backend/docker-compose.cypress.yml b/backend/docker-compose.cypress.yml deleted file mode 100644 index 3d577e638..000000000 --- a/backend/docker-compose.cypress.yml +++ /dev/null @@ -1,18 +0,0 @@ -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/backend/docker-compose.override.yml b/backend/docker-compose.override.yml deleted file mode 100644 index b972c31f6..000000000 --- a/backend/docker-compose.override.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: "3.7" - -services: - backend: - image: humanconnection/nitro-backend:builder - build: - context: . - target: builder - volumes: - - .:/nitro-backend - - /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/backend/docker-compose.travis.yml b/backend/docker-compose.travis.yml deleted file mode 100644 index e1998f6dd..000000000 --- a/backend/docker-compose.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "3.7" - -services: - neo4j: - environment: - - NEO4J_AUTH=none - ports: - - 7687:7687 - - 7474:7474 - backend: - image: humanconnection/nitro-backend:builder - build: - context: . - target: builder diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml deleted file mode 100644 index 30d102f96..000000000 --- a/backend/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: "3.7" - -services: - backend: - image: humanconnection/nitro-backend:latest - build: - context: . - target: production - networks: - - hc-network - depends_on: - - neo4j - ports: - - 4000:4000 - environment: - - NEO4J_URI=bolt://neo4j:7687 - - GRAPHQL_PORT=4000 - - GRAPHQL_URI=http://localhost:4000 - - CLIENT_URI=http://localhost:3000 - - JWT_SECRET=b/&&7b78BF&fv/Vd - - MOCK=false - - MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ - - PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78 - - neo4j: - image: humanconnection/neo4j:latest - build: - context: neo4j - networks: - - hc-network - -networks: - hc-network: - name: hc-network diff --git a/backend/docker-compose.maintenance.yml b/docker-compose.maintenance.yml similarity index 79% rename from backend/docker-compose.maintenance.yml rename to docker-compose.maintenance.yml index 943540351..fbbe4ef41 100644 --- a/backend/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -1,4 +1,4 @@ -version: "3.7" +version: "3.4" services: backend: @@ -9,14 +9,14 @@ services: - mongo-export:/mongo-export environment: - NEO4J_apoc_import_file_enabled=true - maintenance-worker: + maintenance: build: - context: maintenance-worker + context: backend/maintenance-worker volumes: - mongo-export:/mongo-export - uploads:/uploads - - ./maintenance-worker/migration/:/migration - - ./maintenance-worker/.ssh/:/root/.ssh/ + - ./backend/maintenance-worker/migration/:/migration + - ./backend/maintenance-worker/.ssh/:/root/.ssh/ networks: - hc-network depends_on: From c7a08b792ed03dafd1888352f22758789a7dfc7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 18 Apr 2019 22:40:51 +0200 Subject: [PATCH 031/124] Move maintenance & neo4j in dedicated folders --- docker-compose.maintenance.yml | 6 +++--- docker-compose.yml | 2 +- {backend/maintenance-worker => maintenance}/.dockerignore | 0 {backend/maintenance-worker => maintenance}/.gitignore | 0 {backend/maintenance-worker => maintenance}/Dockerfile | 0 {backend/maintenance-worker => maintenance}/README.md | 0 {backend/maintenance-worker => maintenance}/migrate.sh | 0 .../migration/mongo/import.sh | 0 .../migration/neo4j/badges.cql | 0 .../migration/neo4j/categories.cql | 0 .../migration/neo4j/comments.cql | 0 .../migration/neo4j/contributions.cql | 0 .../migration/neo4j/follows.cql | 0 .../migration/neo4j/import.sh | 0 .../migration/neo4j/shouts.cql | 0 .../migration/neo4j/users.cql | 0 {backend/maintenance-worker => maintenance}/sync_uploads.sh | 0 {backend/neo4j => neo4j}/Dockerfile | 0 {backend/neo4j => neo4j}/migrate.sh | 0 19 files changed, 4 insertions(+), 4 deletions(-) rename {backend/maintenance-worker => maintenance}/.dockerignore (100%) rename {backend/maintenance-worker => maintenance}/.gitignore (100%) rename {backend/maintenance-worker => maintenance}/Dockerfile (100%) rename {backend/maintenance-worker => maintenance}/README.md (100%) rename {backend/maintenance-worker => maintenance}/migrate.sh (100%) rename {backend/maintenance-worker => maintenance}/migration/mongo/import.sh (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/badges.cql (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/categories.cql (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/comments.cql (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/contributions.cql (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/follows.cql (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/import.sh (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/shouts.cql (100%) rename {backend/maintenance-worker => maintenance}/migration/neo4j/users.cql (100%) rename {backend/maintenance-worker => maintenance}/sync_uploads.sh (100%) rename {backend/neo4j => neo4j}/Dockerfile (100%) rename {backend/neo4j => neo4j}/migrate.sh (100%) diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index fbbe4ef41..c09accd1d 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -11,12 +11,12 @@ services: - NEO4J_apoc_import_file_enabled=true maintenance: build: - context: backend/maintenance-worker + context: maintenance volumes: - mongo-export:/mongo-export - uploads:/uploads - - ./backend/maintenance-worker/migration/:/migration - - ./backend/maintenance-worker/.ssh/:/root/.ssh/ + - ./maintenance/migration/:/migration + - ./maintenance/.ssh/:/root/.ssh/ networks: - hc-network depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index a7e7c0802..896d1bef9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,7 +38,7 @@ services: neo4j: image: humanconnection/neo4j:latest build: - context: backend/neo4j + context: neo4j networks: - hc-network diff --git a/backend/maintenance-worker/.dockerignore b/maintenance/.dockerignore similarity index 100% rename from backend/maintenance-worker/.dockerignore rename to maintenance/.dockerignore diff --git a/backend/maintenance-worker/.gitignore b/maintenance/.gitignore similarity index 100% rename from backend/maintenance-worker/.gitignore rename to maintenance/.gitignore diff --git a/backend/maintenance-worker/Dockerfile b/maintenance/Dockerfile similarity index 100% rename from backend/maintenance-worker/Dockerfile rename to maintenance/Dockerfile diff --git a/backend/maintenance-worker/README.md b/maintenance/README.md similarity index 100% rename from backend/maintenance-worker/README.md rename to maintenance/README.md diff --git a/backend/maintenance-worker/migrate.sh b/maintenance/migrate.sh similarity index 100% rename from backend/maintenance-worker/migrate.sh rename to maintenance/migrate.sh diff --git a/backend/maintenance-worker/migration/mongo/import.sh b/maintenance/migration/mongo/import.sh similarity index 100% rename from backend/maintenance-worker/migration/mongo/import.sh rename to maintenance/migration/mongo/import.sh diff --git a/backend/maintenance-worker/migration/neo4j/badges.cql b/maintenance/migration/neo4j/badges.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/badges.cql rename to maintenance/migration/neo4j/badges.cql diff --git a/backend/maintenance-worker/migration/neo4j/categories.cql b/maintenance/migration/neo4j/categories.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/categories.cql rename to maintenance/migration/neo4j/categories.cql diff --git a/backend/maintenance-worker/migration/neo4j/comments.cql b/maintenance/migration/neo4j/comments.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/comments.cql rename to maintenance/migration/neo4j/comments.cql diff --git a/backend/maintenance-worker/migration/neo4j/contributions.cql b/maintenance/migration/neo4j/contributions.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/contributions.cql rename to maintenance/migration/neo4j/contributions.cql diff --git a/backend/maintenance-worker/migration/neo4j/follows.cql b/maintenance/migration/neo4j/follows.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/follows.cql rename to maintenance/migration/neo4j/follows.cql diff --git a/backend/maintenance-worker/migration/neo4j/import.sh b/maintenance/migration/neo4j/import.sh similarity index 100% rename from backend/maintenance-worker/migration/neo4j/import.sh rename to maintenance/migration/neo4j/import.sh diff --git a/backend/maintenance-worker/migration/neo4j/shouts.cql b/maintenance/migration/neo4j/shouts.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/shouts.cql rename to maintenance/migration/neo4j/shouts.cql diff --git a/backend/maintenance-worker/migration/neo4j/users.cql b/maintenance/migration/neo4j/users.cql similarity index 100% rename from backend/maintenance-worker/migration/neo4j/users.cql rename to maintenance/migration/neo4j/users.cql diff --git a/backend/maintenance-worker/sync_uploads.sh b/maintenance/sync_uploads.sh similarity index 100% rename from backend/maintenance-worker/sync_uploads.sh rename to maintenance/sync_uploads.sh diff --git a/backend/neo4j/Dockerfile b/neo4j/Dockerfile similarity index 100% rename from backend/neo4j/Dockerfile rename to neo4j/Dockerfile diff --git a/backend/neo4j/migrate.sh b/neo4j/migrate.sh similarity index 100% rename from backend/neo4j/migrate.sh rename to neo4j/migrate.sh From be9980f2aa1ccacf321f4e2cc7c3499b42054fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 18 Apr 2019 22:58:04 +0200 Subject: [PATCH 032/124] Tag maintenance image consitently --- docker-compose.maintenance.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index c09accd1d..6cfaf1f46 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -10,6 +10,7 @@ services: environment: - NEO4J_apoc_import_file_enabled=true maintenance: + image: humanconnection/maintenance-worker:latest build: context: maintenance volumes: From 45c0dbd5a87a669e87dd1525326b42544e9bee1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 19 Apr 2019 01:23:51 +0200 Subject: [PATCH 033/124] Provision maintenance-worker to seed/reset db --- docker-compose.maintenance.yml | 7 +++++++ maintenance/Dockerfile | 29 ++++++++++++++++++++--------- maintenance/seeding/.env.template | 12 ++++++++++++ maintenance/seeding/package.json | 12 ++++++++++++ 4 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 maintenance/seeding/.env.template create mode 100644 maintenance/seeding/package.json diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index 6cfaf1f46..d98cbab2e 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -23,6 +23,13 @@ services: depends_on: - backend environment: + - GRAPHQL_PORT=4000 + - GRAPHQL_URI=http://localhost:4000 + - CLIENT_URI=http://localhost:3000 + - JWT_SECRET=b/&&7b78BF&fv/Vd + - MOCK=false + - MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ + - PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78 - NEO4J_URI=bolt://neo4j:7687 - "SSH_USERNAME=${SSH_USERNAME}" - "SSH_HOST=${SSH_HOST}" diff --git a/maintenance/Dockerfile b/maintenance/Dockerfile index 865a4c330..8cc3d6c28 100644 --- a/maintenance/Dockerfile +++ b/maintenance/Dockerfile @@ -1,13 +1,24 @@ -FROM mongo:4 +FROM humanconnection/neo4j:latest as base + +RUN apk upgrade --update +RUN apk add mongodb mongodb-tools openssh nodejs yarn -RUN apt-get update && apt-get -y install --no-install-recommends wget apt-transport-https \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* -RUN wget -O - https://debian.neo4j.org/neotechnology.gpg.key | apt-key add - -RUN echo 'deb https://debian.neo4j.org/repo stable/' | tee /etc/apt/sources.list.d/neo4j.list -RUN apt-get update && apt-get -y install --no-install-recommends openjdk-8-jre openssh-client neo4j rsync \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* COPY migration ./migration COPY migrate.sh /usr/local/bin/migrate COPY sync_uploads.sh /usr/local/bin/sync_uploads + + +ENV NODE_ENV=maintenance +FROM humanconnection/nitro-backend:latest as backend +FROM base +COPY --from=backend /nitro-backend /nitro-backend + +COPY seeding/package.json . +# Install graphql-request manually, it's required for seeding and not included +# in backend's preinstalled node_modules/ +RUN yarn install && \ + cp -r ./node_modules/* /nitro-backend/node_modules/ && \ + rm -rf ./node_modules/ yarn.lock && \ + mv package.json /nitro-backend/package.json +# We have to do this odd copying to prevent cleaning `node_modules` folder which +# would happen if we `yarn install|add` in the target directory diff --git a/maintenance/seeding/.env.template b/maintenance/seeding/.env.template new file mode 100644 index 000000000..abc62b2dc --- /dev/null +++ b/maintenance/seeding/.env.template @@ -0,0 +1,12 @@ +NEO4J_URI=bolt://localhost:7687 +NEO4J_USER=neo4j +NEO4J_PASSWORD=letmein +GRAPHQL_PORT=4000 +GRAPHQL_URI=http://localhost:4000 +CLIENT_URI=http://localhost:3000 +MOCK=false + +JWT_SECRET="b/&&7b78BF&fv/Vd" +MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ" + +PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78" diff --git a/maintenance/seeding/package.json b/maintenance/seeding/package.json new file mode 100644 index 000000000..f3e5c115f --- /dev/null +++ b/maintenance/seeding/package.json @@ -0,0 +1,12 @@ +{ + "license": "MIT", + "scripts": { + "start": "node dist/", + "db:script:seed": "wait-on tcp:4001 && node dist/seed/seed-db.js", + "db:reset": "node dist/seed/reset-db.js", + "db:seed": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions run-p --race start db:script:seed" + }, + "dependencies": { + "graphql-request": "*" + } +} From 542c37a32bb1761cd5f9380d07c823da48ca061f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 19 Apr 2019 14:06:15 +0200 Subject: [PATCH 034/124] Make maintenance-worker independent of backend --- docker-compose.maintenance.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index d98cbab2e..d88e477b3 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -1,14 +1,6 @@ version: "3.4" services: - backend: - volumes: - - uploads:/nitro-backend/public/uploads - neo4j: - volumes: - - mongo-export:/mongo-export - environment: - - NEO4J_apoc_import_file_enabled=true maintenance: image: humanconnection/maintenance-worker:latest build: @@ -18,10 +10,9 @@ services: - uploads:/uploads - ./maintenance/migration/:/migration - ./maintenance/.ssh/:/root/.ssh/ + - neo4j-data:/data networks: - hc-network - depends_on: - - backend environment: - GRAPHQL_PORT=4000 - GRAPHQL_URI=http://localhost:4000 @@ -30,7 +21,7 @@ services: - MOCK=false - MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ - PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78 - - NEO4J_URI=bolt://neo4j:7687 + - NEO4J_URI=bolt://localhost:7687 - "SSH_USERNAME=${SSH_USERNAME}" - "SSH_HOST=${SSH_HOST}" - "MONGODB_USERNAME=${MONGODB_USERNAME}" @@ -38,7 +29,15 @@ services: - "MONGODB_AUTH_DB=${MONGODB_AUTH_DB}" - "MONGODB_DATABASE=${MONGODB_DATABASE}" - "UPLOADS_DIRECTORY=${UPLOADS_DIRECTORY}" + - NEO4J_AUTH=none + ports: + - 7687:7687 + - 7474:7474 volumes: mongo-export: uploads: + neo4j-data: + +networks: + hc-network: From af4f391e28b7bbbe1bc652360c32bc01a2ba8dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 19 Apr 2019 15:27:48 +0200 Subject: [PATCH 035/124] Simplify migration script --- maintenance/Dockerfile | 25 ++++++++++++---------- maintenance/{ => binaries}/migrate.sh | 0 maintenance/{ => binaries}/sync_uploads.sh | 0 maintenance/migration/mongo/import.sh | 10 ++++----- 4 files changed, 18 insertions(+), 17 deletions(-) rename maintenance/{ => binaries}/migrate.sh (100%) rename maintenance/{ => binaries}/sync_uploads.sh (100%) diff --git a/maintenance/Dockerfile b/maintenance/Dockerfile index 8cc3d6c28..37985fefa 100644 --- a/maintenance/Dockerfile +++ b/maintenance/Dockerfile @@ -1,18 +1,17 @@ FROM humanconnection/neo4j:latest as base -RUN apk upgrade --update -RUN apk add mongodb mongodb-tools openssh nodejs yarn - -COPY migration ./migration -COPY migrate.sh /usr/local/bin/migrate -COPY sync_uploads.sh /usr/local/bin/sync_uploads - - ENV NODE_ENV=maintenance -FROM humanconnection/nitro-backend:latest as backend -FROM base -COPY --from=backend /nitro-backend /nitro-backend +EXPOSE 7687 7474 +VOLUME /mongo-export /uploads /data +RUN apk upgrade --update +RUN apk add --no-cache mongodb-tools openssh nodejs yarn + +FROM humanconnection/nitro-backend:latest as backend + +FROM base + +COPY --from=backend /nitro-backend /nitro-backend COPY seeding/package.json . # Install graphql-request manually, it's required for seeding and not included # in backend's preinstalled node_modules/ @@ -22,3 +21,7 @@ RUN yarn install && \ mv package.json /nitro-backend/package.json # We have to do this odd copying to prevent cleaning `node_modules` folder which # would happen if we `yarn install|add` in the target directory + +COPY migration ./migration +COPY ./binaries/migrate.sh /usr/local/bin/migrate +COPY ./binaries/sync_uploads.sh /usr/local/bin/sync_uploads diff --git a/maintenance/migrate.sh b/maintenance/binaries/migrate.sh similarity index 100% rename from maintenance/migrate.sh rename to maintenance/binaries/migrate.sh diff --git a/maintenance/sync_uploads.sh b/maintenance/binaries/sync_uploads.sh similarity index 100% rename from maintenance/sync_uploads.sh rename to maintenance/binaries/sync_uploads.sh diff --git a/maintenance/migration/mongo/import.sh b/maintenance/migration/mongo/import.sh index 7cf3e91e4..15732e589 100755 --- a/maintenance/migration/mongo/import.sh +++ b/maintenance/migration/mongo/import.sh @@ -9,16 +9,14 @@ echo "MONGODB_DATABASE ${MONGODB_DATABASE}" echo "MONGODB_AUTH_DB ${MONGODB_AUTH_DB}" echo "-------------------------------------------------" -mongo ${MONGODB_DATABASE} --eval "db.dropDatabase();" rm -rf /mongo-export/* ssh -4 -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${SSH_HOST} -mongodump --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --gzip --archive=/tmp/mongodump.archive -mongorestore --gzip --archive=/tmp/mongodump.archive -ssh -S my-ctrl-socket -O check -l ${SSH_USERNAME} ${SSH_HOST} -ssh -S my-ctrl-socket -O exit -l ${SSH_USERNAME} ${SSH_HOST} for collection in "categories" "badges" "users" "contributions" "comments" "follows" "shouts" do - mongoexport --db ${MONGODB_DATABASE} --collection $collection --out "/mongo-export/$collection.json" + mongoexport --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --db ${MONGODB_DATABASE} --collection $collection --out "/mongo-export/$collection.json" done + +ssh -S my-ctrl-socket -O check -l ${SSH_USERNAME} ${SSH_HOST} +ssh -S my-ctrl-socket -O exit -l ${SSH_USERNAME} ${SSH_HOST} From a5f842632548f2607a8d68c32a613282e28b4a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 19 Apr 2019 15:39:20 +0200 Subject: [PATCH 036/124] Add missing env, import mongo collections to neo4j --- docker-compose.maintenance.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index d88e477b3..aca042a54 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -22,6 +22,8 @@ services: - MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ - PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78 - NEO4J_URI=bolt://localhost:7687 + - NEO4J_apoc_import_file_enabled=true + - NEO4J_AUTH=none - "SSH_USERNAME=${SSH_USERNAME}" - "SSH_HOST=${SSH_HOST}" - "MONGODB_USERNAME=${MONGODB_USERNAME}" @@ -29,7 +31,6 @@ services: - "MONGODB_AUTH_DB=${MONGODB_AUTH_DB}" - "MONGODB_DATABASE=${MONGODB_DATABASE}" - "UPLOADS_DIRECTORY=${UPLOADS_DIRECTORY}" - - NEO4J_AUTH=none ports: - 7687:7687 - 7474:7474 From abc17b2adcbf939013e9693f012b78271932e690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 19 Apr 2019 15:51:30 +0200 Subject: [PATCH 037/124] Fix import_legacy_uploads --- maintenance/Dockerfile | 5 ++--- maintenance/binaries/{migrate.sh => import_legacy_db} | 0 .../binaries/{sync_uploads.sh => import_legacy_uploads} | 0 maintenance/binaries/reset_db | 4 ++++ maintenance/binaries/seed_db | 4 ++++ 5 files changed, 10 insertions(+), 3 deletions(-) rename maintenance/binaries/{migrate.sh => import_legacy_db} (100%) rename maintenance/binaries/{sync_uploads.sh => import_legacy_uploads} (100%) create mode 100755 maintenance/binaries/reset_db create mode 100755 maintenance/binaries/seed_db diff --git a/maintenance/Dockerfile b/maintenance/Dockerfile index 37985fefa..83154d836 100644 --- a/maintenance/Dockerfile +++ b/maintenance/Dockerfile @@ -5,7 +5,7 @@ EXPOSE 7687 7474 VOLUME /mongo-export /uploads /data RUN apk upgrade --update -RUN apk add --no-cache mongodb-tools openssh nodejs yarn +RUN apk add --no-cache mongodb-tools openssh nodejs yarn rsync FROM humanconnection/nitro-backend:latest as backend @@ -23,5 +23,4 @@ RUN yarn install && \ # would happen if we `yarn install|add` in the target directory COPY migration ./migration -COPY ./binaries/migrate.sh /usr/local/bin/migrate -COPY ./binaries/sync_uploads.sh /usr/local/bin/sync_uploads +COPY ./binaries/* /usr/local/bin/ diff --git a/maintenance/binaries/migrate.sh b/maintenance/binaries/import_legacy_db similarity index 100% rename from maintenance/binaries/migrate.sh rename to maintenance/binaries/import_legacy_db diff --git a/maintenance/binaries/sync_uploads.sh b/maintenance/binaries/import_legacy_uploads similarity index 100% rename from maintenance/binaries/sync_uploads.sh rename to maintenance/binaries/import_legacy_uploads diff --git a/maintenance/binaries/reset_db b/maintenance/binaries/reset_db new file mode 100755 index 000000000..ce768089a --- /dev/null +++ b/maintenance/binaries/reset_db @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e + +cd /nitro-backend && yarn run db:reset diff --git a/maintenance/binaries/seed_db b/maintenance/binaries/seed_db new file mode 100755 index 000000000..ce768089a --- /dev/null +++ b/maintenance/binaries/seed_db @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e + +cd /nitro-backend && yarn run db:reset From 56c0f8dfdca5eea31c7bad661cd1a493609db89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 19 Apr 2019 16:17:25 +0200 Subject: [PATCH 038/124] Create private SSH key from base64 encoded env --- docker-compose.maintenance.yml | 4 ++-- maintenance/binaries/create_private_ssh_key_from_env | 6 ++++++ maintenance/binaries/import_legacy_uploads | 1 + maintenance/migration/mongo/import.sh | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100755 maintenance/binaries/create_private_ssh_key_from_env diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index aca042a54..b18f97513 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -8,9 +8,8 @@ services: volumes: - mongo-export:/mongo-export - uploads:/uploads - - ./maintenance/migration/:/migration - - ./maintenance/.ssh/:/root/.ssh/ - neo4j-data:/data + - ./maintenance/migration/:/migration networks: - hc-network environment: @@ -26,6 +25,7 @@ services: - NEO4J_AUTH=none - "SSH_USERNAME=${SSH_USERNAME}" - "SSH_HOST=${SSH_HOST}" + - "SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY}" - "MONGODB_USERNAME=${MONGODB_USERNAME}" - "MONGODB_PASSWORD=${MONGODB_PASSWORD}" - "MONGODB_AUTH_DB=${MONGODB_AUTH_DB}" diff --git a/maintenance/binaries/create_private_ssh_key_from_env b/maintenance/binaries/create_private_ssh_key_from_env new file mode 100755 index 000000000..f44671978 --- /dev/null +++ b/maintenance/binaries/create_private_ssh_key_from_env @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +mkdir -p ~/.ssh +echo $SSH_PRIVATE_KEY | base64 -d > ~/.ssh/id_rsa +chmod 600 ~/.ssh/id_rsa diff --git a/maintenance/binaries/import_legacy_uploads b/maintenance/binaries/import_legacy_uploads index d24936e3b..73240715b 100755 --- a/maintenance/binaries/import_legacy_uploads +++ b/maintenance/binaries/import_legacy_uploads @@ -9,4 +9,5 @@ do fi done +create_private_ssh_key_from_env rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/* /uploads/ diff --git a/maintenance/migration/mongo/import.sh b/maintenance/migration/mongo/import.sh index 15732e589..89c255a76 100755 --- a/maintenance/migration/mongo/import.sh +++ b/maintenance/migration/mongo/import.sh @@ -9,6 +9,8 @@ echo "MONGODB_DATABASE ${MONGODB_DATABASE}" echo "MONGODB_AUTH_DB ${MONGODB_AUTH_DB}" echo "-------------------------------------------------" +create_private_ssh_key_from_env + rm -rf /mongo-export/* ssh -4 -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${SSH_HOST} From c50394630c7b796600b8e589d3f1e3e25003456f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sat, 20 Apr 2019 12:16:40 +0200 Subject: [PATCH 039/124] Simplify maintenance-worker once again After learning how to restore a neo4j database in kubernetes I believe the best way to seed is to seed locally, create a backup and then restore the backup to the kubernetes cluster. --- docker-compose.maintenance.yml | 2 -- maintenance/Dockerfile | 19 ++----------------- maintenance/binaries/import_legacy_uploads | 2 +- maintenance/binaries/reset_db | 4 ---- maintenance/binaries/seed_db | 4 ---- maintenance/known_hosts | 3 +++ maintenance/migration/mongo/import.sh | 7 ++++--- maintenance/migration/neo4j/badges.cql | 2 +- maintenance/migration/neo4j/categories.cql | 2 +- maintenance/migration/neo4j/comments.cql | 2 +- maintenance/migration/neo4j/contributions.cql | 2 +- maintenance/migration/neo4j/follows.cql | 2 +- maintenance/migration/neo4j/shouts.cql | 2 +- maintenance/migration/neo4j/users.cql | 2 +- maintenance/seeding/.env.template | 12 ------------ maintenance/seeding/package.json | 12 ------------ 16 files changed, 17 insertions(+), 62 deletions(-) delete mode 100755 maintenance/binaries/reset_db delete mode 100755 maintenance/binaries/seed_db create mode 100644 maintenance/known_hosts delete mode 100644 maintenance/seeding/.env.template delete mode 100644 maintenance/seeding/package.json diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index b18f97513..33a9d28f6 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -6,7 +6,6 @@ services: build: context: maintenance volumes: - - mongo-export:/mongo-export - uploads:/uploads - neo4j-data:/data - ./maintenance/migration/:/migration @@ -36,7 +35,6 @@ services: - 7474:7474 volumes: - mongo-export: uploads: neo4j-data: diff --git a/maintenance/Dockerfile b/maintenance/Dockerfile index 83154d836..1fafce5e8 100644 --- a/maintenance/Dockerfile +++ b/maintenance/Dockerfile @@ -1,26 +1,11 @@ -FROM humanconnection/neo4j:latest as base +FROM humanconnection/neo4j:latest ENV NODE_ENV=maintenance EXPOSE 7687 7474 -VOLUME /mongo-export /uploads /data RUN apk upgrade --update RUN apk add --no-cache mongodb-tools openssh nodejs yarn rsync -FROM humanconnection/nitro-backend:latest as backend - -FROM base - -COPY --from=backend /nitro-backend /nitro-backend -COPY seeding/package.json . -# Install graphql-request manually, it's required for seeding and not included -# in backend's preinstalled node_modules/ -RUN yarn install && \ - cp -r ./node_modules/* /nitro-backend/node_modules/ && \ - rm -rf ./node_modules/ yarn.lock && \ - mv package.json /nitro-backend/package.json -# We have to do this odd copying to prevent cleaning `node_modules` folder which -# would happen if we `yarn install|add` in the target directory - +COPY known_hosts /root/.ssh/known_hosts COPY migration ./migration COPY ./binaries/* /usr/local/bin/ diff --git a/maintenance/binaries/import_legacy_uploads b/maintenance/binaries/import_legacy_uploads index 73240715b..24ae0fca5 100755 --- a/maintenance/binaries/import_legacy_uploads +++ b/maintenance/binaries/import_legacy_uploads @@ -9,5 +9,5 @@ do fi done -create_private_ssh_key_from_env +[ -z "$SSH_PRIVATE_KEY" ] || create_private_ssh_key_from_env rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/* /uploads/ diff --git a/maintenance/binaries/reset_db b/maintenance/binaries/reset_db deleted file mode 100755 index ce768089a..000000000 --- a/maintenance/binaries/reset_db +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -e - -cd /nitro-backend && yarn run db:reset diff --git a/maintenance/binaries/seed_db b/maintenance/binaries/seed_db deleted file mode 100755 index ce768089a..000000000 --- a/maintenance/binaries/seed_db +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -e - -cd /nitro-backend && yarn run db:reset diff --git a/maintenance/known_hosts b/maintenance/known_hosts new file mode 100644 index 000000000..947840cb2 --- /dev/null +++ b/maintenance/known_hosts @@ -0,0 +1,3 @@ +|1|GuOYlVEhTowidPs18zj9p5F2j3o=|sDHJYLz9Ftv11oXeGEjs7SpVyg0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM5N29bI5CeKu1/RBPyM2fwyf7fuajOO+tyhKe1+CC2sZ1XNB5Ff6t6MtCLNRv2mUuvzTbW/HkisDiA5tuXUHOk= +|1|2KP9NV+Q5g2MrtjAeFSVcs8YeOI=|nf3h4wWVwC4xbBS1kzgzE2tBldk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E= +|1|HonYIRNhKyroUHPKU1HSZw0+Qzs=|5T1btfwFBz2vNSldhqAIfTbfIgQ= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E= diff --git a/maintenance/migration/mongo/import.sh b/maintenance/migration/mongo/import.sh index 89c255a76..328560bfc 100755 --- a/maintenance/migration/mongo/import.sh +++ b/maintenance/migration/mongo/import.sh @@ -9,15 +9,16 @@ echo "MONGODB_DATABASE ${MONGODB_DATABASE}" echo "MONGODB_AUTH_DB ${MONGODB_AUTH_DB}" echo "-------------------------------------------------" -create_private_ssh_key_from_env +[ -z "$SSH_PRIVATE_KEY" ] || create_private_ssh_key_from_env -rm -rf /mongo-export/* +rm -rf /tmp/mongo-export/* +mkdir -p /tmp/mongo-export ssh -4 -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${SSH_HOST} for collection in "categories" "badges" "users" "contributions" "comments" "follows" "shouts" do - mongoexport --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --db ${MONGODB_DATABASE} --collection $collection --out "/mongo-export/$collection.json" + mongoexport --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --db ${MONGODB_DATABASE} --collection $collection --out "/tmp/mongo-export/$collection.json" done ssh -S my-ctrl-socket -O check -l ${SSH_USERNAME} ${SSH_HOST} diff --git a/maintenance/migration/neo4j/badges.cql b/maintenance/migration/neo4j/badges.cql index 90e4755b4..f4bf67dda 100644 --- a/maintenance/migration/neo4j/badges.cql +++ b/maintenance/migration/neo4j/badges.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/badges.json') YIELD value as badge +CALL apoc.load.json('file:/tmp/mongo-export/badges.json') YIELD value as badge MERGE(b:Badge {id: badge._id["$oid"]}) ON CREATE SET b.key = badge.key, diff --git a/maintenance/migration/neo4j/categories.cql b/maintenance/migration/neo4j/categories.cql index a2bf6a352..c22354cbe 100644 --- a/maintenance/migration/neo4j/categories.cql +++ b/maintenance/migration/neo4j/categories.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/categories.json') YIELD value as category +CALL apoc.load.json('file:/tmp/mongo-export/categories.json') YIELD value as category MERGE(c:Category {id: category._id["$oid"]}) ON CREATE SET c.name = category.title, diff --git a/maintenance/migration/neo4j/comments.cql b/maintenance/migration/neo4j/comments.cql index 6709acbc8..eb645108a 100644 --- a/maintenance/migration/neo4j/comments.cql +++ b/maintenance/migration/neo4j/comments.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/comments.json') YIELD value as json +CALL apoc.load.json('file:/tmp/mongo-export/comments.json') YIELD value as json MERGE (comment:Comment {id: json._id["$oid"]}) ON CREATE SET comment.content = json.content, diff --git a/maintenance/migration/neo4j/contributions.cql b/maintenance/migration/neo4j/contributions.cql index 0c7b18959..134c276cf 100644 --- a/maintenance/migration/neo4j/contributions.cql +++ b/maintenance/migration/neo4j/contributions.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/contributions.json') YIELD value as post +CALL apoc.load.json('file:/tmp/mongo-export/contributions.json') YIELD value as post MERGE (p:Post {id: post._id["$oid"]}) ON CREATE SET p.title = post.title, diff --git a/maintenance/migration/neo4j/follows.cql b/maintenance/migration/neo4j/follows.cql index 0dad6a435..6f5416723 100644 --- a/maintenance/migration/neo4j/follows.cql +++ b/maintenance/migration/neo4j/follows.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/follows.json') YIELD value as follow +CALL apoc.load.json('file:/tmp/mongo-export/follows.json') YIELD value as follow MATCH (u1:User {id: follow.userId}), (u2:User {id: follow.foreignId}) MERGE (u1)-[:FOLLOWS]->(u2) ; diff --git a/maintenance/migration/neo4j/shouts.cql b/maintenance/migration/neo4j/shouts.cql index 60aca50c9..cd72ab66b 100644 --- a/maintenance/migration/neo4j/shouts.cql +++ b/maintenance/migration/neo4j/shouts.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/shouts.json') YIELD value as shout +CALL apoc.load.json('file:/tmp/mongo-export/shouts.json') YIELD value as shout MATCH (u:User {id: shout.userId}), (p:Post {id: shout.foreignId}) MERGE (u)-[:SHOUTED]->(p) ; diff --git a/maintenance/migration/neo4j/users.cql b/maintenance/migration/neo4j/users.cql index 5f87bb273..22eb46882 100644 --- a/maintenance/migration/neo4j/users.cql +++ b/maintenance/migration/neo4j/users.cql @@ -1,4 +1,4 @@ -CALL apoc.load.json('file:/mongo-export/users.json') YIELD value as user +CALL apoc.load.json('file:/tmp/mongo-export/users.json') YIELD value as user MERGE(u:User {id: user._id["$oid"]}) ON CREATE SET u.name = user.name, diff --git a/maintenance/seeding/.env.template b/maintenance/seeding/.env.template deleted file mode 100644 index abc62b2dc..000000000 --- a/maintenance/seeding/.env.template +++ /dev/null @@ -1,12 +0,0 @@ -NEO4J_URI=bolt://localhost:7687 -NEO4J_USER=neo4j -NEO4J_PASSWORD=letmein -GRAPHQL_PORT=4000 -GRAPHQL_URI=http://localhost:4000 -CLIENT_URI=http://localhost:3000 -MOCK=false - -JWT_SECRET="b/&&7b78BF&fv/Vd" -MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ" - -PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78" diff --git a/maintenance/seeding/package.json b/maintenance/seeding/package.json deleted file mode 100644 index f3e5c115f..000000000 --- a/maintenance/seeding/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "license": "MIT", - "scripts": { - "start": "node dist/", - "db:script:seed": "wait-on tcp:4001 && node dist/seed/seed-db.js", - "db:reset": "node dist/seed/reset-db.js", - "db:seed": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions run-p --race start db:script:seed" - }, - "dependencies": { - "graphql-request": "*" - } -} From 543b7c688855e25c782cd56edda0dbcdd508afeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sat, 20 Apr 2019 12:28:16 +0200 Subject: [PATCH 040/124] Configure neo4j deployment to have 1 pod max --- deployment/human-connection/deployment-neo4j.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deployment/human-connection/deployment-neo4j.yaml b/deployment/human-connection/deployment-neo4j.yaml index 2c76a3322..5b89b3656 100644 --- a/deployment/human-connection/deployment-neo4j.yaml +++ b/deployment/human-connection/deployment-neo4j.yaml @@ -6,6 +6,10 @@ namespace: human-connection spec: replicas: 1 + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: "100%" selector: matchLabels: human-connection.org/selector: deployment-human-connection-neo4j From 3b00a3419978fce9fc0803a65bb5eaf65a849ec3 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 23 Apr 2019 18:19:11 -0300 Subject: [PATCH 041/124] Add cypress step to check the editor is cleared --- cypress/integration/common/post.js | 7 ++++++- cypress/integration/post/Comment.feature | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index fc03b20b3..01601437c 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -15,4 +15,9 @@ When('I should be able to post a comment', () => { Then('I should see my comment', () => { cy.get('div.comment p') .should('contain', 'This is a comment') -}) +}) + +Then('the editor should be cleared', () => { + cy.get('.ProseMirror p') + .should('have.class', 'is-empty') +}) diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature index 164c13bd6..9290d7e21 100644 --- a/cypress/integration/post/Comment.feature +++ b/cypress/integration/post/Comment.feature @@ -10,7 +10,8 @@ Feature: Post Comment And I have a user account And I am logged in - Scenario: + Scenario: Comment creation Given I visit "post/bWBjpkTKZp/101-essays" Then I should be able to post a comment - And I should see my comment \ No newline at end of file + And I should see my comment + And the editor should be cleared From 11a1f7991e0c020ecd295340b8e03ce4bcb15fc4 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Tue, 23 Apr 2019 18:19:48 -0300 Subject: [PATCH 042/124] Remove PostQuery.js - to be added in a separate ticket(?) --- webapp/graphql/PostQuery.js | 79 ------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 webapp/graphql/PostQuery.js diff --git a/webapp/graphql/PostQuery.js b/webapp/graphql/PostQuery.js deleted file mode 100644 index d45624616..000000000 --- a/webapp/graphql/PostQuery.js +++ /dev/null @@ -1,79 +0,0 @@ -import gql from 'graphql-tag' - -export default app => { - const lang = app.$i18n.locale().toUpperCase() - return gql(` - query Post($slug: String!) { - Post(slug: $slug) { - id - title - content - createdAt - disabled - deleted - slug - image - author { - id - slug - name - avatar - disabled - deleted - shoutedCount - contributionsCount - commentsCount - followedByCount - followedByCurrentUser - location { - name: name${lang} - } - badges { - id - key - icon - } - } - tags { - name - } - commentsCount - comments(orderBy: createdAt_desc) { - id - contentExcerpt - createdAt - disabled - deleted - author { - id - slug - name - avatar - disabled - deleted - shoutedCount - contributionsCount - commentsCount - followedByCount - followedByCurrentUser - location { - name: name${lang} - } - badges { - id - key - icon - } - } - } - categories { - id - name - icon - } - shoutedCount - shoutedByCurrentUser - } - } - `) -} From 8b83a27da124829cd26ec7563915a6bccb16b797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 23 Apr 2019 23:56:35 +0200 Subject: [PATCH 043/124] Structure DO and minikube into separate pages --- SUMMARY.md | 2 + deployment/README.md | 75 +----------------- deployment/digital-ocean/README.md | 75 ++++++++++++++++++ .../digital-ocean/dashboard-screenshot.png | Bin 0 -> 181783 bytes deployment/minikube/README.md | 20 +++++ 5 files changed, 99 insertions(+), 73 deletions(-) create mode 100644 deployment/digital-ocean/README.md create mode 100644 deployment/digital-ocean/dashboard-screenshot.png create mode 100644 deployment/minikube/README.md diff --git a/SUMMARY.md b/SUMMARY.md index e1cf09126..dbd9e4299 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -21,6 +21,8 @@ * [Backend tests](backend/testing.md) * [Contributing](CONTRIBUTING.md) * [Kubernetes Deployment](deployment/README.md) + * [Minikube](deployment/minikube/README.md) + * [Digital Ocean](deployment/digital-ocean/README.md) * [Neo4J DB Backup](deployment/backup.md) * [Maintenance](maintenance/README.md) * [Feature Specification](cypress/features.md) diff --git a/deployment/README.md b/deployment/README.md index 84912d2a5..a288cdce7 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -4,80 +4,9 @@ We deploy with [kubernetes](https://kubernetes.io/). In order to deploy your own network you have to [install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and get a kubernetes cluster. -We have tested two different kubernetes providers: [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) -and [Digital Ocean](https://www.digitalocean.com/). +We have tested two different kubernetes providers: [Minikube](./minikube/README.md) +and [Digital Ocean](./digital-ocean/README.md). -## Minikube - -There are many Kubernetes providers, but if you're just getting started, Minikube is a tool that you can use to get your feet wet. - -[Install Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) - -Open minikube dashboard: - -```text -$ minikube dashboard -``` - -This will give you an overview. Some of the steps below need some timing to make ressources available to other dependent deployments. Keeping an eye on the dashboard is a great way to check that. - -Follow the [installation instruction](deployment.md#installation-with-kubernetes) below. If all the pods and services have settled and everything looks green in your minikube dashboard, expose the `nitro-web` service on your host system with: - -```text -$ minikube service nitro-web --namespace=human-connection -``` - -## Digital Ocean - -1. At first, create a cluster on Digital Ocean. -2. Download the config.yaml if the process has finished. -3. Put the config file where you can find it later \(preferable in your home directory under `~/.kube/`\) -4. In the open terminal you can set the current config for the active session: `export KUBECONFIG=~/.kube/THE-NAME-OF-YOUR-CLUSTER-kubeconfig.yaml`. You could make this change permanent by adding the line to your `.bashrc` or `~/.config/fish/config.fish` depending on your shell. - - Otherwise you would have to always add `--kubeconfig ~/.kube/THE-NAME-OF-YOUR-CLUSTER-kubeconfig.yaml` on every `kubectl` command that you are running. - -5. Now check if you can connect to the cluster and if its your newly created one by running: `kubectl get nodes` - -If you got the steps right above and see your nodes you can continue. - -First, install kubernetes dashboard: - -```bash -$ kubectl apply -f dashboard/ -$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml -``` - -Get your token on the command line: - -```bash -$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}') -``` - -It should print something like: - -```text -Name: admin-user-token-6gl6l -Namespace: kube-system -Labels: -Annotations: kubernetes.io/service-account.name=admin-user - kubernetes.io/service-account.uid=b16afba9-dfec-11e7-bbb9-901b0e532516 - -Type: kubernetes.io/service-account-token - -Data -==== -ca.crt: 1025 bytes -namespace: 11 bytes -token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTZnbDZsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMTZhZmJhOS1kZmVjLTExZTctYmJiOS05MDFiMGU1MzI1MTYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.M70CU3lbu3PP4OjhFms8PVL5pQKj-jj4RNSLA4YmQfTXpPUuxqXjiTf094_Rzr0fgN_IVX6gC4fiNUL5ynx9KU-lkPfk0HnX8scxfJNzypL039mpGt0bbe1IXKSIRaq_9VW59Xz-yBUhycYcKPO9RM2Qa1Ax29nqNVko4vLn1_1wPqJ6XSq3GYI8anTzV8Fku4jasUwjrws6Cn6_sPEGmL54sq5R4Z5afUtv-mItTmqZZdxnkRqcJLlg2Y8WbCPogErbsaCDJoABQ7ppaqHetwfM_0yMun6ABOQbIwwl8pspJhpplKwyo700OSpvTT9zlBsu-b35lzXGBRHzv5g_RA -``` - -Proxy localhost to the remote kubernetes dashboard: - -```bash -$ kubectl proxy -``` - -Grab the token from above and paste it into the login screen at [http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) ## Installation with kubernetes diff --git a/deployment/digital-ocean/README.md b/deployment/digital-ocean/README.md new file mode 100644 index 000000000..5431d6338 --- /dev/null +++ b/deployment/digital-ocean/README.md @@ -0,0 +1,75 @@ +# Digital Ocean + +As a start, read the [introduction into kubernetes](https://www.digitalocean.com/community/tutorials/an-introduction-to-kubernetes) by the folks at Digital Ocean. The following section should enable you to deploy Human Connection to your kubernetes cluster. + +## Connect to your local cluster + +1. Create a cluster at [Digital Ocean](https://www.digitalocean.com/). +2. Download the `***-kubeconfig.yaml` from the Web UI. +3. Move the file to the default location where kubectl expects it to be: `mv ***-kubeconfig.yaml ~/.kube/config`. Alternatively you can set the config on every command: `--kubeconfig ***-kubeconfig.yaml` +4. Now check if you can connect to the cluster and if its your newly created one by running: `kubectl get nodes` + +The output should look about like this: +``` +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +nifty-driscoll-uu1w Ready 69d v1.13.2 +nifty-driscoll-uuiw Ready 69d v1.13.2 +nifty-driscoll-uusn Ready 69d v1.13.2 +``` + +If you got the steps right above and see your nodes you can continue. + +## Install kubernetes dashboard + +The kubernetes dashboard is optional but very helpful for debugging. If you want to install it, you have to do so only **once** per cluster: + +```bash +$ kubectl apply -f dashboard/ +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml +``` + +### Login to your dashboard + +Proxy the remote kubernetes dashboard to localhost: + +```bash +$ kubectl proxy +``` + +Visit: + +[http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) + +You should see a login screen. + +To get your token for the dashboard you can run this command: + +```bash +$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}') +``` + +It should print something like: + +```text +Name: admin-user-token-6gl6l +Namespace: kube-system +Labels: +Annotations: kubernetes.io/service-account.name=admin-user + kubernetes.io/service-account.uid=b16afba9-dfec-11e7-bbb9-901b0e532516 + +Type: kubernetes.io/service-account-token + +Data +==== +ca.crt: 1025 bytes +namespace: 11 bytes +token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTZnbDZsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMTZhZmJhOS1kZmVjLTExZTctYmJiOS05MDFiMGU1MzI1MTYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.M70CU3lbu3PP4OjhFms8PVL5pQKj-jj4RNSLA4YmQfTXpPUuxqXjiTf094_Rzr0fgN_IVX6gC4fiNUL5ynx9KU-lkPfk0HnX8scxfJNzypL039mpGt0bbe1IXKSIRaq_9VW59Xz-yBUhycYcKPO9RM2Qa1Ax29nqNVko4vLn1_1wPqJ6XSq3GYI8anTzV8Fku4jasUwjrws6Cn6_sPEGmL54sq5R4Z5afUtv-mItTmqZZdxnkRqcJLlg2Y8WbCPogErbsaCDJoABQ7ppaqHetwfM_0yMun6ABOQbIwwl8pspJhpplKwyo700OSpvTT9zlBsu-b35lzXGBRHzv5g_RA +``` + +Grab the token from above and paste it into the [login screen](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) + +When you are logged in, you should see sth. like: +![Dashboard](./dashboard-screenshot.png) + +Feel free to save the login token from above in your password manager. Unlike the `kubeconfig` file, this token does not expire. diff --git a/deployment/digital-ocean/dashboard-screenshot.png b/deployment/digital-ocean/dashboard-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..6aefb5414d26ac3cb69153c0624456e47e4ba88f GIT binary patch literal 181783 zcmeFZX*|?@`!`-n3nyAkibRVRLMas4Qd*QH#@JP2GJ~;%j4gypqOz5>1(`83wz1Dl z2xTuB%P^E>ForPJvHU+%=k>eJ&g;DDe?Pb%-M=UE%E9ULL-GqbFqX@O?@y|8Ad-Tq*N|4@@4DHoYXJlhQp#0_bqx4AmB= zqfC;!4P#1krTW8JFUzCrTNDxP0|81CJ4u31dcr|QgR%~h+{ zZu-@Syk>J<#BZOt;mosDTBvBJ?JZ}2_rSFq(8cS2`AY7B0uOGL9E!*lur2w%m7GIS zPo7*yQnPZLXa!aKS7CL2e$Bn@fp_n&l`%?xD7AmTPJLqhh95_eb5OHcEWEVw`S*>! z)Vn(Iwd!t}VIlI;_Mi5Bu!x7x2MfiTxQM*3{js=vJgbA~2iy0h-}`=?Z+ik(1bF^_ zu$!e^zLl>(_t`i8k3V0yAG{E$+KB$~!Yg-Dm8A_ zc#jWV=&bC6s?|wYW^SY9+l46-U|lLohpwIFI~Tam=*;)sN{aAqK)Y{_)BX8r8*W?x zZ_#xu+wgr$fjhU}yO-)^f$0cqQR21oV>yX>mAI?YX#dPPUb&&LL!XY7yi? z_!;xTyFVV^xN#GEFi{G2`e)gCzXJ$DqHDkO52d;Rm23*4@1Qv6gz4aklsHrm!;;`& z)A`l%HfJu3IWylCECVU9P&v@CQ89dMs`%9EAJ=jd0uGd#WJ~|?m_M~tvmAUaTjW#s zL+V~?OX>$%6X9bn;|fM+$Cn?5Tb(5Y(M4*;i(e|URWL(-lmI&;3�f35BeD2-34X z5VF@N`LW~sF0Rs;Lf(g`dez0sDj}(dIn!-6PSYc;1?~(z85lmX^^IYrs3PJ<)dNU4 z)Snvhvz6GSb0;;9#=~8!B$g-c)N{wSl&3d5gf6@vk#bw~u&~07!1I(l`csr>iM%w& zffDibn|Y^)Pn$5`u0@&@#uHW2i=>1bCkh)Ud8hUbiJ!E4r8YOyNz%)GL#v4uWVBa? zo9C%iU1H4nA8~rMlyj@}^1OGRQ)kI+^jv}a(=7L=Tg*b6`Q^7qJ7Sebv!7Ib9mV-W z@;q?OA_Q|vrJSeoXhpxVD%pOs&tUE3Nlz!a@3*!|jHg(fEoWWUPWPL&qGj)1jLW(h zeehb6ibw89E4F6c3G5H$`JX#&&se23u73YE(B(5PG@mBKE|}@>u$1D1OQ;z=%1`yG z@Je8P6ry%)ft8gM8saR=x?S`fkk%Rs53Wz{8(QjNVp)9z<+RdMw=SIId?Daw78njI zM~j_aef6}h*{6H)urSrRFE&YOuA4aX@uBd_?YO79?Gxv=5fj327Xy}`)g0*TX2gA< zig$f`F10+p9`>Mcb}TCzhYTU#9v(49&Qb=J&*f}kOTV$nzFwMlcodtrSCemk+>@YS_dwRNs2Rqd8SQAPjbrfZcF-W?^9h)7<}3^YvO1KJ$gz^hdPMeBKgMVP}-(r;^vx zPB7`7W7(%RqE!V=omYyQ<+tO+Uh|gfKQdY(^n3VeP+kJG0DJB9D_Hq-9J;}q@lHlR*YTEPWOip>1fKz-dEe&_Ub&$ z_GEQ-><-)cBtx!j$4>?-s)uJSX`ZyEOoL}bf2gM3T2fCv4bkLVMlW2EHF_D9WQ=`d z)k@BnxqS)ykU91FB4?oxH{3$7Opi)(F*Yo%R?BewkiSAt^S-OPWg3%rao$pPyAi|D z)w@%g*?i*Tf|u^amH9)3hlv6Sm0oqSCi@y;hbc##gHzyxFR1TYBtCSfs$L1AAJD-S z!W2TvYKd|vi`veV_#p?K%lbJl~0?~ z!I(iO8ZFAY<#)8^gF$EhQBQ6+~Ob{OGShG2yN$f3c?;TD2h8xO5(D$@9-r@nPg4POgvl2PGu!v{G z4=EE1U#B65=^^*DntjX?^52@&#A7_Z9Swuzh~Q<%v1b$`5d(d!tgW9as;?e*6Gt&6 zN|=_k6%x5xr>U!-f4>Ha@&K}27~0pmQOt=s19ZKJnQigjDxa3=a4vO!5yP@Q_XfJz z=Mn#XY*FmH^5&h|mHl0J9^Fm#`Z}9nHQU=-Dmz|#Ot<)Di`BJ~{QBCP0}xznedd^V80^q!qmJD5fr~8f zT`GIM@tnc?GigPf^m7eswtUh!tkA@utRo%5D;B1NmwI0RctZ6`8@FBlU%w$R6^NHEeOBMuH}!v3L~( zX2gYQCgXl%q{i~irw8;4@K&YvH+PUSQW*WOjX#tT9$W){RG4A@R|mMs325BM0^53# z)oW+YNtSAj&kIR$}51yb6|=MK&-YQ(EK76jVd=u96iba&)WLA0uLg zCi#1@A~Ra3O3|~U9oQM)hn-3yio`O%%ota*gvl4+C>aV*yWMm1#;wNI4A$4gu7i;H z;$pTA47?vtg?WygFFR5HtgLUHFu-G`OJFbd$ZzU!FSZ;1*WaJH`;Hp z+j-E{I&X`od&WMWZ%IUI@r8}5FU_yD6z<$5T3oHub>%~}5uMdIqsDl>h5GH@9`cA+ zKTM1T9NVHd38hCbqA5bw&AO(2HWU96L^nL9)S*_{HmAF5fag5}e+VUI+3G@xGoj6O zIN)COl;K{sx=gh-N6atT&=x5HN=Ibh3#z?>X$?}HRF6e_F)g;Xt{7$ zK&ahq-|*ZH*@h=7*wvc$RY&+?hp)ajh{Qf9zWMQ`XS3FNv#0wnf5OJsL`tBTR9s%J zUEKn5c$zPNI*&kJsNE)SJsNCdHTC+bUhdbbaEndsWVhNw=}dXk?6*yMTEXi~GW3$l zB)_Wtl!yfSf~2EC8BGVtft#VEsun70eUi7YYbhg0VK~n?{*7t?gLH<9J#6&76=rd*@pn4>1=xl#*&ak7o^jYMvz25#VjRQSmuFj=8GV7wT3N zB3HJsM~G|W58GxaA5ISU8c3E;Vrq>;v_Hd7*_vh!pK`bJ#(EfRR%I1PrCayv+nAM) z>@356K6R^BV~NzTP~zEuSzP>Dj+v?Bfz6EJ6TA}*^4wt?Kgk`CBuDV3MiirwoqH{+AeaZ1Tmy0;K#4s zQ{CKd(TuH;_gH#I*f4y+KY>4AV@bJL&}*p;h3};a+>jcArp?A_u>4U&e#RegtCsFz z99AzdG#m>OtlQnA5_{4nI3k2tiX&)0P3JM_tCrovVembK3|JXiGdL4*__#Bo$AA4<`_ zKK_d~{gJq8=X1p|e#F>A>m+1WO|_p~TMmy>w!7Ye#{PsPxUU(fcvvgE_Mlr)QOF%9dvdojh2z zZ+Wn|LLK}Qm=>yAdyC`S5W5!n$D1VJkf)x+GpyN3M8agXO;6>Ff5jT*F34Ez(11<`GnYU%?GTd%IdgiD3`%ym_MA&3OkxK4W* zFLojx+2osF+F{`d#mrn6n^YjN^;r4YKPcDMY3 z>zIhu-s3|~y_l3p_kf@mS0=Tskb`3@Ggd+ZXq1NhhmgAaWaUM*y`RX$)QeyA$Mq9x zEZg+VWY--RzGcG_T&+Q$_7A9O;Cz{-&ZjUfq!v6sJdaTIZ6#K(85298g+%Z!;O7qc z8N1?Qq1Ea%;v+J9BA)~^$f~Rq+hzD!$M-Rntoe%NTE!wKJ5aX2m?Aeq*Uq}cl#B9P z9>hnP^*k~}@~i_2uFeCG^I?taC6>Z0B%BO7ikC-fSqwIA@!z`5eYF zq?#|VkFAmId@w&*-LJ&Tv%_u~&WDCgHEA8f3=Kk-C(^b`Nb>M+Dvn5mk4$^y(%yCMNfjyg2G)Z-ip(kV3AZ7j$N~d(pi3 z;AFCwvp&-~LoQ3!*~UJjXzCi&5Yv3+%fQvRjyI}~L+!BU1n)L z7{7d_fVe!mn4cNbkVHXD=(4%7|c@s&VQzRb|#`LK0SMd|YmhITKJ8-R*>9PQJm&h#`Nobal&y+5Bb4nf(d9f@hXLCC&Q4+Z5Dy zXpD7MT_bQjDHqel)MS8AsS&MRdvZ-7WqeSGPUxeobtDx@i!i74eGf}xi;vxI8GGn8 zo^^A*$w9{kXFdJ`Z5h|;IaGLmafJpl_y(!UNdRM|D0MPWgupz2{iXB*#%5Lp{?7mSM7ci=!mkOk4%nqnnpyz!*;rzUvLi< z6i3wT^2rjq^CS2CSIDNR#f2;0sMeKiy5|%)n(w^anN&(+d|1K`ErC>MM=JDS#xdXE zQx~UBba|*jd$&J)QpIQXYHv%fsJJ{PYb4bDq6I(!9Q$I$sg)j?PG5M;?GC=Mwb3F- zn?jFof2guv3{_9B@UAf0cUEi3QVIPm-t<17ckh=^}?0f zh!6;}_A}aZutCTvbEisNA|JZ-*wR**A$Hy4>ZJAdK4Ou>CFf3&4h&ZXez$(VGC!nPJU@V4})g4VqwW9Q=v2?C_^v) zNO@S18k+qrrbNQ`n^rpuw%zCBS&?~0*;J#d`a@_8&L7`w>J&e_jZNl^m6AJ)L7;w( zAd&b{x7i{pfMY85*9>?RK#6faKr;DWYI+9I&&n9#melH&43lc(f^zC1i5BV2^L`UN1{X3e__`YxHw$5p z%;`fU2Dx!};WK`;atXe|M@%X_BSc^9bZ(|gQ`^-3C7acMpvOIB@11M9birmy5kEbC zUoKiTcL6`ra36nVwkeI~i|u(*#oys%^+aJi>4-Ba;8M@N6E6`#@>*@C2~Vifa|}vh zi3!EQ>8_&ir1WS*?S>$0-j6DA%B=(Oh+~Vj0uWCm|IW@*)%-nB<-u8ecV0DojSL6V z()-P6>9G6R@g)>qk@u@;WKm_VUFtH6BZ@&l({gkG5CBult4%MavqUB6&<})CX0Xg9 zXa`3WHBIw~#V40)Xc5$nx9ty;zc1uRY2GwXNTc}$EKGo1`n=awY=aY|QP4WOh3dED z{f&J2+t_S-khgaRA?{PbmLNvN2LnDU`RA7CxXw?19)ocwTraX(qw1xmu^d1!}W z+&w%AnA?i-Aw`IotYco_o2S#qcuU*|>vQ`Bmh99^f-BytAI<71oSSLiO0J7H9G>8zmCV09XYxAb5He9T;8EA*{aidSAq1B*m^5Hxod@fL4K{4+{wyDoud^&qX?TH4P!u7aq#9UUBgzS?-Cn@A zQQK>5GDzc57%`cmROLn$f%DiQ=$#IV#ay#W=9%3A&UhUVxx&;* zwdqZl`2&dZVa3~4f6)<#v9Nv{szdM|?k=(4^3P!wrb3QI8f2)J?C;QmRN9m#m#$Z! zSVKvCX*N44>T36>9q*cKo^JC!#sz1C4o1Fnr0XUSrn5bO69qC)`c`h%IQqNIJKqN2F}UCAf3sdKdm>#ktta zrc97{KQ1{|&%b(Y?%t^GR4+`OJl@YUCKHl}@q(e+zqlWUXh~B(Jx+TTZ0+%kb>eEV zQ#ZLq-KFX_rn>bMrg#kdth9Y3zM%@wk8#7)TRF59%_2N%6jVEiZYHkRj(|uO3CvK$M3 z_t?u=VOjzeFn+}%ZVNe4WkYaY&<;`d$g0uZui7|#S0bJ=g^A~U;&dsQ+7hL&JuVon zxJ7_$Y?*YbRi%g0B{7rK-}p{k1NeMq!C8u3fxWG{MSp3@fsT%qT{C4FiUTIioUV2O za?BkaZIRdNU#Sw&nd1#G$cLVKUF4djg$9U1A}R~@Y^bZRc`5+%M5AADhT1fHoXzj< zl2)<9T;R>ug{;!JVjpEL1t$^w1^op7X5Z#Hu(RHMf^a?uMimZT|Q0jSNCY!)1 z*_ruJ=4;FQo$L{~^pmm{@!8B{-ttJJPwPpwkqSa~S17Wb_D234*HxCUB-sHBv&N*e zX~mJ)G)8!mJUl8hB)~Y*mxEEytHnL;(IQmLs#!e7EHGfON&$R3)bo@)hK-K zZ+2YxVv4J!tAu#~>tbPWGX0CWvP?5{2dUzNYmf6JToB22g4Lpvl08{*>fKlM{i3&J zBi^{dkg&58JH7C@#!de0-25qTMUYptDr+>B`4ppUaWA%b_-!BVy6R{9Wqg3f8q~Y6 zyhv|8`W||ka`F(4uN2f&JO?N4kJWb@x4rZ!&73re=|aG9s0_#XS$?53ODXrKomHc6 zA54(b@_79Hwo1J*C&S-~+HC20A5$ygaW&}BmSvBRo(XJQ^A&Hu%Io!{m}B+96mDkA zdqMI_koB`ckM>gkC3M0`_jFl+hrq;TM%CEO+Bg7<#Ky-{Yjw17&mgO0d|R8K_k97r z$B{JEzLvfGMvaKM6M5q61|#GUNNY^fF2+=>BcYR|Z$tKANknrk3D5RxWH-|He?5aww?h?rN3&ZDNCB`N0DDW*EU zIUjfV%j0CSOQy(7J}5LZ37CUmYuWQo@tjl7cFtI=M_V#xW*i@Ha~XL%e9SMz9wT)k zDpc`7rx9!vQKL%deN{-wRmn2i9k(a|5Dzgo_rch(S~-h!-e(ljR^Fx)53|*_OP(`tW6q6ECU7O1J+iz`(IgtU_Ur zF4k#aA)l|Wey?TKgs;ddn{NhgXw97q&j+U;boA=Bbn?EDWs8r|4)H}-3d9s+Ph0c_ zX?;+Fm*W-Y>YwZjb^jL^pov0&??5suJpW8T_Z%9EakUO3@p{;piTpJxlibw6MM=wi z>cxRhzPAZn6hspj?)RWP&()_7JgOZU8!Z@@E0xgG&*9KbneN&VVOHUzM$Bz>%Jr5) zXrh7?EwU`dBu&iumInl{TwL2s5+EA*&ph?;`Qza<+UkJnt3u?-tQX|r+3EIHUCswX zCTqRd9b#xut>i~o6@jW$$s227*~7m2hHZ^|q^d0YW$PjiU%22imiWa?PS`pjSLX9! ztFox1uSzRfTOWk6-0^SOOPPmeu>2>CR!F{vZ50deo}a}fa7+Rlk5Ub`t1=7o^KR*@ zrr$y)hklph&!ta~x|;!P_H zhJ2m=dQ;?K7rSpZMI+rEW8rI<%4s{KZ^J@drqER_DI(4di+_N_J-C7A@iunt@eMvx zFGJk5+O?@aRqi(kALmxj3SMQc=iY>B;Q0+`V&?(Nb#t|Fhngb9ae_^Kqt}kEoG|#| z!%%g_F1w(VQkN6kM?Pa8k>*c=0%9u<3YZIxm1}KZwMB5p%uy`MYMo1+SJxK>ry+~h zWX9ZG8ACr@`FgrhrZd))mDtw)hsg0zu?*$r-8H3xF^&2Lbg}P-Ik0Qfy?3cF<{~2{ zVDVYuA3xQ^R|U3n`4c2HcojF;)Y@vWYyP#Wp5pP*R#DxOz2~qc6$WbL&mMc|*@yZ9 zKd8u@=D4{Nxx86Kr9J5f3iy581FXs69;WylaCiSBq8&TFE zDNmrDK6z`WAu*eTHj#fy=Sbk*i3}xY&`P#@bQm)6& z?8B8!+OAKw@OK#b)milH{(yHH$G?UV#9eizdJxaJDHM0V+d!83zO=6MXLxr=7@SF? zpDp!!AoPXzmN1iB)86M5J=7IKyHpd6Fg|GY{*l|cM_vSyJ!{xn1lvXGH{G1?J&1Q@2*|^r-T&X@o z)+Fdp3VwL8okGZ2bP-4L6KH5Rh=E z?E7~9ZQLbmxV)6aX#PFlb>&;i+DX}=CH=hq4?q6wT@m00J?vR^SmL|j{_EqOCQzjf zUBc-<3i(Fhtd0`94}X^oNptYXS00Rb<)7@JfeZMWe~g~1z?JsEAudPdk>B;$@9#M( z2lm{jysGZpzn8`>MHqlZ^C*WL`o8CP(O@l?oRq`AU)(*Dz^ZT&T7eVorN=`3554-= z@-$QLY}~hRpD7X1%GZuD75wvHxojCLPL<>LgS~DnDJ4}!%`($&7kkBN%KiDY&u=k! zZ;^g!hwi0I4_`YR75Sl|*Y&`91`85-|Hk6^8F~gg(#+?7$7;{9h3Cr$fw1dmMf%Sl zbM|eDd;YwnCBoGF81nZlte57J&VVSQY6T^1p3#|BSMdI^yq`4Yf6jt?qW5+O7OjKH zrza|hDkY}w8VAR zl(4Ly#PFY%eEkv-g=(e5f&V?s|2@pVi`D;$WWIlzI-76+Gq?d+iHz-k?@{z8{qgV5 z{ELnG9#ZZuZ3D$>YG3{n6Hf11-NT}A{-;Jx?0R7zVjbR`E7D4@|5x6I?I>@@PX?FUsty83obIYKkYfLFtVbr(fRd{D#T-+3t42mX* z0ZRyrvn12u3A5^Y*>i=KeMt5(v{&V8>;v+aA}*}pjqGe{a*DmlP?EjYDkEF~Wg0mx z7A9E5mo*kFTljL`p0&ey1Ye&4{1VUlYiF~PIqsY~td)oB6R5P&d*JqK$&23Mlvqr} zfl~%mPqy@pP9A`Dj%pNi*4KP$)KKrk+ZFgW;GZx^q;ZGo@D5O;b{ZsDupaD|Sz_uI zjSSo$>(qs1bau?j=__v!2(Xi}!9;oNDympEzgXqs=zBQ(;xBD>;Cb%l0)hKf@qYAC zXxrfrV^S4l|F6LgUxuCJaRvHt6rFWatKGXU9P7lT(?wXwbUtq7x6qhfoLEs``D!A? z0^F#McjL;mhDs9-lC7%zK}Gp8yS02O&Z%&XOf@GjfHLQr*FERk=^m8ol~ET0>6bn@ zwlX5BI`eH0b?Nh#9n$xiyPjN??=dJ(siG}BvRj`25=8eSp&SZLLP`!Uaj2k z+ia0_KJoGn%c{Dh6@%Oct0Eu%8zEReGl`|ENhEdGS5|;CXXR~wh%?8$?AeAyE+TD; z3d)4woMMC}ltooNUT$4N4#MenIH;{7WQTmnBrAB{<-aczEq6mi?TMpCV^8d^@0b+c z+cPa7S;ud7L8O}rsy<@@4v+%B&e&YQ8|o}Iy5iVtfdssWdadzg^7uCk>3DM!FF%)% zCGzdk?he@xjSh)JJqJwT387W2W&XPMc(a62JrQ*-7WtpD{g>~!aq7OIh=*yRDQBx* zpH`Y7*DjYI8@G1WHA&;ez34mIRx*F1tztgJRcvtwd3|xMB@t!aCylfysqNf?zvvZ& z<wH~I(m!xb`uxThcwF=ln87<^#&W=gFpFZ$ZzD;^vtIWF@>`6 zcpn+gOv8`LTZc2D*!?b2BD$9*i)FbLrFsSQ%$5kI;c6VDe6MEp&TXBw2KAHd5kU9K zpcI-(w23anHm0%4?4?HOn~|)_;X{whBI*Txxy@C9@muZz-c=DOU5)fSpaaeXV78XR z*y=t|Z{)2TkJ%St-MdkAlaB~v8AlQIM8}xAYMORjG_fh5-BFnoMSU{=L8i9j0`bAv zggqJpT%28$z#pFC5*gv9#mpEh>mrI-(d^rFPPU5Ih{+9g z_slw7DSe~%F(Z+1(ch_J9VMAjIGTzz-Fw8gV6A)_W<>&D?@f6(ju`h29#AR>dI$zNzp_7iHM z)gg&TfU-8ab*rC`EkMj~Q$t=yAcQAca{^?$$uvdy61fV{=knW9XX7g$v}ZGGX4C3UB*jIuav~) z*decgf)XG({ajZ32k*aW2X`a>Q)e0zKhnNEPtqd8Xyk z=y$g1avEYa&a)l>T-8dYdA^{swvLrB-AO#8iX-wrg{UC!@}p;G{iYYxopjB{m86!4 zpDX6&U{%(Y2SPSsZx z9wEU%xyR(oI8{DIt+e#?>Vmb{pE00+3VHTi>aCFo z_(Szo5uls;G14z5I5HZOmW8;qod1xjpT7eUQu?K5ThxcFN^WJ=d2RIGYnu?y`#TYq z%+|g7_1*n1!Sw$A8xm}V-@OIM^B&!+6?Zp+zlf26J(<*@S|1hr?c(0@`_6R0uStCc zT%xnhb(gvP{lDaW|8h!y`R+_A(5^>HtL>w71nQ{nq3C-);x|%i{lYYg+QN zzumIrCOfVrEx2|GQ09Jf5t2g#yM7}O;HqA7v7B0^hr_=K(7^exC3UU&`-_&*qDU0tBuZi zs;BY+PhLRiUc5LN4QeN3Kr`-CpYMNq@Ri+feq=K?s!%^g-Q>k-UgXD@ zA%K9_Sp9H^EkKHM+ zy~dpZ=`WJBvdl37lz7VLI2-?tT>*=(KK+l+WqD5xZicYa)6*BeaVs7l4`jQ1Io-2E zGet%b@wvT^N9W=8YoI!to7bo!qc2iSnW}5KFk%0TZEeconcv0+)&I|pC;I-7Nbm9) zCk<2kI8B{OHt69r z&ngJ4-7e0mpe8Le`C};|74`yG(HAVF-oE}FUu^!{a}s`^Yvbg{EtIe?$I}y>bAi8D zU}So*^);4mh*-J=roDb0e|lHQ2ejTM_Wu3*J2Nwq5!{xT{gLuq9 zFRXKS3AM5nxtl~ucmZ4Z6a_PuQJ zH_866R}ZI zQOiD}r``x1AbL`UUWR8Gyf|&E$K4?j(8~L31boC7o8IZN#b``rXaX@QHmzUtp&u<<>4qe%|R9r%TxZ~L3 zG`ko|L_k+gdEZUmUFOH}S%p3RQ{!BUjq1poSBo6`1B;Cda+Ci;*W{BnqW;AN z_$LQ*PdLz*A1xwl-oIui5lI5<%@(y=av4ETq6yWzcqQ#6OvLcW%h(3M5>kK)pgMA^ zY{uJdBfCNItv6U`G}&h?2a7MM6+M14>YBc=u*`=2LlMyyG2UlN-zJ`PUmSmp>4P{T z$|kDCmV0DOvThn>=uI=&(whX#)v#M^0*(uB2v``-Fez{}!y3K{&1B_aCKVe`GImOk z5o@&wn7fguCeKSe+U3XonC@P#4fr@RChbEXU*Rr2Q&`SB-_!=EK$tzZKff_dXUN=W zof0{0+ADMSSm8?(tOGHTx|oj#JXTKc?431&-<$@5U$~gH)~*-76$Q<_Qk7!JEo(my z`r4cd`%=`XY?6&VVv&*%urwVf^h_UE>zfp*c+jNz&w2se{|#-)o*?UnVz4LmQ-;0f zc*~QNKa$!qk9n*Y5WOL5QBc2$Ep2I7)gu*Wc^zisM+_hb?dFvLZEG{Do9=GhItp5o zX5fC(?S@5eW5o<}ivQARh=_IeZtc)H$#YsC0Kcl&rl;5)UfTCab#a=Cw_?gn2%h@( zmQ8IiX504(_N8rDlu2ziVl_j%G4j?!UMyT8eUidZ(Y9Ejmv|sm`=hjdhcbl@B!%Om zjIUw+Q4k)P&jP1AxU_Cyei;Ea(8t)#Ye@?)-+u{{NFI~uEHQ(;lklH=L3w0;0XwO* z9q{saiF_{-6p@3$^s7VsQhe8dJl}*!8-I%_Hc!C>YipZAhoz&aCwmcAILr;+dM*zP zT2tO!Jg~48Jo!^C=xOM$9BF0@6Bis?7?M!ZBKp7En@y!W^P+6MX9`t#(u}#y$8-Z7 zttjbkVaJ2L1{=1Tf5dubICbVvywC@R{}Oys+VKz+6D@>pm4jY;fN_VcJ7np-G~I>^ zSXowoXqf$K+DI(EzKe)*_^Z< zTi!RAI|7*7&~7YW8!)PKDa?u99Ld+MwwOi%@ddZApligtRJhJ2w&ys#8#c9WZ&9du z39}QkfjaJYlLl8)2Axq&0)F$_DYCAvuET8>dG-;cr5T!M{n47arYE5NN$}8UtIsh1 zK}1KEXZ^y?WnU2DYQAgIU_N;FP7#~eIZ{PdZ< z@rH*I8uZx1W?$D>`ekZgL%PTjDh!DG)yK=G7DeQQ?ML{`n(aM871PK*Z ze%{(CyE%MlC?snZnwBjcdMpxP$w&feP$TlQo66L7CCB)ctme3L$AdgSsrYy;Ele+u zmvbrAEoe(fak*I)TJL66j=2W%jYVK*GgF1{J+wHhCP=B(FlYGEmS4m=Zf7NXg6Wn3 zh&;P0@SbqoNFoF6X`;jq)z#Gp12T|^r`sazV|vGfhz_k7Q^!-8%SgEauH&5z?CfV;q8 zAujXThi{V|sWcDysMRc%?>p9A>J`fO#Rc>=Wx7{`NDCzq#I&>tAul`$Z(MKxUrh^$ z%|`)dQawh`b6Y!Ud3AnrfNT!ijjy0J&s6apfnY|C@>Z04lb*jm-2t-Fxjd8y*>k8l zEdKP@WHq0KV_eJ*)8IxzPy5Pv5yN4v**ssb-Vj79+IXxnkQr*GHk3cB)MDBvDK7&b z!r5ffCZsb+Z9qU)y1jblXQmLU5)bRO2&dR&w4T8{7Qew|%B`W^ zrmw9AOb_S^CM~BirxmpSh$RM=da}%ciJJ8lyJ2-qU7b!*2j@=x){>!L2;zzk!*k*> z>Q+Q!>d7yB54es61dvl-aylu@L>B>^@`jhe59n#SaSOFQp#owH8o(Tt12av$>W}#< z4r0IIOJ7txMqX>^$29PL@UVBee*L;**NLoO$&)9j%@3plM#w4190*4J#IA>=^XY4! zbD`s<9a|rqytN3lNc}ie%r40YJ7NN@!-V7pb-sSRZ-u=ifS$GlX4DZHh`neTQLlwU zATUBMtxwF{soo?I0?)9bp?+PjFKk^=3HivE1P#H?ABv{B3m;^Pa;+U>9`WWZaTnHx zLgqRyV}UGb8#Vb2Xvh}qX;T6Wl1GQ_E;>yIePLtNiz@ockCndqS12Z48e%Id7CfM3D(z&-qh z?eve9@lTWRlG}%HQTdU>Z^6lbE85@0`R_m8yTZ+GyHBM5JnZ5hlsHEQq^+esO|`#E zTO}`YIY_tq;+1|8XMBIbJ3IhTM#t1-eup{*9^?*z`4nN>`QIa#A4~qI6ZBGm12AGCHnxOj)&Y0^Z{}+&_}7->my2y{ z1o@Zm|ICW~drJOxnO|7i_`UGhx|Po|?t4ZfN>aT*=4Fz9(~tx5n8Qbpe&pZ69$Sfc zcWiUJ&1&A#g*=d7fgZ?kAs_dz+VFs$6vlrKTEW|RwKToMPg~n1avN&Bb@gt@fn^um z{8*vB_)S}Ns{RTl0vE49qdoU%xg5dDdvC zm~*yQk6#;zw5|(?9?u4Dur2hD9oPG6B4-K3i<5Or%%G#bb1pv9tuVjdw6-3%I@#Fh z=zfV^z_X=Pf(CY+PK2oERD-ewx0%D13qZAI8r5*oDaz7Jg81#;K+oB3FAG*Daj8#C z$uXOYJnX*|-y3|~iZeq)&AK<6lF&r8x!zzU&?f@iYdjY`@RyyKm{^|Rw!^o{o9Dj= zfYY#z&%4d%=4$+xreQ1QBrkvnWx)v6RI(o@=o{y)=%l8mgi2;+=5lgRi3b+Aj_!a} zNB?eDfA&)|HkxMPxn+CKKA6iod(!68Iuf5W#GLNlq&Cy2I^JHsULtY#5cEjerXsKq z&ks^@?LoZ!qt#-le1Pt>=xHpm*&v@A1B0o?gQ?3)9L^Ce=)R|dc2Aj^Y&W5z7fgPb`<8z?gm32U8liae3tq#Ys+uyuOn?~xDsg(fM%#1zUkQUO&g5Ix zRLFXvU5)_ACkbJR_xbFJInRTYj8}}0WThZ)3S69n97f{J!|3DOtgd|GtccYOuP)ax zp;$%zBxn`_-hB{(^6PZSFq<9g_8U^W`gWDia9Z5r^o|Xqx|%kpn#WtJBgB@}-t;JP zsrr8x;D4jdZv#Z;K)~Eps*U`Ww?_HjUmn5YtAfed~+Qs(XwXbB!&n(dhl5ShGxAo(Upw(ki&~DOt@=kp=pD{i05}emx^&mironB_G0yxZo2IHx4-PoWU$^0I=fK zzyZ-wp~muDWmwHXUEFLW*3Fa<005aK6>xv2#6Zh2A^o$#J&+GTzgc;84^-X&sR`eJ z9#}VJS4i=5&QpE$VJzsbw8V}mznN>ETSvYHYzrfXwfnv!hw=Qt#Mm#t!!m) zi%zl=|All!wef&v<7=iNFON#F`D}1cAyEa0)>=?W`WkG#I@2TX^ygh_=s9Uxf^*4- z!pn_%&DhvFl>p^i2cuebZoU!E!vsMUEz&MRKx6ZpfWwx}ScFYd94*u6K!8a$CaM)< zM(_c4Wl04b?nP7C7Y8ms7oM>;YNi3S@elY$=6!CjA(J`>OpdQXRZUxZ|uXF%GM?EZ*gEYK;56*Y+|QK1!FuX>;^3H2aI`%!@PV`m#d z3i<5vwy-gLCIQBQ*xnP{RSJ#O@~73o2s-vuGoVw)5P|6pqBirPJ@oKFRuP~Y*27~C zEkkr?AuN)Tz@|m{uAhq#9SF9DZj|}MME$y7WT7u%V9!R)SlrH&Zm&@&UjVHy zq{6hAJK%}e0Nzcc;w)$)Hon&?Lk02xlwJp`S#F~?@Oc9~)58aVLcXm4!u9aGqvF4K zd&M8I1NU|sv{+9?W-ge(`~5)+uevYj8Im-O#D1E|so6CJE#Px&rJKFDb>+$vp&O9K z5#mS#&IP8e0a|P@Paq>0**t1nhQ0*2&I$(5?XIwzslkS+7de<9#e92SFt(8kp*0;G z>!DBxckCsz42xqYeM1SBlVWNS(w*Q z$;6mc*!TnhK%BCzCw;%M|y@FrmWDdv)e!PYk67kJic%E)klc&0N>g z4q*GmXXR+iz$uXO4cK5^Oaj`9D}ckw*I>YQXXDL4kD|mD>{+6@5!2TSDk!rU5eeL6 z%8_e9+=|TlV#J4?cgg5W48c0B)wzEdi~9nrJJ!=(op;?DT+JYA^{QW)k+8Mb)rT5( z*PJ3wZz}4Lrq7 zL*3ozn*!Up2}?H#KJeG`_?rWa%Htjn^F!otn=~KA2pmT6tyK8Vp~_otwK2+9d~_|M26?5OZv6I>Fe%EWIR^ zlhwC?$)$n+ihOl0vHTTU7vlXBN3jjSIqm$eCRsh@e4Hp7kPZS1u??bTz6l zvsM-+)znwoYyuRme82#s0`xRO#-bogJYaFc6R3eXz=zyM{fhpa()v%E$N?>tVp!Ft zlFjee2PO)lxjqfVq~EkyVG{oF*Ukbz|B4$MF2ilBALcw)MB3jjyYO^Bm}sK{Y6;`M z+Y^m~Q`<(v<;mGH_@WA~3kQfJ@JW~sFuBhOR6f#udO=Lb-tBcn_?^rqb#AEqtx~9I z(&W^{yR9ZLV4_uqlgzyGC~&mG!Yr*~9;E#DnYz4fF$4&5ch;owma zClvzcktV|%gIUm8Szcm+;7l*Tu_M<`{f==v2gKw9ZydAMlR$(>1l6`qFkH^*-M!78 zqd7I{98g?Mp@M1_aBi=Jg0aS#UnP7-GBZXWpW8J7dE42P^$3hQZ3jO&a2>aad^z*K zYtubZU`O9{Nbi3DnpXOCnrQ?Z4Id`1a1_*U3IMz>Cq5j+FPBYOUZ~@PNZ&Xo?h(x$ z$ub)iRY3Ma4>@AcO`D=^pPbz!wf0|-6{rSL}RNeZ|mkz{QTna>u(@9H{N+|lM%mt`8 z2FxaWcYmfqxySTNMaWX$%51<291O@Latp@b%478uKv?AioRJe~QzGwyI^8YxVA=!d zv#_YBh|S==hCf{=Vtu>(^Zo^HD5~zaa94m^bGiX0ZYdF;oA9~<_vfS!XFKmFrgMi2GR&x=6$l!656T$gEZBQ}G%B9W!ZlQIb zaTdy?q{1>T*lTev?9rFXFwQJxoXq+E2z&2vs^33;+$Sifke&9Fh?yB70|7 z_A!rr;IKsJ;ia%UrsBhUq?-p`d;5o{v}fujY2Q*Xmt|haNxC{}Ul% zh1hkaI|0L29=;)X!G=+PI-DVU-U`fGAR`c>BGvFsW5vb{QP7X(YehPx2e1jxdM zB&|t4-}Cnd9D!Ui2heq9b^reTTJ-x767DRLqU@owpzFO+n(TIe8b!fFEnJhi;0dT0 z1V%w6F=;}fV%HU&Zf<7(0b(-8j*BW1J+VBxmLNkd`|?F*l2{AZ;-+o&iZJu~VkNOJ z^)q07Ml_0a7Vv)p#sloAl317eC4lHvsgc~>kMd2+)I33J_e#Ns&h@S|apxla57(jE zKa@WxftfOwu4@`VF%Sy>?60-j{>mDtdYZOFA~H7Bh-ES8I%M_l19GFqU?8ZwQhw(|qx? zoBmNlUaMohH*PJzD9sPRX8rsT=-oE3k8dASSm{C|c|aAOk-DBotMtpJgnE(W&X~RA zYQN^EXX#{&)a$kTi}_#(7;MYA@Cfh>Zl;*#r*B!35VwI!DvOkcX~{ip68{EbA5aC{ zBbZ-$Iclda!!4*^VVeTY{SC-8Pj$@!FbVhaO37Ogm8wFt2sdCG`TYioji_4|BkqkE zEH8vkfKAT(xfx8&eHcgB8hc}jw@~UyddV+Z!U;OyY68g8$~XvDiMxdWgJmY)3o0P` zo+aD9(^h1zDd_n>?K=4e@cDdR=W)EHCeDG@c(=Wuaiee3JQwk`%C^LO_s;h zb%40GjY=L7SbAP|MI-WE`p_cXt)a*2>FLty*~Bi?m+}=f*of?@sm`W9TTPrQ0cF+h zfU-Od^jxBqyVZO*AO)+_>_auQVD&;ahs~AIp~oR+k|Cm-VwNI>j`^0FhVS!Cg{Wfl zwm$fn5q74ti3*4aVg@R|CVI{7?EeTjF~>^Of8ZJvK8)-Zm%qFzU&c^^`YzkB&&@)_ z#r3`3=iNK`4NcYe&UYV?(;G>b_z<%O`jF7~-c1y==)E^`r>lUz)-Q7Z^^LCNw(~6& z;^OvQww)%}8tlRO_?L+bLBIwj&WAMyPH?8V_XeB7zm}>#iuUJjA15V#k{Nd|)q}54 zXy_M63qJG=+lIWv{2T*Fn+29Vr^AUpv%vKT$}2p-DV>ZG#n=7i0szr@%00+Nj}=ka z1VP7lUO8X$)c0HIM1kJ_`y<75PJZ*{i(w%P3i%5t%^v!POq@Z-D zya{=qEhE@t7*oInS|12lf`2LHTYqJd24kSjPWS*4GrWS7rC{;LbNtBYb^g-IG``pC zfZk9*WtwH%8H?EDzs;8o>TzLw7QI>M@V6#&5}Jr@KrX3BLrC!Fy~azLY)`2DZW9VO z)in|(HZT2_fQ(NoslDE(CKs#+zbCLIl=^jrb zE}PZH96OR$sjVzQ$m$YEzszOJM}Ji<@oZvx*p?=H$kD$vY0zzSO<=Q2!*IH^;^KW0 zjH{-DxgQqkR9ef&OC~MdJ=O_$x_kXz%WUq-t}bzNQyZ4I`LVUD4U zk(RZ{e_ULy>KhuIdGp~#Tng{k?o^423vK)5XPI0K9=*S*eiK11pnG#^fFBua5dza5 z=1@tsUMdIEB?kxJXET`g7aUkm0EUZ3>@V&ov0GlW7S&)bH$6<7yk|){6qc+t%DK_HHNkv?wJB zmz72Dp%Zc|jjtfZcoi(xS@9=60M124&jjSRf7Um(=uKR770U5)TH0*#F;^Xwn*TQu z^Clt)@Rlj2Ko^L)o*eC%Kj6I9L!X?aBd|sa2^_qfe{mg?836}%;=*^C@{ZXI2)d0A z0I)Lz@$L8bhQ47<4)knK_S!e@DD)+V&8w|eO@Cy`i0j=hZR5d5@O;qVZJr%`Zh9gn z%Zs3drilpW#W}jz3o9%_vG@a9{u=^6IhQ8+)kI2F=hQl@W=uEvO|SI{R_6wc&+M=x z$GgToMRPQX%kXi5rlD7lDL_|8muR*dd}!h>jO%(ZU1#P zAF^ARHwCr3=S6+bkOU`S9C<&wZkW-Bwdvy-TrBgLYT5hq=@`uThbsG`R3=#Ib_ z!)XOLMngf-lK?(+48TocYQ9YM@unx$p*|f6YVm2+LP%Q1b}qwmpTG&ggd4#a4Mss& zVGrP+BYjSrd9crHMG3@7s?`kkCiFKAyu)6aeO!XQO@6h+*tprNC~yRV7*xBx@C(B) zj#rmFg7k8x=G=7*SGK<{2`E&)p+LV)aY`&qc)&+Ltn;#9r!BGLFq;|fj#!q1OK#Mf zei6I_g%(FA=i>;6Vvn%-)N$qvOb>ML5y(jW?@#?(f~&|dj17pUluFv4-gxXYONB1* z_;xEb&Fi>QcM)Q1<=k}qdXtSffQ&gDpmbD=*H_ws)6&hNnlFo+JK^Z6<8o9`=$h-7T7TA5+jJ`S|ndwM91{S z04w11F(XFxi}j?(4kOKYE0!dt_wbhKO}3XHhq0MYiHR}kyAX~4(-oS4~Fknw8N0FVVI4UbHWaao}w1IA_BSh;BL%U#fX)h^LBvUmclvT)^rdO zHFRi-pF?TRO{(H?K#oiCZy%HgMI=TjBTg-rcVX{EYL6uqJrFU%Rfu!PlA%ZoaLd;` zp@YWsLY7oOGt9qAgKHWJx)a3g&0;ax25)9fsh)t4rjRO!JSWC5hDMgWc(TUTUc36$ zbX@OuYLY5{Sr#JbB+%zmj8u^kho=WkVPRVxQ2naeRgQDM2RusJTp1gH2qzq^Jl zw+dn+Vj^w1Loq+a+?zmd@6Js`tE&yDrD!#g4N--A#836&>M+m8o;MF;#i@#*W0|Cn zpGXLc$|&ILBtqo4Xq_5u7uZF^? z(1*t$9A?qNd?a&G7Pw$hI=kP;+)PDtJ!;F6@ZX>$VMw)R4$DH!^D9KR5$wGltb zrW>`QfPhmVK5ZOaDV`Z*H%oj+^86PC3JzuyM;7 zA*})tux=2FAnZ3JO^(I@&X0n90KOu8*Ex9p3<;g)LK~mj=Eli&t;{$ptZ=IfvDF~? zT*>Sg>;Zri+b-SK)k(eUvMqG>-_H_rF%on1c#}VjE3=kntw}H=DkCnECLOLd`q2VW zO&r4=r}HA{wXoyTVA;BNl;t5EJzJxucwZPMThwUr5i#kGJ5$$wL_yhot-G{$Q4JWr zRA+#hj$j0ZD2Rwrnjjo_!~Z0@i_KKOv#I4t&TMvn)>zhsj$xYdZ*&C^gc9{BbkHQr z9H1H>7@uhNv_;ciX`N4A2-MnmJsm@l9}|7ZPFAs21>r8L1qF@|Tw4Mk=ER1F7X4gH z7U;W+xL^keww>5PZM!nOxTyD3QNMPsGj-i+po)GFd%c9U8f=^fIieLFtjF{qw+-vN z9N_c1%oUbDj*Z?MkB&*9%AraFC5?)@kDslccT!b~$?`2-0PdePsb@&BxC1`iCO8WC zA#jkn*nAYow6d!^a!UHVA1?l@g^oDWL+pg`{H5=1D95mIcqM=fy0jNMD-i|)(}`uC zY_hz?D%(12+tOlP2i}v#d$(_!8_x=J>VEjIDNK2EvP%srdbqyOMV2f0R53I@F@xPIYrpbo) z{AeX0c2t$K&(Z$ZOY;0m0!#b= zg{<^aaX{ij601W#e z07B?N88@xa=6+9GQ&j|Xb=4LFZAV*TILKz|ls4L%Mj~Qr3k0;PoosF*<4&BcHu*=K z0a=zt!sR9@nBJ%D{9=9fTe(+Gpwp7fx&WN~4!HXSVmB39Yg|OVA&ye|PhLMG1J& z*S|TRZxQ5u8Du@k=jwtS|7y%2F}(w1bZaKhqMdaL0YpYpSCmBizYetC9)u#dE3VQ- z5)s6C%h){|(hvY?!M|%nATL3`;-wb7}NpV$FZ<0~J^YoFb=+5|^SS2?W`{ z$p7Oh;2i=OV<1QM=r5o!_4F0_!m)AAOInmNVJ6iw_02QYC-d>Dv!wT6*yV0INj(= z|FsE$xM0I+q}1cUw^su9IFNGXzZS%T*8?6z<WaO91-pfIOVXxgYTFSr+tDDO_+e zmvCN@f|Cg(MI~p5+(PGF{%@He;zY&7P(3Qrp#{mxJvBA8k}K{2E3e#EH3DgS!q;EA zjNt2cW4&3C9@P$XpZ@C~zg`370{H_@8{G#4_mqLE z&Wd>F@c!3Mkqd#NSC!5U2U@=lge-~qiU-es{Sgp+@T=N4m2W8!$gmKD?ajnd)&1}7 z^}a%m>~U)#yleonL@g004($e{e1I=q2E2%feWg-|1vMS}4j~u7<5B%!*70sfKbaJ8 zOhQ4E12pJ=I6nzM#>@Z}CEnk`reWD##u%N)P@sk~=T<;UbQuWBv%(6qf5#wz2O}2; ztA=l;x=aUQy3>8LYb*z@No;>N2K^-zOs=z0@1iGbJ;VY3#~XBKy>VFRb=|H5n0fN^ zCR!H&n|6cR{<~0rqiS$f$|umAv%P2s0)S7TP_PQ_4ekL zPSC_do@$O8c!aExiBT_CXkp(|63ilabu+GBpVV7!n7N1j9;~!{a=ByQ z95*knAkcEI?izbrW{jAp+kTPt{A2lRzH0Z`&pUW9Ui~XBo?QX?Cj)f(UfVc|VeGvZ6=&?B~`899?m4OpLZ5RM4h=$L|@5mkmoi3R4fK^Z^ zPX`R7r<1j}L0QlrxJ!wBOy({q3@s0tcO?2R^u09(y+JgR?xJR$$rhPTU7$Z^%w;Bw zORMziTvwW1ro~&u1RqdRGXw`w$s`V_0uqO-janiz*>QToNp`iv#|LFYdX1pme<3`& zr=?=ti9YX9t`BH~B6c2kyj)awjFbStu@ghhc0CHe#Gt{)TD2yC3Hfe?)102)gb3gT zyrE3DZ!NLGaW`&NOd#`*=1 z_B5!V$$=V4!9z)qHm-uQq-OZm(oi$FU*&wOQ#h(Q&2=t;M)2vwSQ&HV0ti}|LD}a+ zn8dHXw-MC+)(6w~w=IJ>fu8OPbrZ?da}$))!nB2RspfuZqHZjRzen!CxnGE6+c+O9 zI)AZ!Vumcg5@Ge?gU1hKkI%A}h{9|6^4^LkFTQ%|f6;jFHOQ^YsQ#*LCi2Br3AX_C z>sM{Ags7%}%qux3-bof#@F2W5zS={q+-&*!YV8c#41c9I^Qgvf7@p@LmwZV%?ynnT zrsP_zmBxy@fBH^WO61}P3_MSUGa%|~%vHgnwc=u7aj+*aN|-bZ$y>8}j~fY7bOmS$ zgE8Vdn8UrIS?yGK`xl@Bm1n%pCvSXJ|bet0#2#JLbT?RphIKxn)y z!KsoO3Lq&ma0I2z06Mq?s)fzKeslvu54RO9VaI+G$ZADEdEzXwVfp9a32-_@XDnZCy}y-DiUh;T+CqPX()=9+F}s zN6C=4$Z!bLqmoh+PEtjSecF&*6d>=|)kY<15u_J7Sk>N{o2%>$C-6Ze=pf!=slj zAty_Cdy*zEe8jdyL`q&h^{glZ^1wOhTzsCZJiWvkNdztuT>^dVmx_ zp}Api*8O){A98frf(Lo)4twFrY8j<3S3uFdbP)UM0Z#*<(}d95eroW9?w6Ccw7jM) z`oj+`W2e_V7hAP=a*RFcuCUWj2F34kY^D8thFk>G?p%`HvR6S#TM6Ii_%NT}S3?;k z-YBL+AC4(awwEAQ!x_XVFLEk>baWdA@4zT=u>3(d?1Q%PF0l6vKvjdtBzNnX5Sl%s z5TLQ9Du~b!KqlfPg?-+MlqM}v?O`}?Mfjn!WK>kc({rV4h?O@r$fe$)V;NXbN4Y?$ zPec!=`L|xc%{Xe0=gAIyw%)Eh2q8>=cV~CD1{3v@?DO;2iPL@x9m{g;N;2;=JVMF@ zZ)yFp&y)(@JpzZ>{KOLp@a6T(t#iwo8U=jR>YJ96s@IqQ`85d@mP-euXzc1?sg*bD zl-6(O!f)AMSoomF6&UPMA_P1Ta1#dLpJKTyI0uZ8$f_`>?87XKZjB3HD$w`>S+gf* z@Ln}ZHKbZAXlonwOcijn2m4BehB}g0BBDP1auMigvxhI1zT|0cXfeu(bfRa60Ti}Z z>`qVH#+bZ`f?lg0u_b)Dl9!Tj1jy+_?}7_)1mJdmXx9Jz{vy^Kk2!NB{`5sc)`-x}n`p zmdJ7Ibx#@98>%b-m5%6Th!h(GpjxXsE;pGCVgTF7*a^z;Hg_OvfFbfJM7Q<#1TZ?q zqZx|Pz-bgDRv*&!ac+p_gcm)^Rci-$?K{{s8~sq|`fVUw9{moEAkrG}V8Lr(f6sQC z@Y|g1QvOoi=Z{{VB4th$UrkC&b{jZfycu}KJ+*l)AX7UGVeMgnb_x5&pNIIP#$15)obtPWpNc@I5XgMths=&wg*6L4xrsnFJ#Mecs&H;0VoA4TeM0mgESK*#%Dpp zwq2j{{8xchl@DYbTs=#^vc2T8Zz2iGljP}g?z?+5I=Fw_3%Hl6yW41dPRtjLX9 zvD2^AHUaeC3|;)xcUhn+cFP;0G#_N?q)51yDw#`o>~Ffu^7>zEo&Y!_gG2Y-gPP%! zow^gPgf@BI&PE$hVTZyL06&Q)6(xvt1NyE%bT6kx+a(w>K_Owo6Wml?C~5|7i>!PH zFxm=Gt$qslv4!KnGby`y49;iY>&97TXXQdAh57A)2wC4=p@$sb_SiXv{&5Ps6!QJGY^fQK55jlHRjOY7$uMSR zSyUm*)a?PoV}1At7$!n;h}!cN+-qULW|q)=JR7aj++Yp|wh0%+#U&yqCH{oJryW*z#$i=cR2B~O87 zXauar>-u_Wxfuyg2Y`ePfAONUu%d={Th>F~L2naEm?N^0lqeYrd69+C==KwMQHo!L z$O+Ak;niudH?QU&TcIY(x<0~E9>mIrAB;KP{j)bj^e7puIOP>&8J1{Nw`lJ)v32p6 z%GhBLNe0mLmntuh6oVNSR#k&NQcMYuem96iOh6iZW1!TjPDEa&ftdKv>GLz;Q zCXCR$KOxL z@*SVuFLO_dg`JH$!7LU3*q!!)+91-bN$;Q`z#uWEFyzNs9ZMI}3`I3q*ZW?nKcG`R zFF{Qeb~O<*P}|7s{ILm0Em+Z%DW2Xqu!I3|Sh_GJf*K`&QH(BS_5?}20fKSHQcNfT z=i>KQ<&9ZS^+HYxY33`nkZWg|FZXtz8GmMwkQn@HYfLv2_jKc261?NUJ5zyObJ?>U z{C@i68fEL-M6dpG0qWa+yYv0=%(WnIXJB}d&NhPl6hsW*=<09ea!^U(jVj)rSfj6P za!WFEd*P&a+z?RD^KODTB|ptufnB{~C|V@)4EyQZ!|?Pbon5qm3Hy<&t>t?fF0bO^ z{t}*2 z4jz397jm7!y|W^IxLKQ7>N>fWH{nb*)7hWPO0Ekupyy}q1cZ421TTM7nxICOVX7p>vg5r2?v0xG)PKThP4d;5+C~+~)M}2EEh2u$iuJ_= z%e8D@!M^70FqY^v&eQ4nBD3?G*C`hP-uZt+P(W*C>>NY^tQ2?&_t?zpV6QQAKv!pf zEfd2YAJlOMRw}~d9LRGD+D)nBd5$^nszS0ez$rBYfJz90i--M@I0Oqrw>x?uU4Nw z)x_PD`p!_Q&65q&!vtWA*L2!mnsyw7pFvJisLWNx63IYO))CNuauIM~&!r2`2h~fS zTb5U8z|ZHLYF>HZe*arnMqItN#?w&k#WHFC+0xqC;=3V#LIgviU608h<~{iya)!Si z{UN516X1hEJc34GpTSF3>q zW2>uvly>^x1pYWXj5AdHPX;`zk$BHX2bgF)zkCr;Utgo2mtDPGVw5imHZOeF+(kj z4g`S&0J(?oL*88RdsVNDcx$%0KJ=c;Gz>e>oW<;(__{E_jZPr2c943RfaK@K~f9wWCQ5>iGO0I{O-%&fWh_fQ@!vTc@*bli0 zkO+V^0spo0SRE@~QwOIrya_ojV@9F#6sv^5Z|YxU`8#| zGy2Jzact;KwVLSJIi3L-$e6Mu6F9}IQQ*hbQNHt#BbP1SX8hqUDP#rN1tL0B3P+-I zeHw^swj6;AUEv)^C#SU*>SI?s&6`jJ-0bQw>{#*oVXg3C--0%lR zy|l}~$c1MJR!rZdK9qb>mgTkpqyC8iGg+Z0uc~z5H%DJCrhH@c)w#~Q!M1N=IdF3ZvfJ02bd~%r*RS$$xC}Rgq zB)SKHm?!{`fjL$)pvCC@^futLa(DYY*t|p(O-hL?zq!akj@qSYX>XUQ-fNt8ZV=Ul zf&4w@=HY~$FcG`Ls-5K@Lo>yTcX!;G0J3O5isv6>p4#jdW$p(o@$#ppNUR3yN<`jW(3Dw&nlpH=p@VTu|EZn7r93fV(FYwe662rY= zN&0#s12x#)bOX*^%sZAk?9<1E>^!-K6V6xT)_Q`UzGIzY1~Lx7QGhF+&xD74Hc#^~ zd;BW$CE(ovX5yO{KU;;%Ho(}d*s@kJSu7Yz;tOsKy#Pp&_W)*o(`D5_(01%Q(;cs4 z_t7CRW8h)F#tj|UpRY9p^Mj;lKxIMXLVkf55>@|7*XsF9>1FiIn0FCZTJG$U4@RF< z5}$otNbP95`0($k${O?()pgqq$7?#Kb$j|=2VyOcDa;NzwQN*Tz>iZ=B%f98J0K;y z?(>pd^YrBb_)H0=9n?b*4gem8k!S!yd{nI~n0Esu+D!dAPXiDAa`~P%c>{$hFyLnj zaCn<{*Jk)Ye{Idqgd3M`l^`_tDA{v=0W(-gUkhN13yim@KvzYB*U7}FF2@65Tlv8%(}Q{J@|D!xEvI9R1x841`8pvVtjIgJen%9!P^ezUNS5O)(f? zb^%O%x=izPsT@GF;O?i*`t}3?)pF}$>x>Fe_0X(xfGNaYPjEae(ye|rr0YCP)uAG> zMgSq8{=9_G0nJ_&G^*H5Fw(sSV=29PW$orW>ralD72kQrSiBRb2$V$iw+n`9i4Zbd z6YJUN#c9yU*u7nBPEX3!MclIJ(qr9s4nb1=3Av^zzNbd{Jg3i@ISTs_G87fiMd}Ns z2#q3dUUHl!J+j_dad>fxk>v)k;AwW}LEKk^q^pkm4H`XI7%4H9Tb@AEgEn3vTQGd9 z;A1;rJ-;t3YfG%QJpiD*4RQ`b|6gBIA9=6|X8x^}*R?>Gz&`ni<&PRd<{pQr_@IV~Kt*XtFCE{<~&pc;(FwV}q6j z_(A6rOO6SsZkh>)#st0W$&@n&-6!uJru!5YiW_|)f@(rs>aDqPJAe~B`c5au-Yi6M zT;gdri#32IvK~csS9V<|nthgtQ1&J4B=uh@Ty-)YIGHSPkJh zF5$)L;32riD4KIRD_|_7?mLX#8DCR9=|r50%so|2(>e})&b=f%dwh6tx62E_69&Xl z;sFT7r0WDOM~?B64!D;6I3pF#b4Ims!y47)0>Yl1wNAfgMWm;`UQX%ipH?=g$W<4x zZM;x}?imxuwKb$b>w?TI#4`2zF#VL97UFk;aOdKiSr40|$1ct@6`rZ^T4S;hC~TY5 zzI*mGo4FbZjN4Um&kH;@JB&iWhh05-;dWs)uPqXUrX5$12n3?g9Z)7opfQUQ4}#a| zSANsl96AV^1lysxi?GyTR#_|*RgPchh8&|*5!gjm0|x5doU@Jq1zh6m2p zqF7qIKJnd-#G5bzgW4dR$9fAocC+$wvz}IZS-Nh8_O}r`)h!uA{WKLDl~~F6!?l~R zGX}xY;v2vEzfn{R$I{CPLhp{c@{WYIB#O6J9h^gV$LQBB2an$}oUEFiU55C%OJK7=^T4P8) zIW2JUlJQ+LKHo+r>9?iRX!Wr8;~wF~zqsEj4eI@}_-9t&l2^au=lnR8S`#+%SSUQl zG=Tt@>l&&%WFo_-&lR``f6ghf9xxm&+{E-M@7J8sDksNY_IWC_qD8wyoAgW3+_3RE z_%Z)}Y;GJ3>9v2(;KWrZx`|QWizL2kvV5)T8;8v~1cyojUi1^3c;SwqOHTAZuSXb9 ziaSy1ZJL?jn6~2VOppG68Rxjl+yH_PVf-~k-+!K@CI&)YwxCseFy*|NbkR?Pz zMqeM;$ln!y!z=Y3%>*EFR{~bD|CQAw#k-&VCjaChfl``8E()7!(@akBY}eD=NDXwg zb6Cyx4Lxs&^iKg@k)&){!uVtiW{jw?{cFN-ZTtq-Mj<-K9L+9M~PQDK4LwArY} zex7|1a3406b=S-fOew)G%zxL`hPKW-*5Fehn6=-jz_~7Jcobt8BnF!}E zH`yZeU9MDPOk+OWS4tb;LW)6K)F2`Q=cr4QebPVSeuH8}hJh zwyvxMzqULv^;9qJ5cye5Qg8ql9&ji1PM8|x!au`V6rnhlXmq@A5427GMMVL7DZu+J zDCwOS@aOZ-&22~-;Brr>6$8BsEgJAmel*`w@0@k0i)r4RPq1F9N{?HoSonMNk27-Q zoa}r763_M*+~hXHnX&yiX2hoO%@+le9`lC_!Z{JOyGBz#0(O`(>0c{8Z@X0^rfrtP z_F=E`zG2!xeC%E$89Va!evF;s=*!fQ)J+T~L%iVT(UkOyV&Pf}bwCSoyO?{*TR|C7 z5cSovDMF};(d|orN@px^=vV)b%SAEm#?5&2!Z=>X3o@+sJTafG6${&+REta|S7Uzi#k9WWHZLxA z>r?97BfCA@vdkGKxZKygN7wgc;}%tSfmM6cJ9F7h*R9r96wH)S`8<`NkTaS;M12aR9i`g@LdxEZFv*A>FA!D_- z9=>b(@kZCd6#g4~^S4bmmey=1NiWdPWm%*G@2pw!3`D4}jf8~;f_k$bI*txJ#sM92WjeTpUJ*8Lmk9X$nxcVC4o|z{)y7SlS#H!08($2A+t?}iupK19SteU2%idcQO%7WD;<6r-qR zR}gIm<%o;uwwNxeA%?SG1u~tOz1%0Q1TACOe8lt>v|Dv@%*`^}O)V{tzj2vvHwVrj zR2-YWJ?aT{*O0MGPeW-YMQa!nRI=WHR~p#a;b_7 z^7A?H{8=Jnqr$aDFB$DFI-h?YTSrDRLnbM`T}R&h=&7ID{cp#55@CA3H@kj_;PWRe zQ6^+Chaz&%{0=HnSIC<@Z;=J1Ie)4HvUrAL z&VIi!(ywn-j3JB^wfg62pPkdJnpnwifUK>DHP;hJ*g881rFmHyfj4AbZ7l!#$?E6s zy_A_9U$7psq2=Q<Rbq>Kj?->0;D z4mSJhl|*nYEM&F6T-L)uZsDmRP_Obzv;+fhadV=SEJjzGUefNmY(%8{Tm;YU%y+uL zGlISP2&Q%A(Nf{JKjkl9DNP^+zX6b$%04czPcke81fL-e805p21d3fRr-8t3jW!Q7 zMSwM}7GjQ$oTrwGQ4{Et1j&sHtK-f9m7%ttlLg9vR&Ic2X5!s5&|SP#^M2dj{ez9C zl-Z;A7Jgf2(=J|P49&MJyW63rPJBi|?oWwKit60owaZa4GdZ5tV}NoQ{;nng++yFb z=N#)QC_3nk6NBA<&IXpB6x~hz7>Aa&v*WGTilXdEZ-n>6bWlhj>|U@#PqB$Gq-5YlTUnk@aOmcqa#GjdmS$Z)|nnJh*IdxO-dC zRj1BVs`7=Knn;B$iDFXue0-WGUbqD1?i~Hppf-wGiEeyx0A)MweeerkA@0mrpdS2; zz_r}~X75{$-r`sSp6cs6LSsXYdhQf1vlIqvMsL*ErwTrJ%P|vL4t1i_?i}Ng-tMQ} zh*rUi=D_{ghOb+V7jcL5D!bS`dYf)_wGl*aQp2V7=Qwd9Zl4jfoOPEp@)MYFo+K=) zJfr!MNOYDQ5H-EU?7hW*;S3hq@75mjKg@h-&s68scB7s%4QI3*w<8I^F#!Kp#q~2+tw*Tdw50GEM_|T{vYsRfOGR_k zBUEq)@^IVY@B^4h9TsPaX6ue$`0*60h~1C+Rt#Ec1yWy)sxQgn*|#Dr zhAF>SQM>#SGgnIWxI`n=8kKi)#4B~B%X3%k>*7li&|Y8vYdpLkjIfg+A9*$-VvKGgM%2#-E%sq{=H!*TIz1cfxl1c`l#`Z8v=|emnAM7)8;_-NFk0Z zNF;GARPBD=24l{FuKjN`wwN4jJX--&OD>jhr3-+plwUMtE0Bd<%EEY`u<$gN#gP zuba+~l!afdUEp|2CalooOEwW*m0O;`;IY z9jV!&7i#UUjIASyLa1-iI}Ce20_ykX#qBdqI)-(69*8Z&Z*Iglg`vV38TYEK;@Cck zF^Sw8T#4H#R=4V6;8KscJy=PcaC1GDqQ_Qd&HpkU)k^5pPcN!gsy15!UWA^&=kKqp zW142=N;)v+!g|r}E-90q_5@(o3I5JRwYk%5i#?a`dH&D%Y@!qOZE9%Ix76G zTk3KnZUrmt-@|y|n=cFfx6U#bZ6qn#$yR20rA5ELQ8RZ+c8+}bkBmokH!EHKUE1ir zcq-$dQn*Q0heMSBtZh`!f(#J<&y;~l7^VBqSv61g&SLt`if$;*PuhOr&J?5B~yCNi88G`_SlY`cyt*HdGIR%ba&wJ`+ zVU&~{iyAh+DMl{~=fHDJvO{y;cY+NRf1bhSH$Ir*-#EKBpAu*eFA1leA=1!mvr#-! z%w6e3%@k?&?8Q*jn>2FiRDc;K7jfGJzs0o0d=PiWV`uZ^E`H3*NemkMo0+_7qR;HN za;KDK=r|^EBkjY}EJ|sAR6jRvXiA%;QkW*u6d z#Cxr}*8V7ahZr%TfZEIOMVGfwmeCReY^uGFL4 zc4mSnx+L*O%Evdvn%hS{GtiszBBTWN<0&esy3@Q!lb@f7L~O!4Q5kXb(FvnmNSL|S zd@O(LdW~Yj@{EzmrssiJRJC;sS|^`O^4VY^fHkUOSx2bRh_`qJ+RMVNwjy+uEHhnp zVY{F(EI{pOlDTY-TxuEV@UPu4w*NfF$bHkGveN6`;O9f%F6pz|^$kB2S1kWx5P<^F z27QLg0)ZRSIc=xD*M#D)=t)nWk$LkD#F!xp%245#Dp5*D)@~Ma={gP*vW4PPj4M#? zEMCwt=CS6nBc^YzV4~0uLwN6-**6H*h8}jPY^FLT+6%R|j6DIQJwmI>7s`TmCNjLH z7Ad!sSF~G4k_$mQ`@(D5j>W<2X3U|2W)AkxWq31PwesVv?S*qbnSHc2nW3RM*Dlz~ zAcElRO2cN^dhX~>`JCTo7%xs^`v$|DsvbohNc5?k3>Zvu?FZL}ukc;}n~y#|`lgSy>M$CCqds zcL}CBka0YYy?7ONb!V@!vm%%U_CJ`b82C__0IHuulRFO$tnK5M&_q+J#g?e>S(7%U zO<}7up^QuJ6LocLL|Xaf?ymDzF_Ba?F?EW3?G9gMgD_z=oqWxrLP$3M>+^TDj>esF zGS%!s1H9fKU(R*26{8A1fBsDG`A)~)Yx5lW8zGn!!E>Sq)|^Ih3nk-Co81Km51w~W zb?VUP*g4T#(I;V5HOt$zE(YuIn-LU`a@o30e6&zzoI8Ycm4tS>fEJt5@=ovg70wcE zby>*m&G_27=fZlL=B&lizokvRY$U^D%gf3nzV(w&)l^h|SI62VFOhXKrj1 zP1Y>$+;tn0sl9EIaapV)Q*p5mjIE`+v(AFt#2A)#CklSdxxcw@a0OrV%cmf{);sX0 zlB+m?G)?+VWYS^rs~V1gjR4}x&^z4z{Ie4K@1jfNS1_3=T`sBGaYAn~rX7Ro`3!)& zs?VfCdq3B8)COm1PR~BGy}dlS3~-hyH7%ZP^kF<475Y?%@*8Pnz%y0D_7BYn`zCjf?1o1*x{!PI)G;vHNKqfx2*nzzyNd7ued- zT;Amz%ZVy#np;%6$8P4HF<(qZKjtDX#xE2kOeefbH8#t`_=I|GE!GSLt|tto@0!c- zu?zMIZ6qp6rzFX!tP0Qp4^A0+J3Vgb{E*-D&{rsB0j+fag_~RlP+P6gP$=|YpatP0 zcC}UEiygy=^OuOSA}U3^^h@Yb#V|!;75FBToa?IHlJQj;&|$75JaiCz0|}ay<;{Y7 zMa#x>Qm=m9REK-&w0_J#DX&mk>23~tnqWpi)6CXG|CX@Fw3GsPsh{flZXH4GrZOC9 z4UMGsv#dXJM`+IhwTGwcAO4P zwMDfQ8fn%KsIjE_#0x&5X=U*nPuRkL1~4ZJWrdp6btH*fl)_eTf3Mv$XZodYs`ez` z|E0GY*GscpAyr7WR~c|u#3d@O@-atlGxEt5oIk~LmwmlKk-u69+WJJRrXF^3%N~e5 zn(WYfU)cZoK_FEzx1w$fXr+l(b-2Xjx%=b#G}YVCSL5J{qoe)#G;x>!7}wx%@u5@F zb28=DLC`Cd2?nI)e4KT^NTt#FDX650S5f4yRaZPtSvH6FdnTy;<>IratfVJ4#k8=m zTt>ko8;Vzd@Nz7^2lQ3USmlS?)AVdiOm543Gj}!86Yl@K<+3`Z!xma66lFe?cq=7T zF{i+36f{vNt&VcdJT4Nx8!A*9G|}F!DBdIzCU3aED6N<{*m5jFAH+WB%d9eO>( zx)VEwnOI!xI1iOf(Kijl(sv0!$adeVRzH8`%4&1?>rXb7L7z0vlb`}fmr=fL3Hv|( z(gn!P@qp0MemcwZ)+6BTrMuF2FqER+ znhI^D=*L3H@6UU%V3hIAlC_QdYulv8xr^*>^ueWI+b@0`^!ewWX=@Ara#G`~WK(auT#naHoIE@I2b* z@LeQ21cc@szuep-3%cS5q=y?&M-6LSZ}r<;`&Q#r;hykh70{)49iMpoEe(am6EDQ& zXvauc3oLZ_tSqn9nR#bh2Rp~DIYN*S>1h(Ch270?PCE~Qd>HhXtRZM0=jruAZe=Sm zKL0;401({i3uE|keYK4c{|6FWDfx4NyI<=+1UA#Eh*mBR@KAU&6<`ed!j4?$d|AuD z2w7qN*Eh_=^q3muwOa>s3TQ1xK&eb=xcx!F8$cK?qDE8YhNha1TB@ZM32$8?Go$PE z8vVwZX03pju*pQh9BB9Uxt9EnM>~aJ2rZSkjJVIAz&%JBuS)qC9YFJRq0a5tTyr)$ zHeazgP`&->`c@`bPr(9C$y~pmkONwO$iSr866)U9N_6U-VMCQDn2pjwXI52qjbuaC z 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 L0K-54 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 LT8ahoPXhi67M*0s literal 0 HcmV?d00001 diff --git a/deployment/minikube/README.md b/deployment/minikube/README.md new file mode 100644 index 000000000..f1c56d216 --- /dev/null +++ b/deployment/minikube/README.md @@ -0,0 +1,20 @@ +# Minikube + +There are many Kubernetes providers, but if you're just getting started, Minikube is a tool that you can use to get your feet wet. + +[Install Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) + +Open minikube dashboard: + +```text +$ minikube dashboard +``` + +This will give you an overview. Some of the steps below need some timing to make ressources available to other dependent deployments. Keeping an eye on the dashboard is a great way to check that. + +Follow the [installation instruction](deployment.md#installation-with-kubernetes) below. If all the pods and services have settled and everything looks green in your minikube dashboard, expose the `nitro-web` service on your host system with: + +```text +$ minikube service nitro-web --namespace=human-connection +``` + From f1c28ee0ccc9f25360afc8488dfe8e7119dbf0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 24 Apr 2019 00:15:08 +0200 Subject: [PATCH 044/124] Further structuring in https + dashboard chapters --- SUMMARY.md | 2 + deployment/README.md | 54 ----------------- deployment/digital-ocean/README.md | 57 ++---------------- deployment/digital-ocean/dashboard/README.md | 54 +++++++++++++++++ .../dashboard/admin-user.yaml | 0 .../{ => dashboard}/dashboard-screenshot.png | Bin .../dashboard/role-binding.yaml | 0 deployment/digital-ocean/https/README.md | 57 ++++++++++++++++++ .../https}/ingress.yaml | 0 .../https/issuer.yaml | 0 10 files changed, 117 insertions(+), 107 deletions(-) create mode 100644 deployment/digital-ocean/dashboard/README.md rename deployment/{ => digital-ocean}/dashboard/admin-user.yaml (100%) rename deployment/digital-ocean/{ => dashboard}/dashboard-screenshot.png (100%) rename deployment/{ => digital-ocean}/dashboard/role-binding.yaml (100%) create mode 100644 deployment/digital-ocean/https/README.md rename deployment/{human-connection/ingress => digital-ocean/https}/ingress.yaml (100%) rename deployment/{human-connection => digital-ocean}/https/issuer.yaml (100%) diff --git a/SUMMARY.md b/SUMMARY.md index dbd9e4299..937b2b06b 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -23,6 +23,8 @@ * [Kubernetes Deployment](deployment/README.md) * [Minikube](deployment/minikube/README.md) * [Digital Ocean](deployment/digital-ocean/README.md) + * [Kubernetes Dashboard](deployment/digital-ocean/dashboard/README.md) + * [HTTPS](deployment/digital-ocean/https/README.md) * [Neo4J DB Backup](deployment/backup.md) * [Maintenance](maintenance/README.md) * [Feature Specification](cypress/features.md) diff --git a/deployment/README.md b/deployment/README.md index a288cdce7..77e03e722 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -46,60 +46,6 @@ $ kubectl apply -f human-connection/ This can take a while because kubernetes will download the docker images. Sit back and relax and have a look into your kubernetes dashboard. Wait until all pods turn green and they don't show a warning `Waiting: ContainerCreating` anymore. -#### Setup Ingress and HTTPS - -Follow [this quick start guide](https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/index.html) and install certmanager via helm and tiller: - -```text -$ kubectl create serviceaccount tiller --namespace=kube-system -$ kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin -$ helm init --service-account=tiller -$ helm repo update -$ helm install stable/nginx-ingress -$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.6/deploy/manifests/00-crds.yaml -$ helm install --name cert-manager --namespace cert-manager stable/cert-manager -``` - -Create letsencrypt issuers. _Change the email address_ in these files before running this command. - -```bash -$ kubectl apply -f human-connection/https/ -``` - -Create an ingress service in namespace `human-connection`. _Change the domain name_ according to your needs: - -```bash -$ kubectl apply -f human-connection/ingress/ -``` - -Check the ingress server is working correctly: - -```bash -$ curl -kivL -H 'Host: ' 'https://' -``` - -If the response looks good, configure your domain registrar for the new IP address and the domain. - -Now let's get a valid HTTPS certificate. According to the tutorial above, check your tls certificate for staging: - -```bash -$ kubectl describe --namespace=human-connection certificate tls -$ kubectl describe --namespace=human-connection secret tls -``` - -If everything looks good, update the issuer of your ingress. Change the annotation `certmanager.k8s.io/issuer` from `letsencrypt-staging` to `letsencrypt-prod` in your ingress configuration in `human-connection/ingress/ingress.yaml`. - -```bash -$ kubectl apply -f human-connection/ingress/ingress.yaml -``` - -Delete the former secret to force a refresh: - -```text -$ kubectl --namespace=human-connection delete secret tls -``` - -Now, HTTPS should be configured on your domain. Congrats. #### Legacy data migration diff --git a/deployment/digital-ocean/README.md b/deployment/digital-ocean/README.md index 5431d6338..12c272691 100644 --- a/deployment/digital-ocean/README.md +++ b/deployment/digital-ocean/README.md @@ -20,56 +20,7 @@ nifty-driscoll-uusn Ready 69d v1.13.2 If you got the steps right above and see your nodes you can continue. -## Install kubernetes dashboard - -The kubernetes dashboard is optional but very helpful for debugging. If you want to install it, you have to do so only **once** per cluster: - -```bash -$ kubectl apply -f dashboard/ -$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml -``` - -### Login to your dashboard - -Proxy the remote kubernetes dashboard to localhost: - -```bash -$ kubectl proxy -``` - -Visit: - -[http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) - -You should see a login screen. - -To get your token for the dashboard you can run this command: - -```bash -$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}') -``` - -It should print something like: - -```text -Name: admin-user-token-6gl6l -Namespace: kube-system -Labels: -Annotations: kubernetes.io/service-account.name=admin-user - kubernetes.io/service-account.uid=b16afba9-dfec-11e7-bbb9-901b0e532516 - -Type: kubernetes.io/service-account-token - -Data -==== -ca.crt: 1025 bytes -namespace: 11 bytes -token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTZnbDZsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMTZhZmJhOS1kZmVjLTExZTctYmJiOS05MDFiMGU1MzI1MTYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.M70CU3lbu3PP4OjhFms8PVL5pQKj-jj4RNSLA4YmQfTXpPUuxqXjiTf094_Rzr0fgN_IVX6gC4fiNUL5ynx9KU-lkPfk0HnX8scxfJNzypL039mpGt0bbe1IXKSIRaq_9VW59Xz-yBUhycYcKPO9RM2Qa1Ax29nqNVko4vLn1_1wPqJ6XSq3GYI8anTzV8Fku4jasUwjrws6Cn6_sPEGmL54sq5R4Z5afUtv-mItTmqZZdxnkRqcJLlg2Y8WbCPogErbsaCDJoABQ7ppaqHetwfM_0yMun6ABOQbIwwl8pspJhpplKwyo700OSpvTT9zlBsu-b35lzXGBRHzv5g_RA -``` - -Grab the token from above and paste it into the [login screen](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) - -When you are logged in, you should see sth. like: -![Dashboard](./dashboard-screenshot.png) - -Feel free to save the login token from above in your password manager. Unlike the `kubeconfig` file, this token does not expire. +Digital Ocean kubernetes clusters don't have a graphical interface, so I suggest +to setup the [kubernetes dashboard](./dashboard/README.md) as a next step. +Configuring [HTTPS](./https/README.md) is bit tricky and therefore I suggest to +do this as a last step. diff --git a/deployment/digital-ocean/dashboard/README.md b/deployment/digital-ocean/dashboard/README.md new file mode 100644 index 000000000..a2e5446b2 --- /dev/null +++ b/deployment/digital-ocean/dashboard/README.md @@ -0,0 +1,54 @@ +# Install Kubernetes Dashboard + +The kubernetes dashboard is optional but very helpful for debugging. If you want to install it, you have to do so only **once** per cluster: + +```bash +# in folder deployment/digital-ocean/ +$ kubectl apply -f dashboard/ +$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml +``` + +### Login to your dashboard + +Proxy the remote kubernetes dashboard to localhost: + +```bash +$ kubectl proxy +``` + +Visit: + +[http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) + +You should see a login screen. + +To get your token for the dashboard you can run this command: + +```bash +$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}') +``` + +It should print something like: + +```text +Name: admin-user-token-6gl6l +Namespace: kube-system +Labels: +Annotations: kubernetes.io/service-account.name=admin-user + kubernetes.io/service-account.uid=b16afba9-dfec-11e7-bbb9-901b0e532516 + +Type: kubernetes.io/service-account-token + +Data +==== +ca.crt: 1025 bytes +namespace: 11 bytes +token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTZnbDZsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMTZhZmJhOS1kZmVjLTExZTctYmJiOS05MDFiMGU1MzI1MTYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.M70CU3lbu3PP4OjhFms8PVL5pQKj-jj4RNSLA4YmQfTXpPUuxqXjiTf094_Rzr0fgN_IVX6gC4fiNUL5ynx9KU-lkPfk0HnX8scxfJNzypL039mpGt0bbe1IXKSIRaq_9VW59Xz-yBUhycYcKPO9RM2Qa1Ax29nqNVko4vLn1_1wPqJ6XSq3GYI8anTzV8Fku4jasUwjrws6Cn6_sPEGmL54sq5R4Z5afUtv-mItTmqZZdxnkRqcJLlg2Y8WbCPogErbsaCDJoABQ7ppaqHetwfM_0yMun6ABOQbIwwl8pspJhpplKwyo700OSpvTT9zlBsu-b35lzXGBRHzv5g_RA +``` + +Grab the token from above and paste it into the [login screen](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/) + +When you are logged in, you should see sth. like: +![Dashboard](./dashboard-screenshot.png) + +Feel free to save the login token from above in your password manager. Unlike the `kubeconfig` file, this token does not expire. diff --git a/deployment/dashboard/admin-user.yaml b/deployment/digital-ocean/dashboard/admin-user.yaml similarity index 100% rename from deployment/dashboard/admin-user.yaml rename to deployment/digital-ocean/dashboard/admin-user.yaml diff --git a/deployment/digital-ocean/dashboard-screenshot.png b/deployment/digital-ocean/dashboard/dashboard-screenshot.png similarity index 100% rename from deployment/digital-ocean/dashboard-screenshot.png rename to deployment/digital-ocean/dashboard/dashboard-screenshot.png diff --git a/deployment/dashboard/role-binding.yaml b/deployment/digital-ocean/dashboard/role-binding.yaml similarity index 100% rename from deployment/dashboard/role-binding.yaml rename to deployment/digital-ocean/dashboard/role-binding.yaml diff --git a/deployment/digital-ocean/https/README.md b/deployment/digital-ocean/https/README.md new file mode 100644 index 000000000..398601e78 --- /dev/null +++ b/deployment/digital-ocean/https/README.md @@ -0,0 +1,57 @@ +# Setup Ingress and HTTPS + +Follow [this quick start guide](https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/index.html) and install certmanager via helm and tiller: + +```text +$ kubectl create serviceaccount tiller --namespace=kube-system +$ kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin +$ helm init --service-account=tiller +$ helm repo update +$ helm install stable/nginx-ingress +$ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.6/deploy/manifests/00-crds.yaml +$ helm install --name cert-manager --namespace cert-manager stable/cert-manager +``` + +Create letsencrypt issuers. _Change the email address_ in these files before running this command. + +```bash +# in folder deployment/digital-ocean/https/ +$ kubectl apply -f issuer.yaml +``` + +Create an ingress service in namespace `human-connection`. _Change the domain name_ according to your needs: + +```bash +# in folder deployment/digital-ocean/https/ +$ kubectl apply -f ingress.yaml +``` + +Check the ingress server is working correctly: + +```bash +$ curl -kivL -H 'Host: ' 'https://' +``` + +If the response looks good, configure your domain registrar for the new IP address and the domain. + +Now let's get a valid HTTPS certificate. According to the tutorial above, check your tls certificate for staging: + +```bash +$ kubectl describe --namespace=human-connection certificate tls +$ kubectl describe --namespace=human-connection secret tls +``` + +If everything looks good, update the issuer of your ingress. Change the annotation `certmanager.k8s.io/issuer` from `letsencrypt-staging` to `letsencrypt-prod` in your ingress configuration in `ingress.yaml`. + +```bash +# in folder deployment/digital-ocean/https/ +$ kubectl apply -f ingress.yaml +``` + +Delete the former secret to force a refresh: + +```text +$ kubectl --namespace=human-connection delete secret tls +``` + +Now, HTTPS should be configured on your domain. Congrats. diff --git a/deployment/human-connection/ingress/ingress.yaml b/deployment/digital-ocean/https/ingress.yaml similarity index 100% rename from deployment/human-connection/ingress/ingress.yaml rename to deployment/digital-ocean/https/ingress.yaml diff --git a/deployment/human-connection/https/issuer.yaml b/deployment/digital-ocean/https/issuer.yaml similarity index 100% rename from deployment/human-connection/https/issuer.yaml rename to deployment/digital-ocean/https/issuer.yaml From 6ed5ad58d5cd216847d32ca7e8e73fd645ce72c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 24 Apr 2019 00:26:37 +0200 Subject: [PATCH 045/124] Merge maintenance with deployment --- SUMMARY.md | 2 +- deployment/README.md | 54 ------------------ deployment/legacy-migration/README.md | 55 +++++++++++++++++++ .../maintenance-worker}/.dockerignore | 0 .../maintenance-worker}/.gitignore | 0 .../maintenance-worker}/Dockerfile | 0 .../maintenance-worker}/README.md | 0 .../binaries/create_private_ssh_key_from_env | 0 .../binaries/import_legacy_db | 0 .../binaries/import_legacy_uploads | 0 .../maintenance-worker}/known_hosts | 0 .../migration/mongo/import.sh | 0 .../migration/neo4j/badges.cql | 0 .../migration/neo4j/categories.cql | 0 .../migration/neo4j/comments.cql | 0 .../migration/neo4j/contributions.cql | 0 .../migration/neo4j/follows.cql | 0 .../migration/neo4j/import.sh | 0 .../migration/neo4j/shouts.cql | 0 .../migration/neo4j/users.cql | 0 docker-compose.maintenance.yml | 4 +- 21 files changed, 58 insertions(+), 57 deletions(-) create mode 100644 deployment/legacy-migration/README.md rename {maintenance => deployment/legacy-migration/maintenance-worker}/.dockerignore (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/.gitignore (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/Dockerfile (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/README.md (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/binaries/create_private_ssh_key_from_env (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/binaries/import_legacy_db (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/binaries/import_legacy_uploads (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/known_hosts (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/mongo/import.sh (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/badges.cql (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/categories.cql (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/comments.cql (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/contributions.cql (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/follows.cql (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/import.sh (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/shouts.cql (100%) rename {maintenance => deployment/legacy-migration/maintenance-worker}/migration/neo4j/users.cql (100%) diff --git a/SUMMARY.md b/SUMMARY.md index 937b2b06b..54a751238 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -26,7 +26,7 @@ * [Kubernetes Dashboard](deployment/digital-ocean/dashboard/README.md) * [HTTPS](deployment/digital-ocean/https/README.md) * [Neo4J DB Backup](deployment/backup.md) -* [Maintenance](maintenance/README.md) + * [Legacy Migration](deployment/legacy-migration/README.md) * [Feature Specification](cypress/features.md) * [Code of conduct](CODE_OF_CONDUCT.md) * [License](LICENSE.md) diff --git a/deployment/README.md b/deployment/README.md index 77e03e722..e191bddc0 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -45,57 +45,3 @@ $ kubectl apply -f human-connection/ ``` This can take a while because kubernetes will download the docker images. Sit back and relax and have a look into your kubernetes dashboard. Wait until all pods turn green and they don't show a warning `Waiting: ContainerCreating` anymore. - - -#### Legacy data migration - -This setup is completely optional and only required if you have data on a server which is running our legacy code and you want to import that data. It will import the uploads folder and migrate a dump of mongodb into neo4j. - -**Prepare migration of Human Connection legacy server** - -Create a configmap with the specific connection data of your legacy server: - -```bash -$ kubectl create configmap maintenance-worker \ - --namespace=human-connection \ - --from-literal=SSH_USERNAME=someuser \ - --from-literal=SSH_HOST=yourhost \ - --from-literal=MONGODB_USERNAME=hc-api \ - --from-literal=MONGODB_PASSWORD=secretpassword \ - --from-literal=MONGODB_AUTH_DB=hc_api \ - --from-literal=MONGODB_DATABASE=hc_api \ - --from-literal=UPLOADS_DIRECTORY=/var/www/api/uploads \ - --from-literal=NEO4J_URI=bolt://localhost:7687 -``` - -Create a secret with your public and private ssh keys. As the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys) points out, you should be careful with your ssh keys. Anyone with access to your cluster will have access to your ssh keys. Better create a new pair with `ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`: - -```bash -$ kubectl create secret generic ssh-keys \ - --namespace=human-connection \ - --from-file=id_rsa=/path/to/.ssh/id_rsa \ - --from-file=id_rsa.pub=/path/to/.ssh/id_rsa.pub \ - --from-file=known_hosts=/path/to/.ssh/known_hosts -``` - -**Migrate legacy database** - -Patch the existing deployments to use a multi-container setup: - -```bash -cd legacy-migration -kubectl apply -f volume-claim-mongo-export.yaml -kubectl patch --namespace=human-connection deployment nitro-backend --patch "$(cat deployment-backend.yaml)" -kubectl patch --namespace=human-connection deployment nitro-neo4j --patch "$(cat deployment-neo4j.yaml)" -cd .. -``` - -Run the migration: - -```text -$ kubectl --namespace=human-connection get pods -# change below -$ kubectl --namespace=human-connection exec -it nitro-neo4j-65bbdb597c-nc2lv migrate -$ kubectl --namespace=human-connection exec -it nitro-backend-c6cc5ff69-8h96z sync_uploads -``` - diff --git a/deployment/legacy-migration/README.md b/deployment/legacy-migration/README.md new file mode 100644 index 000000000..419a9a1dc --- /dev/null +++ b/deployment/legacy-migration/README.md @@ -0,0 +1,55 @@ +# Legacy data migration + +This setup is **completely optional** and only required if you have data on a +server which is running our legacy code and you want to import that data. It +will import the uploads folder and migrate a dump of the legacy Mongo database +into our new Neo4J graph database. + +**Prepare migration of Human Connection legacy server** + +Create a configmap with the specific connection data of your legacy server: + +```bash +$ kubectl create configmap maintenance-worker \ + --namespace=human-connection \ + --from-literal=SSH_USERNAME=someuser \ + --from-literal=SSH_HOST=yourhost \ + --from-literal=MONGODB_USERNAME=hc-api \ + --from-literal=MONGODB_PASSWORD=secretpassword \ + --from-literal=MONGODB_AUTH_DB=hc_api \ + --from-literal=MONGODB_DATABASE=hc_api \ + --from-literal=UPLOADS_DIRECTORY=/var/www/api/uploads \ + --from-literal=NEO4J_URI=bolt://localhost:7687 +``` + +Create a secret with your public and private ssh keys. As the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys) points out, you should be careful with your ssh keys. Anyone with access to your cluster will have access to your ssh keys. Better create a new pair with `ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`: + +```bash +$ kubectl create secret generic ssh-keys \ + --namespace=human-connection \ + --from-file=id_rsa=/path/to/.ssh/id_rsa \ + --from-file=id_rsa.pub=/path/to/.ssh/id_rsa.pub \ + --from-file=known_hosts=/path/to/.ssh/known_hosts +``` + +**Migrate legacy database** + +Patch the existing deployments to use a multi-container setup: + +```bash +cd legacy-migration +kubectl apply -f volume-claim-mongo-export.yaml +kubectl patch --namespace=human-connection deployment nitro-backend --patch "$(cat deployment-backend.yaml)" +kubectl patch --namespace=human-connection deployment nitro-neo4j --patch "$(cat deployment-neo4j.yaml)" +cd .. +``` + +Run the migration: + +```text +$ kubectl --namespace=human-connection get pods +# change below +$ kubectl --namespace=human-connection exec -it nitro-neo4j-65bbdb597c-nc2lv migrate +$ kubectl --namespace=human-connection exec -it nitro-backend-c6cc5ff69-8h96z sync_uploads +``` + diff --git a/maintenance/.dockerignore b/deployment/legacy-migration/maintenance-worker/.dockerignore similarity index 100% rename from maintenance/.dockerignore rename to deployment/legacy-migration/maintenance-worker/.dockerignore diff --git a/maintenance/.gitignore b/deployment/legacy-migration/maintenance-worker/.gitignore similarity index 100% rename from maintenance/.gitignore rename to deployment/legacy-migration/maintenance-worker/.gitignore diff --git a/maintenance/Dockerfile b/deployment/legacy-migration/maintenance-worker/Dockerfile similarity index 100% rename from maintenance/Dockerfile rename to deployment/legacy-migration/maintenance-worker/Dockerfile diff --git a/maintenance/README.md b/deployment/legacy-migration/maintenance-worker/README.md similarity index 100% rename from maintenance/README.md rename to deployment/legacy-migration/maintenance-worker/README.md diff --git a/maintenance/binaries/create_private_ssh_key_from_env b/deployment/legacy-migration/maintenance-worker/binaries/create_private_ssh_key_from_env similarity index 100% rename from maintenance/binaries/create_private_ssh_key_from_env rename to deployment/legacy-migration/maintenance-worker/binaries/create_private_ssh_key_from_env diff --git a/maintenance/binaries/import_legacy_db b/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_db similarity index 100% rename from maintenance/binaries/import_legacy_db rename to deployment/legacy-migration/maintenance-worker/binaries/import_legacy_db diff --git a/maintenance/binaries/import_legacy_uploads b/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads similarity index 100% rename from maintenance/binaries/import_legacy_uploads rename to deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads diff --git a/maintenance/known_hosts b/deployment/legacy-migration/maintenance-worker/known_hosts similarity index 100% rename from maintenance/known_hosts rename to deployment/legacy-migration/maintenance-worker/known_hosts diff --git a/maintenance/migration/mongo/import.sh b/deployment/legacy-migration/maintenance-worker/migration/mongo/import.sh similarity index 100% rename from maintenance/migration/mongo/import.sh rename to deployment/legacy-migration/maintenance-worker/migration/mongo/import.sh diff --git a/maintenance/migration/neo4j/badges.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/badges.cql similarity index 100% rename from maintenance/migration/neo4j/badges.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/badges.cql diff --git a/maintenance/migration/neo4j/categories.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/categories.cql similarity index 100% rename from maintenance/migration/neo4j/categories.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/categories.cql diff --git a/maintenance/migration/neo4j/comments.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/comments.cql similarity index 100% rename from maintenance/migration/neo4j/comments.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/comments.cql diff --git a/maintenance/migration/neo4j/contributions.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions.cql similarity index 100% rename from maintenance/migration/neo4j/contributions.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/contributions.cql diff --git a/maintenance/migration/neo4j/follows.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/follows.cql similarity index 100% rename from maintenance/migration/neo4j/follows.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/follows.cql diff --git a/maintenance/migration/neo4j/import.sh b/deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh similarity index 100% rename from maintenance/migration/neo4j/import.sh rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/import.sh diff --git a/maintenance/migration/neo4j/shouts.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts.cql similarity index 100% rename from maintenance/migration/neo4j/shouts.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/shouts.cql diff --git a/maintenance/migration/neo4j/users.cql b/deployment/legacy-migration/maintenance-worker/migration/neo4j/users.cql similarity index 100% rename from maintenance/migration/neo4j/users.cql rename to deployment/legacy-migration/maintenance-worker/migration/neo4j/users.cql diff --git a/docker-compose.maintenance.yml b/docker-compose.maintenance.yml index 33a9d28f6..551a7e244 100644 --- a/docker-compose.maintenance.yml +++ b/docker-compose.maintenance.yml @@ -4,11 +4,11 @@ services: maintenance: image: humanconnection/maintenance-worker:latest build: - context: maintenance + context: deployment/legacy-migration/maintenance-worker volumes: - uploads:/uploads - neo4j-data:/data - - ./maintenance/migration/:/migration + - ./deployment/legacy-migration/maintenance-worker/migration/:/migration networks: - hc-network environment: From 31cff1020648aa274281892ab9e68c3c6891d228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 24 Apr 2019 01:10:35 +0200 Subject: [PATCH 046/124] Clean up kubernetes config for maintenance-worker We're going into the direction of removing the backend and database deployments, accessing `/uploads` and `/data` through the maintenance worker pod and carrying out tasks from there. --- deployment/legacy-migration/README.md | 47 +++++++++++++------ .../db-migration-worker.yaml | 19 +------- .../legacy-migration/deployment-backend.yaml | 27 ----------- .../legacy-migration/deployment-neo4j.yaml | 39 --------------- .../maintenance-worker/README.md | 40 ---------------- .../maintenance-worker/docker-compose.yml | 4 +- .../volume-claim-mongo-export.yaml | 12 ----- 7 files changed, 37 insertions(+), 151 deletions(-) rename deployment/{ => legacy-migration}/db-migration-worker.yaml (57%) delete mode 100644 deployment/legacy-migration/deployment-backend.yaml delete mode 100644 deployment/legacy-migration/deployment-neo4j.yaml delete mode 100644 deployment/legacy-migration/maintenance-worker/README.md rename docker-compose.maintenance.yml => deployment/legacy-migration/maintenance-worker/docker-compose.yml (88%) delete mode 100644 deployment/legacy-migration/volume-claim-mongo-export.yaml diff --git a/deployment/legacy-migration/README.md b/deployment/legacy-migration/README.md index 419a9a1dc..eeb613146 100644 --- a/deployment/legacy-migration/README.md +++ b/deployment/legacy-migration/README.md @@ -5,7 +5,7 @@ server which is running our legacy code and you want to import that data. It will import the uploads folder and migrate a dump of the legacy Mongo database into our new Neo4J graph database. -**Prepare migration of Human Connection legacy server** +## Configure Maintenance-Worker Pod Create a configmap with the specific connection data of your legacy server: @@ -19,7 +19,6 @@ $ kubectl create configmap maintenance-worker \ --from-literal=MONGODB_AUTH_DB=hc_api \ --from-literal=MONGODB_DATABASE=hc_api \ --from-literal=UPLOADS_DIRECTORY=/var/www/api/uploads \ - --from-literal=NEO4J_URI=bolt://localhost:7687 ``` Create a secret with your public and private ssh keys. As the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys) points out, you should be careful with your ssh keys. Anyone with access to your cluster will have access to your ssh keys. Better create a new pair with `ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`: @@ -32,24 +31,44 @@ $ kubectl create secret generic ssh-keys \ --from-file=known_hosts=/path/to/.ssh/known_hosts ``` -**Migrate legacy database** +## Deploy a Temporary Maintenance-Worker Pod -Patch the existing deployments to use a multi-container setup: +Bring the application into maintenance mode. +{% hint style="info" %} TODO: implement maintenance mode {% endhint %} + + +Then temporarily delete backend and database deployments ```bash -cd legacy-migration -kubectl apply -f volume-claim-mongo-export.yaml -kubectl patch --namespace=human-connection deployment nitro-backend --patch "$(cat deployment-backend.yaml)" -kubectl patch --namespace=human-connection deployment nitro-neo4j --patch "$(cat deployment-neo4j.yaml)" -cd .. +$ kubectl --namespace=human-connection get deployments +NAME READY UP-TO-DATE AVAILABLE AGE +nitro-backend 1/1 1 1 3d11h +nitro-neo4j 1/1 1 1 3d11h +nitro-web 2/2 2 2 73d +$ kubectl --namespace=human-connection delete deployment nitro-neo4j +deployment.extensions "nitro-neo4j" deleted +$ kubectl --namespace=human-connection delete deployment nitro-backend +deployment.extensions "nitro-backend" deleted ``` -Run the migration: +Deploy one-time maintenance-worker pod: +``` +# in deployment/legacy-migration/ +$ kubectl apply -f db-migration-worker.yaml +pod/nitro-maintenance-worker created +``` + +Import legacy database and uploads: ```text -$ kubectl --namespace=human-connection get pods -# change below -$ kubectl --namespace=human-connection exec -it nitro-neo4j-65bbdb597c-nc2lv migrate -$ kubectl --namespace=human-connection exec -it nitro-backend-c6cc5ff69-8h96z sync_uploads +$ kubectl --namespace=human-connection exec -it nitro-maintenance-worker bash +$ import_legacy_db +$ import_uploads +$ exit +``` + +Delete the pod when you're done: +``` +$ kubectl --namespace=human-connection delete pod nitro-maintenance-worker ``` diff --git a/deployment/db-migration-worker.yaml b/deployment/legacy-migration/db-migration-worker.yaml similarity index 57% rename from deployment/db-migration-worker.yaml rename to deployment/legacy-migration/db-migration-worker.yaml index 8ee86f11c..f983bd5bb 100644 --- a/deployment/db-migration-worker.yaml +++ b/deployment/legacy-migration/db-migration-worker.yaml @@ -10,30 +10,15 @@ secret: secretName: ssh-keys defaultMode: 0400 - - name: mongo-export - persistentVolumeClaim: - claimName: mongo-export-claim containers: - name: nitro-maintenance-worker image: humanconnection/maintenance-worker:latest envFrom: - configMapRef: name: maintenance-worker + - configMapRef: + name: configmap volumeMounts: - name: secret-volume readOnly: false mountPath: /root/.ssh - - name: mongo-export - mountPath: /mongo-export/ ---- - kind: PersistentVolumeClaim - apiVersion: v1 - metadata: - name: mongo-export-claim - namespace: human-connection - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi diff --git a/deployment/legacy-migration/deployment-backend.yaml b/deployment/legacy-migration/deployment-backend.yaml deleted file mode 100644 index 1bcb380aa..000000000 --- a/deployment/legacy-migration/deployment-backend.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: nitro-backend - namespace: human-connection - spec: - template: - spec: - containers: - - name: nitro-maintenance-worker - image: humanconnection/maintenance-worker:latest - imagePullPolicy: Always - envFrom: - - configMapRef: - name: maintenance-worker - volumeMounts: - - name: secret-volume - readOnly: false - mountPath: /root/.ssh - - name: uploads - mountPath: /uploads/ - volumes: - - name: secret-volume - secret: - secretName: ssh-keys - defaultMode: 0400 diff --git a/deployment/legacy-migration/deployment-neo4j.yaml b/deployment/legacy-migration/deployment-neo4j.yaml deleted file mode 100644 index 0f3252984..000000000 --- a/deployment/legacy-migration/deployment-neo4j.yaml +++ /dev/null @@ -1,39 +0,0 @@ ---- - apiVersion: extensions/v1beta1 - kind: Deployment - metadata: - name: nitro-neo4j - namespace: human-connection - spec: - template: - spec: - containers: - - name: nitro-maintenance-worker - image: humanconnection/maintenance-worker:latest - imagePullPolicy: Always - envFrom: - - configMapRef: - name: maintenance-worker - env: - - name: COMMIT - value: - - name: NEO4J_URI - value: bolt://localhost:7687 - volumeMounts: - - name: secret-volume - readOnly: false - mountPath: /root/.ssh - - name: mongo-export - mountPath: /mongo-export/ - - name: nitro-neo4j - volumeMounts: - - mountPath: /mongo-export/ - name: mongo-export - volumes: - - name: secret-volume - secret: - secretName: ssh-keys - defaultMode: 0400 - - name: mongo-export - persistentVolumeClaim: - claimName: mongo-export-claim diff --git a/deployment/legacy-migration/maintenance-worker/README.md b/deployment/legacy-migration/maintenance-worker/README.md deleted file mode 100644 index 4b93686ac..000000000 --- a/deployment/legacy-migration/maintenance-worker/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Legacy Migration - -This guide helps you to import data from our legacy servers, which are using FeathersJS and MongoDB. - -**You can skip this if you don't plan to migrate any legacy applications!** - -## Prerequisites - -You need [docker](https://www.docker.com/) installed on your machine. Furthermore you need SSH access to the server and you need to know the following login credentials and server settings: - -| Environment variable | Description | -| :--- | :--- | -| SSH\_USERNAME | Your ssh username on the server | -| SSH\_HOST | The IP address of the server | -| MONGODB\_USERNAME | Mongo username on the server | -| MONGODB\_PASSWORD | Mongo password on the server | -| MONGODB\_AUTH\_DB | Mongo authentication database | -| MONGODB\_DATABASE | The name of the mongo database | -| UPLOADS\_DIRECTORY | Path to remote uploads folder | - -## Run the database migration - -Run `docker-compose` with all environment variables specified: - -```bash -SSH_USERNAME=username SSH_HOST=some.server.com MONGODB_USERNAME='hc-api' MONGODB_PASSWORD='secret' MONGODB_DATABASE=hc_api MONGODB_AUTH_DB=hc_api UPLOADS_DIRECTORY=/var/www/api/uploads docker-compose up -``` - -Download the remote mongo database: - -```bash -docker-compose exec maintenance-worker ./import.sh -``` - -Import the local download into Neo4J: - -```bash -docker-compose exec neo4j import/import.sh -``` - diff --git a/docker-compose.maintenance.yml b/deployment/legacy-migration/maintenance-worker/docker-compose.yml similarity index 88% rename from docker-compose.maintenance.yml rename to deployment/legacy-migration/maintenance-worker/docker-compose.yml index 551a7e244..a45a5163a 100644 --- a/docker-compose.maintenance.yml +++ b/deployment/legacy-migration/maintenance-worker/docker-compose.yml @@ -4,11 +4,11 @@ services: maintenance: image: humanconnection/maintenance-worker:latest build: - context: deployment/legacy-migration/maintenance-worker + context: . volumes: - uploads:/uploads - neo4j-data:/data - - ./deployment/legacy-migration/maintenance-worker/migration/:/migration + - ./migration/:/migration networks: - hc-network environment: diff --git a/deployment/legacy-migration/volume-claim-mongo-export.yaml b/deployment/legacy-migration/volume-claim-mongo-export.yaml deleted file mode 100644 index 106ef4736..000000000 --- a/deployment/legacy-migration/volume-claim-mongo-export.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- - kind: PersistentVolumeClaim - apiVersion: v1 - metadata: - name: mongo-export-claim - namespace: human-connection - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi From 7f2df14ef3448abacc3a6f4e10a42a26b91b3e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 24 Apr 2019 12:52:10 +0200 Subject: [PATCH 047/124] Update human connection specific configuration --- SUMMARY.md | 1 + deployment/.gitignore | 5 +- deployment/README.md | 40 +-------------- ...configmap.yaml => configmap.template.yaml} | 2 - deployment/human-connection/README.md | 50 +++++++++++++++++++ deployment/secrets.template.yaml | 1 + 6 files changed, 57 insertions(+), 42 deletions(-) rename deployment/{human-connection/configmap.yaml => configmap.template.yaml} (67%) create mode 100644 deployment/human-connection/README.md diff --git a/SUMMARY.md b/SUMMARY.md index 54a751238..24470f8fa 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -25,6 +25,7 @@ * [Digital Ocean](deployment/digital-ocean/README.md) * [Kubernetes Dashboard](deployment/digital-ocean/dashboard/README.md) * [HTTPS](deployment/digital-ocean/https/README.md) + * [Human Connection](deployment/human-connection/README.md) * [Neo4J DB Backup](deployment/backup.md) * [Legacy Migration](deployment/legacy-migration/README.md) * [Feature Specification](cypress/features.md) diff --git a/deployment/.gitignore b/deployment/.gitignore index aad0daea8..14cfa18ed 100644 --- a/deployment/.gitignore +++ b/deployment/.gitignore @@ -1,3 +1,4 @@ secrets.yaml -*/secrets.yaml -kubeconfig.yaml +configmap.yaml +**/secrets.yaml +**/configmap.yaml diff --git a/deployment/README.md b/deployment/README.md index e191bddc0..0615ccf9b 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -7,41 +7,5 @@ and get a kubernetes cluster. We have tested two different kubernetes providers: [Minikube](./minikube/README.md) and [Digital Ocean](./digital-ocean/README.md). - -## Installation with kubernetes - -You have to do some prerequisites e.g. change some secrets according to your own setup. - -### Edit secrets - -```bash -$ cp secrets.template.yaml human-connection/secrets.yaml -``` - -Change all secrets as needed. - -If you want to edit secrets, you have to `base64` encode them. See [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually). - -```text -# example how to base64 a string: -$ echo -n 'admin' | base64 -YWRtaW4= -``` - -Those secrets get `base64` decoded in a kubernetes pod. - -### Create a namespace - -```text -$ kubectl apply -f namespace-human-connection.yaml -``` - -Switch to the namespace `human-connection` in your kubernetes dashboard. - -### Run the configuration - -```text -$ kubectl apply -f human-connection/ -``` - -This can take a while because kubernetes will download the docker images. Sit back and relax and have a look into your kubernetes dashboard. Wait until all pods turn green and they don't show a warning `Waiting: ContainerCreating` anymore. +Check out the specific documentation for your provider. After that, learn how +to apply the specific kubernetes configuration for [Human Connection](./human-connection/README.md). diff --git a/deployment/human-connection/configmap.yaml b/deployment/configmap.template.yaml similarity index 67% rename from deployment/human-connection/configmap.yaml rename to deployment/configmap.template.yaml index 5e4d6ba89..baf41661a 100644 --- a/deployment/human-connection/configmap.yaml +++ b/deployment/configmap.template.yaml @@ -9,8 +9,6 @@ NEO4J_USER: "neo4j" NEO4J_AUTH: "none" CLIENT_URI: "https://nitro-staging.human-connection.org" - MAPBOX_TOKEN: "pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ" - PRIVATE_KEY_PASSPHRASE: "a7dsf78sadg87ad87sfagsadg78" metadata: name: configmap namespace: human-connection diff --git a/deployment/human-connection/README.md b/deployment/human-connection/README.md new file mode 100644 index 000000000..7df30e345 --- /dev/null +++ b/deployment/human-connection/README.md @@ -0,0 +1,50 @@ +# Kubernetes Configuration for Human Connection + +Deploying Human Connection with kubernetes is straight forward. All you have to +do is to change certain parameters, like domain names and API keys, then you +just apply our provided configuration files to your cluster. + +## Configuration + +Copy our provided templates: + +```bash +$ cp secrets.template.yaml human-connection/secrets.yaml +$ cp configmap.template.yaml human-connection/configmap.yaml +``` + +Change the `configmap.yaml` as needed, all variables will be available as +environment variables in your deployed kubernetes pods. + +If you want to edit secrets, you have to `base64` encode them. See [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually). + +```text +# example how to base64 a string: +$ echo -n 'admin' | base64 --wrap 0 +YWRtaW4= +``` + +Those secrets get `base64` decoded and are available as environment variables in +your deployed kubernetes pods. + +## Create a namespace + +```text +$ kubectl apply -f namespace-human-connection.yaml +``` + +If you have a [kubernets dashboard](../digital-ocean/dashboard/README.md) +deployed you should switch to namespace `human-connection` in order to +monitor the state of your deployments. + +## Apply the configuration + +```text +# in folder deployment/ +$ kubectl apply -f human-connection/ +``` + +This can take a while because kubernetes will download the docker images. Sit +back and relax and have a look into your kubernetes dashboard. Wait until all +pods turn green and they don't show a warning `Waiting: ContainerCreating` +anymore. diff --git a/deployment/secrets.template.yaml b/deployment/secrets.template.yaml index ac56b7aa1..8f18dbf46 100644 --- a/deployment/secrets.template.yaml +++ b/deployment/secrets.template.yaml @@ -4,6 +4,7 @@ data: JWT_SECRET: "Yi8mJjdiNzhCRiZmdi9WZA==" MONGODB_PASSWORD: "TU9OR09EQl9QQVNTV09SRA==" PRIVATE_KEY_PASSPHRASE: "YTdkc2Y3OHNhZGc4N2FkODdzZmFnc2FkZzc4" + MAPBOX_TOKEN: "cGsuZXlKMUlqb2lhSFZ0WVc0dFkyOXVibVZqZEdsdmJpSXNJbUVpT2lKamFqbDBjbkJ1Ykdvd2VUVmxNM1Z3WjJsek5UTnVkM1p0SW4wLktaOEtLOWw3MG9talhiRWtrYkhHc1EK" metadata: name: human-connection namespace: human-connection From 0078b743fe5ae3bcd401c628e53e14ad7a191eb3 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 24 Apr 2019 09:11:33 -0300 Subject: [PATCH 048/124] Validate comments length, presence/test - Co-authored-by: Wolfgang Huss - Co-authored-by: Mike Aono --- backend/src/resolvers/comments.js | 7 +++++++ backend/src/resolvers/comments.spec.js | 22 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index b3350ec8e..60ecbcc8e 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -1,5 +1,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js' +import { UserInputError } from 'apollo-server' +const COMMENT_MIN_LENGTH = 3 export default { Query: { CommentByPost: async (object, params, context, resolveInfo) => { @@ -23,6 +25,11 @@ export default { }, Mutation: { CreateComment: async (object, params, context, resolveInfo) => { + const content = params.content.replace(/<(?:.|\n)*?>/gm, '').trim() + + if (!params.content || content.length < COMMENT_MIN_LENGTH) { + throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} characters long!`) + } const { postId } = params delete params.postId const comment = await neo4jgraphql(object, params, context, resolveInfo, false) diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index 9918038a7..eb4e39633 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -44,7 +44,7 @@ describe('CreateComment', () => { client = new GraphQLClient(host, { headers }) }) - it('creates a post', async () => { + it('creates a comment', async () => { variables = { postId: 'p1', content: 'I\'m authorised to comment' @@ -57,5 +57,25 @@ describe('CreateComment', () => { await expect(client.request(mutation, variables)).resolves.toMatchObject(expected) }) + + it('throw an error if an empty string is sent as content', async () => { + variables = { + postId: 'p1', + content: '

' + } + + await expect(client.request(mutation, variables)) + .rejects.toThrow('Comment must be at least 3 characters long!') + }) + + it('throws an error if a comment is less than 3 characters', async () => { + variables = { + postId: 'p1', + content: '

ab

' + } + + await expect(client.request(mutation, variables)) + .rejects.toThrow('Comment must be at least 3 characters long!') + }) }) }) From 546e2c99adb1c96ca4128aa0e929ff385ebdf84c Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 24 Apr 2019 09:15:41 -0300 Subject: [PATCH 049/124] Localise text, remove frontend validations - validations are not working for the editor, they only work for our ds-input --- webapp/components/Editor/index.vue | 2 +- webapp/locales/de.json | 3 +++ webapp/locales/en.json | 3 +++ webapp/pages/post/_id/_slug/index.vue | 5 ----- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/webapp/components/Editor/index.vue b/webapp/components/Editor/index.vue index 5636c3714..57998fcbc 100644 --- a/webapp/components/Editor/index.vue +++ b/webapp/components/Editor/index.vue @@ -224,7 +224,7 @@ export default { new ListItem(), new Placeholder({ emptyNodeClass: 'is-empty', - emptyNodeText: 'Schreib etwas inspirerendes…' + emptyNodeText: this.$t('editor.placeholder') }), new History(), new Mention({ diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 04a14f2a7..3fac0310d 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -8,6 +8,9 @@ "moreInfo": "Was ist Human Connection?", "hello": "Hallo" }, + "editor": { + "placeholder": "Schreib etwas inspirerendes…" + }, "profile": { "name": "Mein Profil", "memberSince": "Mitglied seit", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index c74cbed52..83e8c4eda 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -8,6 +8,9 @@ "moreInfo": "What is Human Connection?", "hello": "Hello" }, + "editor": { + "placeholder": "Leave your inspirational thoughts…" + }, "profile": { "name": "My Profile", "memberSince": "Member since", diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 0ecd98c4e..29ad646f1 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -115,7 +115,6 @@ @@ -445,6 +448,9 @@ export default { // remove link command({ href: null }) } + }, + clear() { + this.editor.clearContent(true) } } } diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index fb3f8e5f9..459c9696d 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -107,7 +107,7 @@ color="primary" size="small" round - >{{ post.commentsCount }}  Comments + >{{ comments.length }}  Comments @@ -121,6 +121,7 @@ {{ $t('actions.cancel') }} @@ -346,15 +347,13 @@ export default { } this.$refs.commentForm.update('content', value) }, - clearEditor() { - this.form.content = ' ' + clear() { + this.$refs.editor.clear() }, addComment(comment) { this.$apollo.queries.CommentByPost.refetch() }, handleSubmit() { - const content = this.form.content - this.form.content = ' ' this.$apollo .mutate({ mutation: gql` @@ -367,11 +366,12 @@ export default { `, variables: { postId: this.post.id, - content + content: this.form.content } }) .then(res => { this.addComment(res.data.CreateComment) + this.$refs.editor.clear() this.loading = false this.disabled = false this.$toast.success(this.$t('post.comment.submitted')) @@ -395,7 +395,7 @@ export default { }, fetchPolicy: 'cache-and-network' }, - User: { + User: { query() { return gql(`{ User(orderBy: slug_asc) { From bc35ab835f09220899ddec08695d569fe09e83d7 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Wed, 24 Apr 2019 20:56:57 -0300 Subject: [PATCH 059/124] Modify cypress tests, attempt to get them to work - they are still not triggering a change in form.content, therefore sending an empty string and either failing the back end validations, or if removed creating a comment with an empty string --- cypress/integration/common/post.js | 17 +++++------------ cypress/integration/post/Comment.feature | 7 ++++++- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index 63df8459b..23a7d5764 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -1,20 +1,13 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps' -When('I should be able to post a comment', () => { - cy.get('[contenteditable]') - .type('This is a comment') - // .get('.ds-form') - // .submit() - .get('button') - .contains('Submit Comment') - .click() - .get('.iziToast-message') +Then('my comment should be successfully created', () => { + cy.get('.iziToast-message') .contains('Comment Submitted') - }) - +}) + Then('I should see my comment', () => { cy.get('div.comment p') - .should('contain', 'This is a comment') + .should('contain', 'Human Connection rocks') }) Then('the editor should be cleared', () => { diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature index 9290d7e21..8370bcfd4 100644 --- a/cypress/integration/post/Comment.feature +++ b/cypress/integration/post/Comment.feature @@ -12,6 +12,11 @@ Feature: Post Comment Scenario: Comment creation Given I visit "post/bWBjpkTKZp/101-essays" - Then I should be able to post a comment + And I type in the following text: + """ + Human Connection rocks + """ + And I click on "Submit Comment" + Then my comment should be successfully created And I should see my comment And the editor should be cleared From 473bf63946c9dc5d01a953517c85843b821671f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Thu, 25 Apr 2019 04:46:46 +0000 Subject: [PATCH 060/124] Bump eslint-plugin-jest from 22.4.1 to 22.5.0 in /backend Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 22.4.1 to 22.5.0. - [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases) - [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v22.4.1...v22.5.0) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 942cd8023..a258b1f27 100644 --- a/backend/package.json +++ b/backend/package.json @@ -85,7 +85,7 @@ "eslint": "~5.16.0", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.17.2", - "eslint-plugin-jest": "~22.4.1", + "eslint-plugin-jest": "~22.5.0", "eslint-plugin-node": "~8.0.1", "eslint-plugin-promise": "~4.1.1", "eslint-plugin-standard": "~4.0.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index b7cf8099b..e66d93297 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -3030,10 +3030,10 @@ eslint-plugin-import@~2.17.2: read-pkg-up "^2.0.0" resolve "^1.10.0" -eslint-plugin-jest@~22.4.1: - version "22.4.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz#a5fd6f7a2a41388d16f527073b778013c5189a9c" - integrity sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg== +eslint-plugin-jest@~22.5.0: + version "22.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.5.0.tgz#3a02527a5b08f7232f7bb0a52da98407bf84cdd0" + integrity sha512-YLeCRAuU3qP9lRZMul1/IbxXGg1THVpWFPBEa+VUQkcqEtO3W9GDKZ84MxYxzKTwMChTjj1l2vuNKva8HYtGPg== eslint-plugin-node@~8.0.1: version "8.0.1" From 418b93e4fd940d7e3fcdee474d6a8c21961f87f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Thu, 25 Apr 2019 04:54:26 +0000 Subject: [PATCH 061/124] Bump tippy.js from 4.2.1 to 4.3.0 in /webapp Bumps [tippy.js](https://github.com/atomiks/tippyjs) from 4.2.1 to 4.3.0. - [Release notes](https://github.com/atomiks/tippyjs/releases) - [Commits](https://github.com/atomiks/tippyjs/compare/v4.2.1...v4.3.0) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 58adc74a2..80a0ff427 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -80,7 +80,7 @@ "nodemon": "~1.18.11", "prettier": "~1.14.3", "sass-loader": "~7.1.0", - "tippy.js": "^4.2.1", + "tippy.js": "^4.3.0", "vue-jest": "~3.0.4", "vue-svg-loader": "~0.12.0" } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 1059cfb53..5b7364d2f 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -10355,10 +10355,10 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tippy.js@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.2.1.tgz#9e4939d976465f77229b05a3cb233b5dc28cf850" - integrity sha512-xEE7zYNgQxCDdPcuT6T04f0frPh0wO7CcIqJKMFazU/NqusyjCgYSkLRosIHoiRkZMRzSPOudC8wRN5GjvAyOQ== +tippy.js@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.0.tgz#5f661fed7fa30c90609eb87f6657005dd041ede3" + integrity sha512-SjctzIfkx3+waue+Ew58MMTuzYD4SK9wJOnCEdrCmwZiKJ7chZSxOguFmBm11tmTlZuGbxncUC/5Qu6GqzD2qQ== dependencies: popper.js "^1.14.7" From 18458d23f5cfa9a3f2c27f0a31a8903c5b23b74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 25 Apr 2019 10:36:03 +0200 Subject: [PATCH 062/124] Localisations corrected Had some old failures and some criss-cross with German and English. --- webapp/locales/de.json | 6 +++--- webapp/locales/en.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 90a2cf997..5831d5e61 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -9,7 +9,7 @@ "hello": "Hallo" }, "editor": { - "placeholder": "Schreib etwas inspirerendes…" + "placeholder": "Schreib etwas Inspirierendes …" }, "profile": { "name": "Mein Profil", @@ -116,8 +116,8 @@ "name": "Aktiv werden" }, "comment": { - "submit": "Commentar Senden", - "submitted": "Commentar Gesendet" + "submit": "Kommentar Senden", + "submitted": "Kommentar Gesendet" } }, "quotes": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index e909bbbf8..e92b2a190 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -9,7 +9,7 @@ "hello": "Hello" }, "editor": { - "placeholder": "Leave your inspirational thoughts…" + "placeholder": "Leave your inspirational thoughts …" }, "profile": { "name": "My Profile", From 43ac10f7d7d729f2a8f1f39e366f009007914d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 25 Apr 2019 11:43:24 +0200 Subject: [PATCH 063/124] Nice catch @Tirokk --- deployment/legacy-migration/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/legacy-migration/README.md b/deployment/legacy-migration/README.md index 8a3870ab3..8cc7bd746 100644 --- a/deployment/legacy-migration/README.md +++ b/deployment/legacy-migration/README.md @@ -18,7 +18,7 @@ $ kubectl create configmap maintenance-worker \ --from-literal=MONGODB_PASSWORD=secretpassword \ --from-literal=MONGODB_AUTH_DB=hc_api \ --from-literal=MONGODB_DATABASE=hc_api \ - --from-literal=UPLOADS_DIRECTORY=/var/www/api/uploads \ + --from-literal=UPLOADS_DIRECTORY=/var/www/api/uploads ``` Create a secret with your public and private ssh keys. As the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys) points out, you should be careful with your ssh keys. Anyone with access to your cluster will have access to your ssh keys. Better create a new pair with `ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`: From 092dcd71227bc1dc95c8844ee95f6a75cda48f23 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 10:10:08 -0300 Subject: [PATCH 064/124] Get cypress test passing in a hacky way --- cypress/integration/common/post.js | 2 ++ webapp/components/Editor/index.vue | 3 +++ webapp/pages/post/_id/_slug/index.vue | 14 +++++++------- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index 23a7d5764..e3a922139 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -1,6 +1,8 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps' Then('my comment should be successfully created', () => { + cy.get('.ds-form') + .submit() cy.get('.iziToast-message') .contains('Comment Submitted') }) diff --git a/webapp/components/Editor/index.vue b/webapp/components/Editor/index.vue index 5b542b3ce..7596e75e4 100644 --- a/webapp/components/Editor/index.vue +++ b/webapp/components/Editor/index.vue @@ -415,6 +415,9 @@ export default { this.$emit('input', content) } }, + update(value) { + this.editor.setContent(value) + }, showLinkMenu(attrs) { this.linkUrl = attrs.href this.linkMenuIsActive = true diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 459c9696d..c99e4e5be 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -339,13 +339,16 @@ export default { return this.$store.getters['auth/user'].id === id }, updateEditorContent(value) { - const content = value.replace(/<(?:.|\n)*?>/gm, '').trim().length - if (content < 3) { + const content = value.replace(/<(?:.|\n)*?>/gm, '').trim() + if (content.length < 3) { this.disabled = true } else { this.disabled = false } - this.$refs.commentForm.update('content', value) + console.log(value) + this.form.content = value + // this.$refs.commentForm.update('content', value) + // this.$refs.editor.update(value) }, clear() { this.$refs.editor.clear() @@ -354,6 +357,7 @@ export default { this.$apollo.queries.CommentByPost.refetch() }, handleSubmit() { + console.log('content', this.form.content) this.$apollo .mutate({ mutation: gql` @@ -372,14 +376,10 @@ export default { .then(res => { this.addComment(res.data.CreateComment) this.$refs.editor.clear() - this.loading = false - this.disabled = false this.$toast.success(this.$t('post.comment.submitted')) }) .catch(err => { this.$toast.error(err.message) - this.loading = false - this.disabled = false }) } }, From c387760046bb2bd4c4170dbafb9283edf0b4e1d9 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 14:20:00 -0300 Subject: [PATCH 065/124] Extract CommentForm component - Cypress test is now passing as a result Co-authored-by: Tirokk --- cypress/integration/common/post.js | 2 - webapp/components/CommentForm/index.vue | 123 ++++++++++++++++++++++++ webapp/pages/post/_id/_slug/index.vue | 110 +-------------------- 3 files changed, 127 insertions(+), 108 deletions(-) create mode 100644 webapp/components/CommentForm/index.vue diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index e3a922139..23a7d5764 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -1,8 +1,6 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps' Then('my comment should be successfully created', () => { - cy.get('.ds-form') - .submit() cy.get('.iziToast-message') .contains('Comment Submitted') }) diff --git a/webapp/components/CommentForm/index.vue b/webapp/components/CommentForm/index.vue new file mode 100644 index 000000000..f13ce6927 --- /dev/null +++ b/webapp/components/CommentForm/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index c99e4e5be..82de14481 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -111,49 +111,7 @@ - - - - - +
/gm, '').trim() - if (content.length < 3) { - this.disabled = true - } else { - this.disabled = false - } - console.log(value) - this.form.content = value - // this.$refs.commentForm.update('content', value) - // this.$refs.editor.update(value) - }, - clear() { - this.$refs.editor.clear() - }, addComment(comment) { this.$apollo.queries.CommentByPost.refetch() - }, - handleSubmit() { - console.log('content', this.form.content) - this.$apollo - .mutate({ - mutation: gql` - mutation($postId: ID, $content: String!) { - CreateComment(postId: $postId, content: $content) { - id - content - } - } - `, - variables: { - postId: this.post.id, - content: this.form.content - } - }) - .then(res => { - this.addComment(res.data.CreateComment) - this.$refs.editor.clear() - this.$toast.success(this.$t('post.comment.submitted')) - }) - .catch(err => { - this.$toast.error(err.message) - }) } }, apollo: { @@ -394,19 +305,6 @@ export default { } }, fetchPolicy: 'cache-and-network' - }, - User: { - query() { - return gql(`{ - User(orderBy: slug_asc) { - id - slug - } - }`) - }, - result(result) { - this.users = result.data.User - } } } } From 05ad8ccc302279ac7507b91abe64fe03dd2e29bb Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 16:36:59 -0300 Subject: [PATCH 066/124] Remove unused variable/method --- webapp/components/CommentForm/index.vue | 4 +--- webapp/components/Editor/index.vue | 3 --- webapp/locales/de.json | 2 +- webapp/locales/en.json | 2 +- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/webapp/components/CommentForm/index.vue b/webapp/components/CommentForm/index.vue index f13ce6927..bb71af1f7 100644 --- a/webapp/components/CommentForm/index.vue +++ b/webapp/components/CommentForm/index.vue @@ -19,7 +19,7 @@ @@ -29,7 +29,6 @@ @@ -57,7 +56,6 @@ export default { }, data() { return { - loading: false, disabled: true, form: { content: '' diff --git a/webapp/components/Editor/index.vue b/webapp/components/Editor/index.vue index 7596e75e4..5b542b3ce 100644 --- a/webapp/components/Editor/index.vue +++ b/webapp/components/Editor/index.vue @@ -415,9 +415,6 @@ export default { this.$emit('input', content) } }, - update(value) { - this.editor.setContent(value) - }, showLinkMenu(attrs) { this.linkUrl = attrs.href this.linkMenuIsActive = true diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 5831d5e61..f920498bf 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -9,7 +9,7 @@ "hello": "Hallo" }, "editor": { - "placeholder": "Schreib etwas Inspirierendes …" + "placeholder": "Schreib etwas Inspirierendes..." }, "profile": { "name": "Mein Profil", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index e92b2a190..6fd664425 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -9,7 +9,7 @@ "hello": "Hello" }, "editor": { - "placeholder": "Leave your inspirational thoughts …" + "placeholder": "Leave your inspirational thoughts..." }, "profile": { "name": "My Profile", From 7b1be0cb7d6b4c17d6c330b5537f98bd9e8d0a51 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 17:17:12 -0300 Subject: [PATCH 067/124] Add test coverage --- .gitignore | 1 + backend/package.json | 2 ++ package.json | 4 ++- webapp/package.json | 2 ++ yarn.lock | 70 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 07094a43b..07623b965 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ cypress/screenshots/ cypress.env.json !.gitkeep +**/coverage diff --git a/backend/package.json b/backend/package.json index a258b1f27..b9660315b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,8 @@ "license": "MIT", "jest": { "verbose": true, + "collectCoverage": true, + "coverageReporters": ["text", "lcov"], "testMatch": [ "**/src/**/?(*.)+(spec|test).js?(x)" ] diff --git a/package.json b/package.json index 703997ee1..ec511da35 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "cypress:webapp": "cd webapp && cross-env GRAPHQL_URI=http://localhost:4123 yarn run dev", "cypress:setup": "run-p cypress:backend:* cypress:webapp", "cypress:run": "cypress run --browser chromium", - "cypress:open": "cypress open --browser chromium" + "cypress:open": "cypress open --browser chromium", + "test:jest": "cd webapp && yarn test && cd ../backend && yarn test:jest && codecov" }, "devDependencies": { + "codecov": "^3.3.0", "cross-env": "^5.2.0", "cypress": "^3.2.0", "cypress-cucumber-preprocessor": "^1.11.0", diff --git a/webapp/package.json b/webapp/package.json index 80a0ff427..dd649c10e 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -17,6 +17,8 @@ }, "jest": { "verbose": true, + "collectCoverage": true, + "coverageReporters": ["text", "lcov"], "moduleFileExtensions": [ "js", "json", diff --git a/yarn.lock b/yarn.lock index 1d7745f03..a1f294474 100644 --- a/yarn.lock +++ b/yarn.lock @@ -762,6 +762,13 @@ acorn@^6.0.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== +agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + ajv@^6.5.5: version "6.10.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" @@ -840,6 +847,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argv@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" + integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas= + arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -1491,6 +1503,17 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codecov@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.3.0.tgz#7bf337b3f7b0474606b5c31c56dd9e44e395e15d" + integrity sha512-S70c3Eg9SixumOvxaKE/yKUxb9ihu/uebD9iPO2IR73IdP4i6ZzjXEULj3d0HeyWPr0DqBfDkjNBWxURjVO5hw== + dependencies: + argv "^0.0.2" + ignore-walk "^3.0.1" + js-yaml "^3.12.0" + teeny-request "^3.7.0" + urlgrey "^0.4.4" + coffee-react-transform@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/coffee-react-transform/-/coffee-react-transform-3.3.0.tgz#f1f90fa22de8d767fca2793e3b70f0f7d7a2e467" @@ -2103,6 +2126,18 @@ es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" +es6-promise@^4.0.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" @@ -2632,6 +2667,14 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3007,6 +3050,14 @@ js-levenshtein@^1.1.3: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^3.12.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.9.0: version "3.13.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" @@ -3509,6 +3560,11 @@ node-fetch@2.1.2: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= +node-fetch@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" + integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== + node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" @@ -4621,6 +4677,15 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +teeny-request@^3.7.0: + version "3.11.3" + resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-3.11.3.tgz#335c629f7645e5d6599362df2f3230c4cbc23a55" + integrity sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw== + dependencies: + https-proxy-agent "^2.2.1" + node-fetch "^2.2.0" + uuid "^3.3.2" + text-encoding@^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" @@ -4847,6 +4912,11 @@ url@0.11.0, url@~0.11.0: punycode "1.3.2" querystring "0.2.0" +urlgrey@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" + integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8= + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From 06650de30f62e813e1b5ab6e2f6aa7be7caf918e Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 18:05:49 -0300 Subject: [PATCH 068/124] Update travis to run codecov --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cd43b771f..9e983ec76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ script: - yarn run cypress:run after_success: + - codecov --token=$CODECOV_TOKEN - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh - chmod +x send.sh - ./send.sh success $WEBHOOK_URL From f72a6e8a42dc69a6fa5f95839dc4151b54a41b78 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 18:37:03 -0300 Subject: [PATCH 069/124] Add codecov globally in travis.yml, add badge --- .travis.yml | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9e983ec76..7f1f8ccfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ addons: before_install: - yarn global add wait-on + - yarn global add codecov - yarn install - cp cypress.env.template.json cypress.env.json diff --git a/README.md b/README.md index 3f92be96d..ac7d2a024 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Human-Connection [![Build Status](https://travis-ci.com/Human-Connection/Human-Connection.svg?branch=master)](https://travis-ci.com/Human-Connection/Human-Connection) +[![Codecov Coverage](https://img.shields.io/codecov/c/github/Human-Connection/Human-Connection/master.svg?style=flat-square)](https://codecov.io/gh/Human-Connection/Human-Connection/) [![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/Human-Connection/Nitro-Backend/blob/backend/LICENSE.md) [![Discord Channel](https://img.shields.io/discord/489522408076738561.svg)](https://discord.gg/6ub73U3) From 341e4b9c9f29be3fd0c217d9b46a88b5ee3c3e3c Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 25 Apr 2019 19:09:31 -0300 Subject: [PATCH 070/124] Remove codecov from .travis.yml - errored out when running the latest, but was adding coverage previously --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f1f8ccfc..cd43b771f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ addons: before_install: - yarn global add wait-on - - yarn global add codecov - yarn install - cp cypress.env.template.json cypress.env.json @@ -32,7 +31,6 @@ script: - yarn run cypress:run after_success: - - codecov --token=$CODECOV_TOKEN - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh - chmod +x send.sh - ./send.sh success $WEBHOOK_URL From d7102d48a1730cc3b9c8faa2610aa172a01ce518 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 26 Apr 2019 04:49:58 +0000 Subject: [PATCH 071/124] Bump eslint-plugin-jest from 22.5.0 to 22.5.1 in /backend Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 22.5.0 to 22.5.1. - [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases) - [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v22.5.0...v22.5.1) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index a258b1f27..e9b5b9b0f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -85,7 +85,7 @@ "eslint": "~5.16.0", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.17.2", - "eslint-plugin-jest": "~22.5.0", + "eslint-plugin-jest": "~22.5.1", "eslint-plugin-node": "~8.0.1", "eslint-plugin-promise": "~4.1.1", "eslint-plugin-standard": "~4.0.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index e66d93297..009207eca 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -3030,10 +3030,10 @@ eslint-plugin-import@~2.17.2: read-pkg-up "^2.0.0" resolve "^1.10.0" -eslint-plugin-jest@~22.5.0: - version "22.5.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.5.0.tgz#3a02527a5b08f7232f7bb0a52da98407bf84cdd0" - integrity sha512-YLeCRAuU3qP9lRZMul1/IbxXGg1THVpWFPBEa+VUQkcqEtO3W9GDKZ84MxYxzKTwMChTjj1l2vuNKva8HYtGPg== +eslint-plugin-jest@~22.5.1: + version "22.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.5.1.tgz#a31dfe9f9513c6af7c17ece4c65535a1370f060b" + integrity sha512-c3WjZR/HBoi4GedJRwo2OGHa8Pzo1EbSVwQ2HFzJ+4t2OoYM7Alx646EH/aaxZ+9eGcPiq0FT0UGkRuFFx2FHg== eslint-plugin-node@~8.0.1: version "8.0.1" From 589112966271cda7eaa53b7791da39109c4f14a1 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 26 Apr 2019 08:57:29 -0300 Subject: [PATCH 072/124] Ordered comments like alpha, move HcCommentForm, mobile responsive - in the first instance, we will be importing posts/comments from the alpha, so to maintain consistency, we've ordered them alike. In the future, we could support user choice of order. - Gives more space for HcCommentForm, user can see comments added to list, helps with mobile responsiveness Co-authored-by: Tirokk Co-authored-by: Mike Aono --- backend/src/resolvers/comments.js | 2 +- webapp/components/CommentForm/index.vue | 80 ++++++++++++------------- webapp/graphql/CommentQuery.js | 4 +- webapp/locales/de.json | 2 +- webapp/locales/en.json | 2 +- webapp/pages/post/_id/_slug/index.vue | 31 +++++----- 6 files changed, 58 insertions(+), 63 deletions(-) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 60ecbcc8e..899338644 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -10,7 +10,7 @@ export default { const session = context.driver.session() const transactionRes = await session.run(` MATCH (comment:Comment)-[:COMMENTS]->(post:Post {id: $postId}) - RETURN comment {.id, .contentExcerpt, .createdAt}`, { + RETURN comment {.id, .contentExcerpt, .createdAt} ORDER BY comment.createdAt ASC`, { postId }) diff --git a/webapp/components/CommentForm/index.vue b/webapp/components/CommentForm/index.vue index bb71af1f7..b1f3ddf80 100644 --- a/webapp/components/CommentForm/index.vue +++ b/webapp/components/CommentForm/index.vue @@ -1,45 +1,43 @@ diff --git a/webapp/graphql/PostCommentsQuery.js b/webapp/graphql/PostCommentsQuery.js new file mode 100644 index 000000000..2c37f2933 --- /dev/null +++ b/webapp/graphql/PostCommentsQuery.js @@ -0,0 +1,40 @@ +import gql from 'graphql-tag' + +export default app => { + const lang = app.$i18n.locale().toUpperCase() + return gql(` + query Post($slug: String!) { + Post(slug: $slug) { + commentsCount + comments(orderBy: createdAt_asc) { + id + contentExcerpt + createdAt + disabled + deleted + author { + id + slug + name + avatar + disabled + deleted + shoutedCount + contributionsCount + commentsCount + followedByCount + followedByCurrentUser + location { + name: name${lang} + } + badges { + id + key + icon + } + } + } + } + } + `) +} diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 7ae683425..692dbe69e 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -96,39 +96,9 @@ -

- - - {{ comments.length }}  Comments - -

+ -
- -
- - - +
@@ -142,9 +112,8 @@ import HcTag from '~/components/Tag' import ContentMenu from '~/components/ContentMenu' import HcUser from '~/components/User' import HcShoutButton from '~/components/ShoutButton.vue' -import HcEmpty from '~/components/Empty.vue' import HcCommentForm from '~/components/CommentForm' -import Comment from '~/components/Comment.vue' +import HcCommentList from '~/components/CommentList' export default { transition: { @@ -156,10 +125,9 @@ export default { HcCategory, HcUser, HcShoutButton, - HcEmpty, - Comment, ContentMenu, - HcCommentForm + HcCommentForm, + HcCommentList }, head() { return { @@ -169,7 +137,6 @@ export default { data() { return { post: null, - comments: null, ready: false, title: 'loading' } @@ -178,9 +145,6 @@ export default { Post(post) { this.post = post[0] || {} this.title = this.post.title - }, - Comment(comments) { - this.comments = comments || [] } }, async asyncData(context) { @@ -289,22 +253,6 @@ export default { methods: { isAuthor(id) { return this.$store.getters['auth/user'].id === id - }, - addComment(comment) { - this.$apollo.queries.Comment.refetch() - } - }, - apollo: { - Comment: { - query() { - return require('~/graphql/CommentQuery.js').default(this) - }, - variables() { - return { - postId: this.post.id - } - }, - fetchPolicy: 'cache-and-network' } } } From 54fe0a58e2f56ebcce1d42baadf3cfee6ef7ea01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 3 May 2019 04:51:08 +0000 Subject: [PATCH 100/124] Bump graphql-shield from 5.3.4 to 5.3.5 in /backend Bumps [graphql-shield](https://github.com/maticzav/graphql-shield) from 5.3.4 to 5.3.5. - [Release notes](https://github.com/maticzav/graphql-shield/releases) - [Commits](https://github.com/maticzav/graphql-shield/compare/v5.3.4...v5.3.5) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/package.json b/backend/package.json index 7e972d0e6..de72d1744 100644 --- a/backend/package.json +++ b/backend/package.json @@ -50,7 +50,7 @@ "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", "graphql-middleware": "~3.0.2", - "graphql-shield": "~5.3.4", + "graphql-shield": "~5.3.5", "graphql-tag": "~2.10.1", "graphql-yoga": "~1.17.4", "helmet": "~3.16.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index fab84f08e..845fc8146 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1104,10 +1104,10 @@ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0" integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA== -"@types/yup@0.26.12": - version "0.26.12" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.12.tgz#60fc1a485923a929699d2107fac46e6769707c4a" - integrity sha512-lWCsvLer6G84Gj7yh+oFGRuGHsqZd1Dwu47CVVL0ATw+bOnGDgMNHbTn80p1onT66fvLfN8FnRA3eRANsnnbbQ== +"@types/yup@0.26.13": + version "0.26.13" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.13.tgz#0aeeba85231a34ddc68c74b3a2c64eeb2ccf68bf" + integrity sha512-sMMtb+c2xxf/FcK0kW36+0uuSWpNwvCBZYI7vpnD9J9Z6OYk09P4TmDkMWV+NWdi9Nzt2tUJjtpnPpkiUklBaw== "@types/zen-observable@^0.5.3": version "0.5.4" @@ -3752,12 +3752,12 @@ graphql-request@~1.8.2: dependencies: cross-fetch "2.2.2" -graphql-shield@~5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.4.tgz#bd126d7d39adc6ae5b91d93ab5f65ae25f93ce80" - integrity sha512-YasNfKk7d0hiSU9eh0zvJmRmUMDLZrfVTwSke/4y46cBRXFiI9fv6OA12Ux+1DB4TyDAjGGnqx8d92ptL7ZN3w== +graphql-shield@~5.3.5: + version "5.3.5" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.5.tgz#cba409f4c1714e107212cff0a1cb2d934273392b" + integrity sha512-3kmL9x+b85NK2ipH3VGudUgUo1vXy0Z44WXhnGi3b0T0peg53DOSlXBbZOO4PNh1AcULnUjYf+DpDrP8Uc97Gw== dependencies: - "@types/yup" "0.26.12" + "@types/yup" "0.26.13" lightercollective "^0.3.0" object-hash "^1.3.1" yup "^0.27.0" From 957d8943227bef2c4c4c8acd8352c1e00c969254 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 3 May 2019 11:18:14 -0300 Subject: [PATCH 101/124] Start writing component test - restructured directories --- .../comments/CommentForm/CommentForm.test.js | 74 +++++++++++++++++++ .../{ => comments}/CommentForm/index.vue | 2 + .../{ => comments}/CommentList/index.vue | 0 webapp/graphql/PostCommentsQuery.js | 1 - webapp/pages/post/_id/_slug/index.vue | 4 +- 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 webapp/components/comments/CommentForm/CommentForm.test.js rename webapp/components/{ => comments}/CommentForm/index.vue (96%) rename webapp/components/{ => comments}/CommentList/index.vue (100%) diff --git a/webapp/components/comments/CommentForm/CommentForm.test.js b/webapp/components/comments/CommentForm/CommentForm.test.js new file mode 100644 index 000000000..6453f689a --- /dev/null +++ b/webapp/components/comments/CommentForm/CommentForm.test.js @@ -0,0 +1,74 @@ +import { config, mount, createLocalVue } from '@vue/test-utils' +import CommentForm from './index.vue' +import Vue from 'vue' +import Styleguide from '@human-connection/styleguide' + +const localVue = createLocalVue() + +localVue.use(Styleguide) + +config.stubs['no-ssr'] = '' + +describe('CommentForm.vue', () => { + let mocks + let wrapper + let form + let propsData + let submitBtn + let cancelBtn + + beforeEach(() => { + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest + .fn() + .mockRejectedValue({ message: 'Ouch!' }) + .mockResolvedValueOnce({ data: { CreateComment: { contentExcerpt: 'this is a comment' } } }) + }, + $toast: { + error: jest.fn(), + success: jest.fn() + }, + $root: { + $emit: jest.fn() + } + }, + propsData = { + post: { id: 1 } + } + }) + + describe('mount', () => { + const Wrapper = () => { + return mount(CommentForm, { mocks, localVue, propsData }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('calls the apollo mutation when form is submitted', () => { + wrapper.vm.updateEditorContent('ok') + form = wrapper.find('form') + form.trigger('submit') + expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) + }) + + it("calls clear method when the cancel button is clicked", () => { + const spy = jest.spyOn(wrapper.vm, 'clear') + wrapper.vm.updateEditorContent('ok') + cancelBtn = wrapper.find('.cancelBtn') + cancelBtn.trigger('click') + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('shows a success toaster if the mutation resolves', () => { + wrapper.vm.updateEditorContent('ok') + form = wrapper.find('form') + form.trigger('submit') + expect(mocks.$root.$emit).toHaveBeenCalledTimes(1) + expect(mocks.$toast.success).toHaveBeenCalledTimes(1) + }) + }) +}) \ No newline at end of file diff --git a/webapp/components/CommentForm/index.vue b/webapp/components/comments/CommentForm/index.vue similarity index 96% rename from webapp/components/CommentForm/index.vue rename to webapp/components/comments/CommentForm/index.vue index 6019b0965..4377e2e2c 100644 --- a/webapp/components/CommentForm/index.vue +++ b/webapp/components/comments/CommentForm/index.vue @@ -21,6 +21,7 @@ :disabled="disabled" ghost @click.prevent="clear" + class="cancelBtn" > {{ $t('actions.cancel') }}
@@ -91,6 +92,7 @@ export default { } }) .then(res => { + // console.log(this.$toast.success.mockResolvedValue()) this.$root.$emit('addComment', res.data.CreateComment) this.$refs.editor.clear() this.$toast.success(this.$t('post.comment.submitted')) diff --git a/webapp/components/CommentList/index.vue b/webapp/components/comments/CommentList/index.vue similarity index 100% rename from webapp/components/CommentList/index.vue rename to webapp/components/comments/CommentList/index.vue diff --git a/webapp/graphql/PostCommentsQuery.js b/webapp/graphql/PostCommentsQuery.js index 2c37f2933..c0ab1320a 100644 --- a/webapp/graphql/PostCommentsQuery.js +++ b/webapp/graphql/PostCommentsQuery.js @@ -5,7 +5,6 @@ export default app => { return gql(` query Post($slug: String!) { Post(slug: $slug) { - commentsCount comments(orderBy: createdAt_asc) { id contentExcerpt diff --git a/webapp/pages/post/_id/_slug/index.vue b/webapp/pages/post/_id/_slug/index.vue index 692dbe69e..87538eb6d 100644 --- a/webapp/pages/post/_id/_slug/index.vue +++ b/webapp/pages/post/_id/_slug/index.vue @@ -112,8 +112,8 @@ import HcTag from '~/components/Tag' import ContentMenu from '~/components/ContentMenu' import HcUser from '~/components/User' import HcShoutButton from '~/components/ShoutButton.vue' -import HcCommentForm from '~/components/CommentForm' -import HcCommentList from '~/components/CommentList' +import HcCommentForm from '~/components/comments/CommentForm' +import HcCommentList from '~/components/comments/CommentList' export default { transition: { From ebc290c6af3ebd51a297fc58db472bcf4c454e77 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 3 May 2019 13:39:43 -0300 Subject: [PATCH 102/124] Delete CommentForm.test.js, remove console.log - this test is not part of this PR and I'll create another issue and put in a different PR for it --- .../comments/CommentForm/CommentForm.test.js | 74 ------------------- .../components/comments/CommentForm/index.vue | 3 +- 2 files changed, 1 insertion(+), 76 deletions(-) delete mode 100644 webapp/components/comments/CommentForm/CommentForm.test.js diff --git a/webapp/components/comments/CommentForm/CommentForm.test.js b/webapp/components/comments/CommentForm/CommentForm.test.js deleted file mode 100644 index 6453f689a..000000000 --- a/webapp/components/comments/CommentForm/CommentForm.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { config, mount, createLocalVue } from '@vue/test-utils' -import CommentForm from './index.vue' -import Vue from 'vue' -import Styleguide from '@human-connection/styleguide' - -const localVue = createLocalVue() - -localVue.use(Styleguide) - -config.stubs['no-ssr'] = '' - -describe('CommentForm.vue', () => { - let mocks - let wrapper - let form - let propsData - let submitBtn - let cancelBtn - - beforeEach(() => { - mocks = { - $t: jest.fn(), - $apollo: { - mutate: jest - .fn() - .mockRejectedValue({ message: 'Ouch!' }) - .mockResolvedValueOnce({ data: { CreateComment: { contentExcerpt: 'this is a comment' } } }) - }, - $toast: { - error: jest.fn(), - success: jest.fn() - }, - $root: { - $emit: jest.fn() - } - }, - propsData = { - post: { id: 1 } - } - }) - - describe('mount', () => { - const Wrapper = () => { - return mount(CommentForm, { mocks, localVue, propsData }) - } - - beforeEach(() => { - wrapper = Wrapper() - }) - - it('calls the apollo mutation when form is submitted', () => { - wrapper.vm.updateEditorContent('ok') - form = wrapper.find('form') - form.trigger('submit') - expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) - }) - - it("calls clear method when the cancel button is clicked", () => { - const spy = jest.spyOn(wrapper.vm, 'clear') - wrapper.vm.updateEditorContent('ok') - cancelBtn = wrapper.find('.cancelBtn') - cancelBtn.trigger('click') - expect(spy).toHaveBeenCalledTimes(1) - }) - - it('shows a success toaster if the mutation resolves', () => { - wrapper.vm.updateEditorContent('ok') - form = wrapper.find('form') - form.trigger('submit') - expect(mocks.$root.$emit).toHaveBeenCalledTimes(1) - expect(mocks.$toast.success).toHaveBeenCalledTimes(1) - }) - }) -}) \ No newline at end of file diff --git a/webapp/components/comments/CommentForm/index.vue b/webapp/components/comments/CommentForm/index.vue index 4377e2e2c..585b97dba 100644 --- a/webapp/components/comments/CommentForm/index.vue +++ b/webapp/components/comments/CommentForm/index.vue @@ -20,8 +20,8 @@ {{ $t('actions.cancel') }} @@ -92,7 +92,6 @@ export default { } }) .then(res => { - // console.log(this.$toast.success.mockResolvedValue()) this.$root.$emit('addComment', res.data.CreateComment) this.$refs.editor.clear() this.$toast.success(this.$t('post.comment.submitted')) From 31c8b6e35d2af573c2ef1b1088d381a314a25986 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 3 May 2019 13:41:20 -0300 Subject: [PATCH 103/124] Use single cypher query in CreateComments - to create relation between post, comment, author - fix test to create a post so said cypher query doesn't silently fail --- backend/src/resolvers/comments.js | 13 ++------ backend/src/resolvers/comments.spec.js | 44 ++++++++++++++++---------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index 56280d6ea..eb792ecb8 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -16,17 +16,10 @@ export default { const session = context.driver.session() await session.run(` - MATCH (author:User {id: $userId}), (comment:Comment {id: $commentId}) - MERGE (comment)<-[:WROTE]-(author) - RETURN author`, { - userId: context.user.id, - commentId: comment.id - } - ) - await session.run(` - MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}) - MERGE (post)<-[:COMMENTS]-(comment) + MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}), (author:User {id: $userId}) + MERGE (post)<-[:COMMENTS]-(comment)<-[:WROTE]-(author) RETURN post`, { + userId: context.user.id, postId, commentId: comment.id } diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index e572e206b..9a54acd17 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -4,7 +4,8 @@ import { host, login } from '../jest/helpers' const factory = Factory() let client -let variables +let createCommentVariables +let createPostVariables beforeEach(async () => { await factory.create('User', { @@ -18,7 +19,7 @@ afterEach(async () => { }) describe('CreateComment', () => { - const mutation = ` + const createCommentMutation = ` mutation($postId: ID, $content: String!) { CreateComment(postId: $postId, content: $content) { id @@ -26,14 +27,21 @@ describe('CreateComment', () => { } } ` + const createPostMutation = ` + mutation($id: ID!, $title: String!, $content: String!) { + CreatePost(id: $id, title: $title, content: $content) { + id + } + } + ` describe('unauthenticated', () => { it('throws authorization error', async () => { - variables = { + createCommentVariables = { postId: 'p1', content: 'I\'m not authorised to comment' } client = new GraphQLClient(host) - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + await expect(client.request(createCommentMutation, createCommentVariables)).rejects.toThrow('Not Authorised') }) }) @@ -42,28 +50,30 @@ describe('CreateComment', () => { beforeEach(async () => { headers = await login({ email: 'test@example.org', password: '1234' }) client = new GraphQLClient(host, { headers }) - }) - - it('creates a comment', async () => { - variables = { + createCommentVariables = { postId: 'p1', content: 'I\'m authorised to comment' } + }) + + it('creates a comment', async () => { const expected = { CreateComment: { content: 'I\'m authorised to comment' } } - await expect(client.request(mutation, variables)).resolves.toMatchObject(expected) + await expect(client.request(createCommentMutation, createCommentVariables)).resolves.toMatchObject(expected) }) it('assigns the authenticated user as author', async () => { - variables = { - postId: 'p1', - content: 'I\'m authorised to comment' + createPostVariables = { + id: 'p1', + title: 'post to comment on', + content: 'please comment on me' } - await client.request(mutation, variables) + await client.request(createPostMutation, createPostVariables) + await client.request(createCommentMutation, createCommentVariables) const { User } = await client.request(`{ User(email: "test@example.org") { @@ -77,22 +87,22 @@ describe('CreateComment', () => { }) it('throw an error if an empty string is sent as content', async () => { - variables = { + createCommentVariables = { postId: 'p1', content: '

' } - await expect(client.request(mutation, variables)) + await expect(client.request(createCommentMutation, createCommentVariables)) .rejects.toThrow('Comment must be at least 1 character long!') }) it('throws an error if a comment does not contain a single character', async () => { - variables = { + createCommentVariables = { postId: 'p1', content: '

' } - await expect(client.request(mutation, variables)) + await expect(client.request(createCommentMutation, createCommentVariables)) .rejects.toThrow('Comment must be at least 1 character long!') }) }) From d1c86827104fb2df3c3e90d4e279ae5ea8430fa1 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 3 May 2019 15:47:54 -0300 Subject: [PATCH 104/124] Update method name to be more descriptive/accurate --- webapp/components/comments/CommentForm/index.vue | 2 +- webapp/components/comments/CommentList/index.vue | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/webapp/components/comments/CommentForm/index.vue b/webapp/components/comments/CommentForm/index.vue index 585b97dba..9f5197a06 100644 --- a/webapp/components/comments/CommentForm/index.vue +++ b/webapp/components/comments/CommentForm/index.vue @@ -92,7 +92,7 @@ export default { } }) .then(res => { - this.$root.$emit('addComment', res.data.CreateComment) + this.$root.$emit('refetchPostComments', res.data.CreateComment) this.$refs.editor.clear() this.$toast.success(this.$t('post.comment.submitted')) }) diff --git a/webapp/components/comments/CommentList/index.vue b/webapp/components/comments/CommentList/index.vue index ba9ada717..57b720087 100644 --- a/webapp/components/comments/CommentList/index.vue +++ b/webapp/components/comments/CommentList/index.vue @@ -26,6 +26,7 @@
@@ -44,21 +45,21 @@ export default { }, data() { return { - comments: null + comments: [] } }, watch: { Post(post) { - this.comments = post[0].comments || {} + this.comments = post[0].comments || [] } }, mounted() { - this.$root.$on('addComment', comment => { - this.addComment(comment) + this.$root.$on('refetchPostComments', comment => { + this.refetchPostComments(comment) }) }, methods: { - addComment(comment) { + refetchPostComments(comment) { this.$apollo.queries.Post.refetch() } }, From da218f8a58b2dd596f0f0a92174bd43dce37abe5 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 3 May 2019 15:48:20 -0300 Subject: [PATCH 105/124] Add component test for extracted CommentList.vue --- .../comments/CommentList/CommentList.spec.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 webapp/components/comments/CommentList/CommentList.spec.js diff --git a/webapp/components/comments/CommentList/CommentList.spec.js b/webapp/components/comments/CommentList/CommentList.spec.js new file mode 100644 index 000000000..fa6394e53 --- /dev/null +++ b/webapp/components/comments/CommentList/CommentList.spec.js @@ -0,0 +1,64 @@ +import { config, mount, createLocalVue } from '@vue/test-utils' +import CommentList from '.' +import Empty from '~/components/Empty' +import Vue from 'vue' +import Vuex from 'vuex' +import Filters from '~/plugins/vue-filters' +import Styleguide from '@human-connection/styleguide' + +const localVue = createLocalVue() + +localVue.use(Styleguide) +localVue.use(Vuex) +localVue.filter('truncate', string => string) + +config.stubs['v-popover'] = '' +config.stubs['nuxt-link'] = '' +config.stubs['no-ssr'] = '' + +describe('CommentList.vue', () => { + let mocks + let store + let wrapper + let propsData + + propsData = { + post: { id: 1 } + } + store = new Vuex.Store({ + getters: { + 'auth/user': () => { + return {} + } + } + }) + mocks = { + $t: jest.fn() + } + + describe('shallowMount', () => { + const Wrapper = () => { + return mount(CommentList, { store, mocks, localVue, propsData }) + } + + beforeEach(() => { + wrapper = Wrapper() + wrapper.setData({ + comments: [{ id: 'c1', contentExcerpt: 'this is a comment' }] + }) + }) + + it('displays a comments counter', () => { + expect(wrapper.find('span.ds-tag').text()).toEqual('1') + }) + + it('displays comments when there are comments to display', () => { + expect(wrapper.find('div#comments').text()).toEqual('this is a comment') + }) + + it('displays a message icon when there are no comments to display', () => { + wrapper.setData({ comments: [] }) + expect(wrapper.findAll(Empty)).toHaveLength(1) + }) + }) +}) From 70fb34345dde30ae4df302d80e9b9ae721168e42 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 3 May 2019 15:52:26 -0300 Subject: [PATCH 106/124] Improve test by mounting CommentList with data --- .../comments/CommentList/CommentList.spec.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/webapp/components/comments/CommentList/CommentList.spec.js b/webapp/components/comments/CommentList/CommentList.spec.js index fa6394e53..6a96f3e19 100644 --- a/webapp/components/comments/CommentList/CommentList.spec.js +++ b/webapp/components/comments/CommentList/CommentList.spec.js @@ -21,6 +21,7 @@ describe('CommentList.vue', () => { let store let wrapper let propsData + let data propsData = { post: { id: 1 } @@ -35,10 +36,15 @@ describe('CommentList.vue', () => { mocks = { $t: jest.fn() } + data = () => { + return { + comments: [] + } + } describe('shallowMount', () => { const Wrapper = () => { - return mount(CommentList, { store, mocks, localVue, propsData }) + return mount(CommentList, { store, mocks, localVue, propsData, data }) } beforeEach(() => { @@ -48,6 +54,10 @@ describe('CommentList.vue', () => { }) }) + it('displays a message icon when there are no comments to display', () => { + expect(Wrapper().findAll(Empty)).toHaveLength(1) + }) + it('displays a comments counter', () => { expect(wrapper.find('span.ds-tag').text()).toEqual('1') }) @@ -55,10 +65,5 @@ describe('CommentList.vue', () => { it('displays comments when there are comments to display', () => { expect(wrapper.find('div#comments').text()).toEqual('this is a comment') }) - - it('displays a message icon when there are no comments to display', () => { - wrapper.setData({ comments: [] }) - expect(wrapper.findAll(Empty)).toHaveLength(1) - }) }) }) From 4d631c452d09bc5ea44cb3a04aec14c4a917cea7 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Sat, 4 May 2019 17:42:42 -0300 Subject: [PATCH 107/124] Add more unit tests, add back postId deletion --- backend/src/resolvers/comments.js | 27 +++++++- backend/src/resolvers/comments.spec.js | 85 +++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/backend/src/resolvers/comments.js b/backend/src/resolvers/comments.js index eb792ecb8..d4775b235 100644 --- a/backend/src/resolvers/comments.js +++ b/backend/src/resolvers/comments.js @@ -2,18 +2,41 @@ import { neo4jgraphql } from 'neo4j-graphql-js' import { UserInputError } from 'apollo-server' const COMMENT_MIN_LENGTH = 1 +const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!' + export default { Mutation: { CreateComment: async (object, params, context, resolveInfo) => { const content = params.content.replace(/<(?:.|\n)*?>/gm, '').trim() + const { postId } = params + // Adding relationship from comment to post by passing in the postId, + // but we do not want to create the comment with postId as an attribute + // because we use relationships for this. So, we are deleting it from params + // before comment creation. + delete params.postId if (!params.content || content.length < COMMENT_MIN_LENGTH) { throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`) } - const { postId } = params - const comment = await neo4jgraphql(object, params, context, resolveInfo, false) + if (!postId.trim()) { + throw new UserInputError(NO_POST_ERR_MESSAGE) + } const session = context.driver.session() + const postQueryRes = await session.run(` + MATCH (post:Post {id: $postId}) + RETURN post`, { + postId + } + ) + const [post] = postQueryRes.records.map(record => { + return record.get('post') + }) + + if (!post) { + throw new UserInputError(NO_POST_ERR_MESSAGE) + } + const comment = await neo4jgraphql(object, params, context, resolveInfo, false) await session.run(` MATCH (post:Post {id: $postId}), (comment:Comment {id: $commentId}), (author:User {id: $userId}) diff --git a/backend/src/resolvers/comments.spec.js b/backend/src/resolvers/comments.spec.js index 9a54acd17..87a0df270 100644 --- a/backend/src/resolvers/comments.spec.js +++ b/backend/src/resolvers/comments.spec.js @@ -6,6 +6,8 @@ const factory = Factory() let client let createCommentVariables let createPostVariables +let createCommentVariablesSansPostId +let createCommentVariablesWithNonExistentPost beforeEach(async () => { await factory.create('User', { @@ -34,6 +36,13 @@ describe('CreateComment', () => { } } ` + const commentQueryForPostId = ` + query($content: String) { + Comment(content: $content) { + postId + } + } + ` describe('unauthenticated', () => { it('throws authorization error', async () => { createCommentVariables = { @@ -54,6 +63,12 @@ describe('CreateComment', () => { postId: 'p1', content: 'I\'m authorised to comment' } + createPostVariables = { + id: 'p1', + title: 'post to comment on', + content: 'please comment on me' + } + await client.request(createPostMutation, createPostVariables) }) it('creates a comment', async () => { @@ -67,12 +82,6 @@ describe('CreateComment', () => { }) it('assigns the authenticated user as author', async () => { - createPostVariables = { - id: 'p1', - title: 'post to comment on', - content: 'please comment on me' - } - await client.request(createPostMutation, createPostVariables) await client.request(createCommentMutation, createCommentVariables) const { User } = await client.request(`{ @@ -86,7 +95,7 @@ describe('CreateComment', () => { expect(User).toEqual([ { comments: [ { content: 'I\'m authorised to comment' } ] } ]) }) - it('throw an error if an empty string is sent as content', async () => { + it('throw an error if an empty string is sent from the editor as content', async () => { createCommentVariables = { postId: 'p1', content: '

' @@ -96,7 +105,7 @@ describe('CreateComment', () => { .rejects.toThrow('Comment must be at least 1 character long!') }) - it('throws an error if a comment does not contain a single character', async () => { + it('throws an error if a comment sent from the editor does not contain a single character', async () => { createCommentVariables = { postId: 'p1', content: '

' @@ -105,5 +114,65 @@ describe('CreateComment', () => { await expect(client.request(createCommentMutation, createCommentVariables)) .rejects.toThrow('Comment must be at least 1 character long!') }) + + it('throws an error if postId is sent as an empty string', async () => { + createCommentVariables = { + postId: 'p1', + content: '' + } + + await expect(client.request(createCommentMutation, createCommentVariables)) + .rejects.toThrow('Comment must be at least 1 character long!') + }) + + it('throws an error if content is sent as an string of empty characters', async () => { + createCommentVariables = { + postId: 'p1', + content: ' ' + } + + await expect(client.request(createCommentMutation, createCommentVariables)) + .rejects.toThrow('Comment must be at least 1 character long!') + }) + + it('throws an error if postId is sent as an empty string', async () => { + createCommentVariablesSansPostId = { + postId: '', + content: 'this comment should not be created' + } + + await expect(client.request(createCommentMutation, createCommentVariablesSansPostId)) + .rejects.toThrow('Comment cannot be created without a post!') + }) + + it('throws an error if postId is sent as an string of empty characters', async () => { + createCommentVariablesSansPostId = { + postId: ' ', + content: 'this comment should not be created' + } + + await expect(client.request(createCommentMutation, createCommentVariablesSansPostId)) + .rejects.toThrow('Comment cannot be created without a post!') + }) + + it('throws an error if the post does not exist in the database', async () => { + createCommentVariablesWithNonExistentPost = { + postId: 'p2', + content: 'comment should not be created cause the post doesn\'t exist' + } + + await expect(client.request(createCommentMutation, createCommentVariablesWithNonExistentPost)) + .rejects.toThrow('Comment cannot be created without a post!') + }) + + it('does not create the comment with the postId as an attribute', async () => { + const commentQueryVariablesByContent = { + content: 'I\'m authorised to comment' + } + + await client.request(createCommentMutation, createCommentVariables) + const { Comment } = await client.request(commentQueryForPostId, commentQueryVariablesByContent) + expect(Comment).toEqual([{ postId: null }]) + }) }) }) From 1fff9bbc46563d5a2f8f0e90dfc4a43056faa0c5 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Sat, 4 May 2019 18:46:34 -0300 Subject: [PATCH 108/124] Add cypress test to expose comment creation bug - at the moment, one can create the same comment by clicking rapidly on the "Comment" button - an idea for a fix https://stackoverflow.com/questions/53101521/prevent-repeated-queries-in-apollo-server-2 --- cypress/integration/common/post.js | 19 +++++++++++++++++++ cypress/integration/common/steps.js | 1 + cypress/integration/post/Comment.feature | 11 +++++++++++ .../components/comments/CommentForm/index.vue | 6 ++++++ 4 files changed, 37 insertions(+) diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index 85a9f3339..ca7e62b30 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -1,5 +1,7 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps' +const narratorAvatar = 'https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg' + Then('I click on the {string} button', text => { cy.get('button').contains(text).click() }) @@ -12,9 +14,26 @@ Then('my comment should be successfully created', () => { Then('I should see my comment', () => { cy.get('div.comment p') .should('contain', 'Human Connection rocks') + .get('.ds-avatar img') + .should('have.attr', 'src') + .and('contain', narratorAvatar) }) Then('the editor should be cleared', () => { cy.get('.ProseMirror p') .should('have.class', 'is-empty') }) + +Then('I rapidly double click on the {string} button', text => { + cy.get('button').contains(text).click().click() +}) + +Then('I should see my comment once', () => { + cy.get('div.comment p') + .should('contain', 'Human Connection rocks') + .and('have.length', 1) + .get('.ds-avatar img') + .should('have.attr', 'src') + .and('contain', narratorAvatar) + .and('have.length', 1) +}) \ No newline at end of file diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js index 85a660f43..0be3f882f 100644 --- a/cypress/integration/common/steps.js +++ b/cypress/integration/common/steps.js @@ -11,6 +11,7 @@ let loginCredentials = { } const narratorParams = { name: 'Peter Pan', + avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg', ...loginCredentials } diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature index e7e462824..afac543d9 100644 --- a/cypress/integration/post/Comment.feature +++ b/cypress/integration/post/Comment.feature @@ -20,3 +20,14 @@ Feature: Post Comment Then my comment should be successfully created And I should see my comment And the editor should be cleared + + Scenario: Prevention of multiple comment creation + Given I visit "post/bWBjpkTKZp/101-essays" + And I type in the following text: + """ + Human Connection rocks + """ + And I rapidly double click on the "Comment" button + Then my comment should be successfully created + And I should see my comment once + And the editor should be cleared diff --git a/webapp/components/comments/CommentForm/index.vue b/webapp/components/comments/CommentForm/index.vue index 9f5197a06..3efb3a625 100644 --- a/webapp/components/comments/CommentForm/index.vue +++ b/webapp/components/comments/CommentForm/index.vue @@ -29,6 +29,7 @@ @@ -56,6 +57,7 @@ export default { data() { return { disabled: true, + loading: false, form: { content: '' }, @@ -76,6 +78,8 @@ export default { this.$refs.editor.clear() }, handleSubmit() { + this.loading = true + this.loading = false this.$apollo .mutate({ mutation: gql` @@ -92,9 +96,11 @@ export default { } }) .then(res => { + this.loading = false this.$root.$emit('refetchPostComments', res.data.CreateComment) this.$refs.editor.clear() this.$toast.success(this.$t('post.comment.submitted')) + this.disabled = false }) .catch(err => { this.$toast.error(err.message) From ad46c2d059671b43c81cfb51f0bfc42590a61f90 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Sun, 5 May 2019 12:26:02 -0300 Subject: [PATCH 109/124] Remove cypress test - it is not part of the scope of this ticket, and therefore should be extracted and dealt with separately --- cypress/integration/common/post.js | 14 -------------- cypress/integration/post/Comment.feature | 11 ----------- webapp/components/comments/CommentForm/index.vue | 2 +- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/cypress/integration/common/post.js b/cypress/integration/common/post.js index ca7e62b30..f6a1bbedd 100644 --- a/cypress/integration/common/post.js +++ b/cypress/integration/common/post.js @@ -23,17 +23,3 @@ Then('the editor should be cleared', () => { cy.get('.ProseMirror p') .should('have.class', 'is-empty') }) - -Then('I rapidly double click on the {string} button', text => { - cy.get('button').contains(text).click().click() -}) - -Then('I should see my comment once', () => { - cy.get('div.comment p') - .should('contain', 'Human Connection rocks') - .and('have.length', 1) - .get('.ds-avatar img') - .should('have.attr', 'src') - .and('contain', narratorAvatar) - .and('have.length', 1) -}) \ No newline at end of file diff --git a/cypress/integration/post/Comment.feature b/cypress/integration/post/Comment.feature index afac543d9..e7e462824 100644 --- a/cypress/integration/post/Comment.feature +++ b/cypress/integration/post/Comment.feature @@ -20,14 +20,3 @@ Feature: Post Comment Then my comment should be successfully created And I should see my comment And the editor should be cleared - - Scenario: Prevention of multiple comment creation - Given I visit "post/bWBjpkTKZp/101-essays" - And I type in the following text: - """ - Human Connection rocks - """ - And I rapidly double click on the "Comment" button - Then my comment should be successfully created - And I should see my comment once - And the editor should be cleared diff --git a/webapp/components/comments/CommentForm/index.vue b/webapp/components/comments/CommentForm/index.vue index 3efb3a625..d59314e07 100644 --- a/webapp/components/comments/CommentForm/index.vue +++ b/webapp/components/comments/CommentForm/index.vue @@ -79,7 +79,7 @@ export default { }, handleSubmit() { this.loading = true - this.loading = false + this.disabled = true this.$apollo .mutate({ mutation: gql` From 4c09268f49f1911bae816f607cd35dc29c166779 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Sun, 5 May 2019 13:57:12 -0300 Subject: [PATCH 110/124] Update tests after backend validations - Now a comment cannot be created without a post to associate it with --- backend/src/resolvers/moderation.spec.js | 48 ++++++++++++++---------- backend/src/resolvers/reports.spec.js | 11 +++++- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/backend/src/resolvers/moderation.spec.js b/backend/src/resolvers/moderation.spec.js index f8aa6e10b..28f4dc322 100644 --- a/backend/src/resolvers/moderation.spec.js +++ b/backend/src/resolvers/moderation.spec.js @@ -16,6 +16,9 @@ const setupAuthenticateClient = (params) => { let createResource let authenticateClient +let createPostVariables +let createCommentVariables + beforeEach(() => { createResource = () => {} authenticateClient = () => { @@ -103,18 +106,21 @@ describe('disable', () => { variables = { id: 'c47' } - + createPostVariables = { + id: 'p3', + title: 'post to comment on', + content: 'please comment on me' + } + createCommentVariables = { + id: 'c47', + postId: 'p3', + content: 'this comment was created for this post' + } createResource = async () => { await factory.create('User', { id: 'u45', email: 'commenter@example.org', password: '1234' }) - await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' }) - await Promise.all([ - factory.create('Post', { id: 'p3' }), - factory.create('Comment', { id: 'c47', postId: 'p3', content: 'this comment was created for this post' }) - ]) - - await Promise.all([ - factory.relate('Comment', 'Author', { from: 'u45', to: 'c47' }) - ]) + const asAuthenticatedUser = await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' }) + await asAuthenticatedUser.create('Post', createPostVariables) + await asAuthenticatedUser.create('Comment', createCommentVariables) } }) @@ -277,17 +283,21 @@ describe('enable', () => { variables = { id: 'c456' } - + createPostVariables = { + id: 'p9', + title: 'post to comment on', + content: 'please comment on me' + } + createCommentVariables = { + id: 'c456', + postId: 'p9', + content: 'this comment was created for this post' + } createResource = async () => { await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' }) - await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) - await Promise.all([ - factory.create('Post', { id: 'p9' }), - factory.create('Comment', { id: 'c456' }) - ]) - await Promise.all([ - factory.relate('Comment', 'Author', { from: 'u123', to: 'c456' }) - ]) + const asAuthenticatedUser = await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) + await asAuthenticatedUser.create('Post', createPostVariables) + await asAuthenticatedUser.create('Comment', createCommentVariables) const disableMutation = ` mutation { diff --git a/backend/src/resolvers/reports.spec.js b/backend/src/resolvers/reports.spec.js index ae8894572..9bd1fe753 100644 --- a/backend/src/resolvers/reports.spec.js +++ b/backend/src/resolvers/reports.spec.js @@ -9,6 +9,7 @@ describe('report', () => { let headers let returnedObject let variables + let createPostVariables beforeEach(async () => { returnedObject = '{ description }' @@ -128,8 +129,14 @@ describe('report', () => { describe('reported resource is a comment', () => { beforeEach(async () => { - await factory.authenticateAs({ email: 'test@example.org', password: '1234' }) - await factory.create('Comment', { id: 'c34', content: 'Robert getting tired.' }) + createPostVariables = { + id: 'p1', + title: 'post to comment on', + content: 'please comment on me' + } + const asAuthenticatedUser = await factory.authenticateAs({ email: 'test@example.org', password: '1234' }) + await asAuthenticatedUser.create('Post', createPostVariables) + await asAuthenticatedUser.create('Comment', { postId: 'p1', id: 'c34', content: 'Robert getting tired.' }) variables = { id: 'c34' } }) From 0beb2d1763c2c81296a18346bf92c2cb5d652bcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 04:46:30 +0000 Subject: [PATCH 111/124] Bump neo4j-driver from 1.7.3 to 1.7.4 in /backend Bumps [neo4j-driver](https://github.com/neo4j/neo4j-javascript-driver) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/neo4j/neo4j-javascript-driver/releases) - [Commits](https://github.com/neo4j/neo4j-javascript-driver/compare/1.7.3...1.7.4) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 7e972d0e6..a70d4c925 100644 --- a/backend/package.json +++ b/backend/package.json @@ -58,7 +58,7 @@ "linkifyjs": "~2.1.8", "lodash": "~4.17.11", "ms": "~2.1.1", - "neo4j-driver": "~1.7.3", + "neo4j-driver": "~1.7.4", "neo4j-graphql-js": "~2.4.2", "node-fetch": "~2.4.1", "npm-run-all": "~4.1.5", diff --git a/backend/yarn.lock b/backend/yarn.lock index fab84f08e..19b86530d 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -5588,10 +5588,10 @@ negotiator@0.6.1: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= -neo4j-driver@^1.7.2, neo4j-driver@~1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.3.tgz#1c1108ab26b7243975f1b20045daf31d8f685207" - integrity sha512-UCNOFiQdouq14PvZGTr+psy657BJsBpO6O2cJpP+NprZnEF4APrDzAcydPZSFxE1nfooLNc50vfuZ0q54UyY2Q== +neo4j-driver@^1.7.2, neo4j-driver@~1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.4.tgz#9661cf643b63818bff85e82c4691918e75098c1e" + integrity sha512-pbK1HbXh92zNSwMlXL8aNynkHohg9Jx/Tk+EewdJawGm8n8sKIY4NpRkp0nRw6RHvVBU3u9cQXt01ftFVe7j+A== dependencies: babel-runtime "^6.26.0" text-encoding "^0.6.4" From c046524b8f87f09089974bc9ef4b0445eab47400 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 04:59:48 +0000 Subject: [PATCH 112/124] Bump babel-jest from 24.7.1 to 24.8.0 in /webapp Bumps [babel-jest](https://github.com/facebook/jest/tree/HEAD/packages/babel-jest) from 24.7.1 to 24.8.0. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/commits/v24.8.0/packages/babel-jest) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 131 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index c0ae69579..67673da56 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -68,7 +68,7 @@ "@vue/test-utils": "~1.0.0-beta.29", "babel-core": "~7.0.0-bridge.0", "babel-eslint": "~10.0.1", - "babel-jest": "~24.7.1", + "babel-jest": "~24.8.0", "eslint": "~5.16.0", "eslint-config-prettier": "~4.2.0", "eslint-loader": "~2.1.2", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 7f0d207f2..daac89557 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -831,6 +831,15 @@ jest-message-util "^24.7.1" jest-mock "^24.7.0" +"@jest/fake-timers@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1" + integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw== + dependencies: + "@jest/types" "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + "@jest/reporters@^24.7.1": version "24.7.1" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.7.1.tgz#38ac0b096cd691bbbe3051ddc25988d42e37773a" @@ -875,6 +884,15 @@ "@jest/types" "^24.7.0" "@types/istanbul-lib-coverage" "^2.0.0" +"@jest/test-result@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" + integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng== + dependencies: + "@jest/console" "^24.7.1" + "@jest/types" "^24.8.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@jest/test-sequencer@^24.7.1": version "24.7.1" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz#9c18e428e1ad945fa74f6233a9d35745ca0e63e0" @@ -885,33 +903,34 @@ jest-runner "^24.7.1" jest-runtime "^24.7.1" -"@jest/transform@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.7.1.tgz#872318f125bcfab2de11f53b465ab1aa780789c2" - integrity sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw== +"@jest/transform@^24.7.1", "@jest/transform@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" + integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" babel-plugin-istanbul "^5.1.0" chalk "^2.0.1" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.15" - jest-haste-map "^24.7.1" + jest-haste-map "^24.8.0" jest-regex-util "^24.3.0" - jest-util "^24.7.1" + jest-util "^24.8.0" micromatch "^3.1.10" realpath-native "^1.1.0" slash "^2.0.0" source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.7.0": - version "24.7.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" - integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== +"@jest/types@^24.7.0", "@jest/types@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" + integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" "@nuxt/babel-preset-app@2.6.3": @@ -1350,11 +1369,31 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/istanbul-lib-coverage@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + "@types/istanbul-lib-coverage@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85" integrity sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg== +"@types/istanbul-lib-report@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" + integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + "@types/long@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" @@ -2322,13 +2361,13 @@ babel-eslint@~10.0.1: eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" -babel-jest@^24.7.1, babel-jest@~24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.7.1.tgz#73902c9ff15a7dfbdc9994b0b17fcefd96042178" - integrity sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg== +babel-jest@^24.7.1, babel-jest@~24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" + integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== dependencies: - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" "@types/babel__core" "^7.1.0" babel-plugin-istanbul "^5.1.0" babel-preset-jest "^24.6.0" @@ -6168,6 +6207,25 @@ jest-haste-map@^24.7.1: optionalDependencies: fsevents "^1.2.7" +jest-haste-map@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800" + integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ== + dependencies: + "@jest/types" "^24.8.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.4.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + jest-jasmine2@^24.7.1: version "24.7.1" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz#01398686dabe46553716303993f3be62e5d9d818" @@ -6221,6 +6279,20 @@ jest-message-util@^24.7.1: slash "^2.0.0" stack-utils "^1.0.1" +jest-message-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b" + integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + jest-mock@^24.7.0: version "24.7.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.7.0.tgz#e49ce7262c12d7f5897b0d8af77f6db8e538023b" @@ -6228,6 +6300,13 @@ jest-mock@^24.7.0: dependencies: "@jest/types" "^24.7.0" +jest-mock@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" + integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A== + dependencies: + "@jest/types" "^24.8.0" + jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" @@ -6353,6 +6432,24 @@ jest-util@^24.7.1: slash "^2.0.0" source-map "^0.6.0" +jest-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" + integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== + dependencies: + "@jest/console" "^24.7.1" + "@jest/fake-timers" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + jest-validate@^24.7.0: version "24.7.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.7.0.tgz#70007076f338528ee1b1c8a8258b1b0bb982508d" From 1ef1b7ef3da5a4318b1ad5486c422a4560900604 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 21:07:42 +0000 Subject: [PATCH 113/124] Bump helmet from 3.16.0 to 3.18.0 in /backend Bumps [helmet](https://github.com/helmetjs/helmet) from 3.16.0 to 3.18.0. - [Release notes](https://github.com/helmetjs/helmet/releases) - [Changelog](https://github.com/helmetjs/helmet/blob/master/CHANGELOG.md) - [Commits](https://github.com/helmetjs/helmet/compare/v3.16.0...v3.18.0) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 58 ++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/backend/package.json b/backend/package.json index 2a02a2b89..e3c4300f0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -53,7 +53,7 @@ "graphql-shield": "~5.3.5", "graphql-tag": "~2.10.1", "graphql-yoga": "~1.17.4", - "helmet": "~3.16.0", + "helmet": "~3.18.0", "jsonwebtoken": "~8.5.1", "linkifyjs": "~2.1.8", "lodash": "~4.17.11", diff --git a/backend/yarn.lock b/backend/yarn.lock index 4dccfd39d..c4e51c3be 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -3232,10 +3232,10 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect-ct@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.1.tgz#de84476a2dbcb85000d5903737e9bc8a5ba7b897" - integrity sha512-ngXzTfoRGG7fYens3/RMb6yYoVLvLMfmsSllP/mZPxNHgFq41TmPSLF/nLY7fwoclI2vElvAmILFWGUYqdjfCg== +expect-ct@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.2.0.tgz#3a54741b6ed34cc7a93305c605f63cd268a54a62" + integrity sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g== expect@^24.7.1: version "24.7.1" @@ -3370,10 +3370,10 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -feature-policy@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.2.0.tgz#22096de49ab240176878ffe2bde2f6ff04d48c43" - integrity sha512-2hGrlv6efG4hscYVZeaYjpzpT6I2OZgYqE2yDUzeAcKj2D1SH0AsEzqJNXzdoglEddcIXQQYop3lD97XpG75Jw== +feature-policy@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" + integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ== figures@2.0.0, figures@^2.0.0: version "2.0.0" @@ -3498,10 +3498,10 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -frameguard@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9" - integrity sha1-e8rUae57lukdEs6zlZx4I1qScuk= +frameguard@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.1.0.tgz#bd1442cca1d67dc346a6751559b6d04502103a22" + integrity sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g== fresh@0.5.2: version "0.5.2" @@ -3946,25 +3946,25 @@ helmet-csp@2.7.1: dasherize "2.0.0" platform "1.3.5" -helmet@~3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.16.0.tgz#7df41a4bfe4c83d90147c1e30d70893f92a9d97c" - integrity sha512-rsTKRogc5OYGlvSHuq5QsmOsOzF6uDoMqpfh+Np8r23+QxDq+SUx90Rf8HyIKQVl7H6NswZEwfcykinbAeZ6UQ== +helmet@~3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.18.0.tgz#37666f7c861bd1ff3015e0cdb903a43501e3da3e" + integrity sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA== dependencies: depd "2.0.0" dns-prefetch-control "0.1.0" dont-sniff-mimetype "1.0.0" - expect-ct "0.1.1" - feature-policy "0.2.0" - frameguard "3.0.0" + expect-ct "0.2.0" + feature-policy "0.3.0" + frameguard "3.1.0" helmet-crossdomain "0.3.0" helmet-csp "2.7.1" hide-powered-by "1.0.0" hpkp "2.0.0" hsts "2.2.0" ienoopen "1.1.0" - nocache "2.0.0" - referrer-policy "1.1.0" + nocache "2.1.0" + referrer-policy "1.2.0" x-xss-protection "1.1.0" hide-powered-by@1.0.0: @@ -5624,10 +5624,10 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" -nocache@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980" - integrity sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA= +nocache@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" + integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== node-fetch@2.1.2: version "2.1.2" @@ -6492,10 +6492,10 @@ reasoner@2.0.0: vocabs-rdfs "^0.11.1" vocabs-xsd "^0.11.1" -referrer-policy@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79" - integrity sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk= +referrer-policy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" + integrity sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA== regenerate-unicode-properties@^8.0.2: version "8.0.2" From a58258c6688e636f41b3c4b2265f0f1e170524f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 21:08:52 +0000 Subject: [PATCH 114/124] Bump node-sass from 4.11.0 to 4.12.0 in /webapp Bumps [node-sass](https://github.com/sass/node-sass) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/sass/node-sass/releases) - [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md) - [Commits](https://github.com/sass/node-sass/compare/v4.11.0...v4.12.0) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 37 ++++++++++--------------------------- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index f0d455558..1a6515c34 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -76,7 +76,7 @@ "eslint-plugin-vue": "~5.2.2", "fuse.js": "^3.4.4", "jest": "~24.7.1", - "node-sass": "~4.11.0", + "node-sass": "~4.12.0", "nodemon": "~1.19.0", "prettier": "~1.14.3", "sass-loader": "~7.1.0", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index b1d5fb255..c113ee306 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -6866,16 +6866,6 @@ lodash._reinterpolate@~3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash.assign@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= - -lodash.clonedeep@^4.3.2: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -6916,11 +6906,6 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.mergewith@^4.6.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" - integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== - lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -7332,10 +7317,10 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.10.0, nan@^2.9.2: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== +nan@^2.13.2, nan@^2.9.2: + version "2.13.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" + integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== nanomatch@^1.2.9: version "1.2.13" @@ -7516,10 +7501,10 @@ node-releases@^1.1.13: dependencies: semver "^5.3.0" -node-sass@~4.11.0: - version "4.11.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" - integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== +node-sass@~4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017" + integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -7528,12 +7513,10 @@ node-sass@~4.11.0: get-stdin "^4.0.1" glob "^7.0.3" in-publish "^2.0.0" - lodash.assign "^4.2.0" - lodash.clonedeep "^4.3.2" - lodash.mergewith "^4.6.0" + lodash "^4.17.11" meow "^3.7.0" mkdirp "^0.5.1" - nan "^2.10.0" + nan "^2.13.2" node-gyp "^3.8.0" npmlog "^4.0.0" request "^2.88.0" From fc7d8a5c6c544966395c2b9ec86c1711b988a426 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 21:08:57 +0000 Subject: [PATCH 115/124] Bump neo4j-driver from 1.7.3 to 1.7.4 Bumps [neo4j-driver](https://github.com/neo4j/neo4j-javascript-driver) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/neo4j/neo4j-javascript-driver/releases) - [Commits](https://github.com/neo4j/neo4j-javascript-driver/compare/1.7.3...1.7.4) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 703997ee1..0eb343e0d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dotenv": "^7.0.0", "faker": "^4.1.0", "graphql-request": "^1.8.2", - "neo4j-driver": "^1.7.3", + "neo4j-driver": "^1.7.4", "npm-run-all": "^4.1.5" } } diff --git a/yarn.lock b/yarn.lock index 1d7745f03..6f8bc6716 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3473,10 +3473,10 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" -neo4j-driver@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.3.tgz#1c1108ab26b7243975f1b20045daf31d8f685207" - integrity sha512-UCNOFiQdouq14PvZGTr+psy657BJsBpO6O2cJpP+NprZnEF4APrDzAcydPZSFxE1nfooLNc50vfuZ0q54UyY2Q== +neo4j-driver@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/neo4j-driver/-/neo4j-driver-1.7.4.tgz#9661cf643b63818bff85e82c4691918e75098c1e" + integrity sha512-pbK1HbXh92zNSwMlXL8aNynkHohg9Jx/Tk+EewdJawGm8n8sKIY4NpRkp0nRw6RHvVBU3u9cQXt01ftFVe7j+A== dependencies: babel-runtime "^6.26.0" text-encoding "^0.6.4" From d255a165b4a38e821cc8ab4c4396624b291bf086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 7 May 2019 00:49:48 +0200 Subject: [PATCH 116/124] Delete unused folder cypres/cypress/ Where did that come from? @mattwr18 --- cypress/cypress/plugins/index.js | 17 ----------------- cypress/cypress/support/commands.js | 25 ------------------------- cypress/cypress/support/index.js | 20 -------------------- 3 files changed, 62 deletions(-) delete mode 100644 cypress/cypress/plugins/index.js delete mode 100644 cypress/cypress/support/commands.js delete mode 100644 cypress/cypress/support/index.js diff --git a/cypress/cypress/plugins/index.js b/cypress/cypress/plugins/index.js deleted file mode 100644 index fd170fba6..000000000 --- a/cypress/cypress/plugins/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} diff --git a/cypress/cypress/support/commands.js b/cypress/cypress/support/commands.js deleted file mode 100644 index c1f5a772e..000000000 --- a/cypress/cypress/support/commands.js +++ /dev/null @@ -1,25 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This is will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/cypress/cypress/support/index.js b/cypress/cypress/support/index.js deleted file mode 100644 index a24e4442d..000000000 --- a/cypress/cypress/support/index.js +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' -require('cypress-plugin-retries') -// Alternatively you can use CommonJS syntax: -// require('./commands') From a1b74bef001ca24de30fa7e4596dbeb2c90ea567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 7 May 2019 00:51:18 +0200 Subject: [PATCH 117/124] Configure 1 retry on Travis --- .travis.yml | 2 +- cypress/integration/common/report.js | 2 -- cypress/support/index.js | 5 +++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd43b771f..632786285 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ script: - docker-compose exec webapp yarn run lint - docker-compose exec webapp yarn run test --ci --verbose=false - docker-compose exec -d backend yarn run test:before:seeder - - yarn run cypress:run + - CYPRESS_RETRIES=1 yarn run cypress:run after_success: - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js index 519c8514b..2c8b848b4 100644 --- a/cypress/integration/common/report.js +++ b/cypress/integration/common/report.js @@ -1,6 +1,4 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps' -// intermittent failing tests -Cypress.env('RETRIES', 2) /* global cy */ diff --git a/cypress/support/index.js b/cypress/support/index.js index 3519487bf..195b0de7d 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -14,8 +14,13 @@ // *********************************************************** // Import commands.js using ES2015 syntax: + import './commands' import './factories' +// intermittent failing tests +import 'cypress-plugin-retries' + // Alternatively you can use CommonJS syntax: // require('./commands') + From af56b7aa8104918d856e90e2284a25d17a145ce0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 23:41:18 +0000 Subject: [PATCH 118/124] Bump dotenv from 7.0.0 to 8.0.0 Bumps [dotenv](https://github.com/motdotla/dotenv) from 7.0.0 to 8.0.0. - [Release notes](https://github.com/motdotla/dotenv/releases) - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v7.0.0...v8.0.0) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index cf865e5a5..3d866c203 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "cross-env": "^5.2.0", "cypress": "^3.2.0", "cypress-cucumber-preprocessor": "^1.11.0", - "dotenv": "^7.0.0", + "dotenv": "^8.0.0", "faker": "^4.1.0", "graphql-request": "^1.8.2", "neo4j-driver": "^1.7.4", diff --git a/yarn.lock b/yarn.lock index d6cf39004..31416a522 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2022,10 +2022,10 @@ domain-browser@^1.2.0: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -dotenv@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" - integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== +dotenv@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.0.0.tgz#ed310c165b4e8a97bb745b0a9d99c31bda566440" + integrity sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg== duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: version "0.1.4" From 42016178c5ccae7d1ac38aa8c46eef34c20d2ae4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 6 May 2019 23:42:06 +0000 Subject: [PATCH 119/124] Bump node-fetch from 2.4.1 to 2.5.0 in /backend Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.4.1 to 2.5.0. - [Release notes](https://github.com/bitinn/node-fetch/releases) - [Changelog](https://github.com/bitinn/node-fetch/blob/master/CHANGELOG.md) - [Commits](https://github.com/bitinn/node-fetch/compare/v2.4.1...v2.5.0) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 0b6c71198..5dd2bfcad 100644 --- a/backend/package.json +++ b/backend/package.json @@ -62,7 +62,7 @@ "ms": "~2.1.1", "neo4j-driver": "~1.7.4", "neo4j-graphql-js": "~2.4.2", - "node-fetch": "~2.4.1", + "node-fetch": "~2.5.0", "npm-run-all": "~4.1.5", "request": "~2.88.0", "sanitize-html": "~1.20.1", diff --git a/backend/yarn.lock b/backend/yarn.lock index 67b360c5d..78046b163 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -5634,10 +5634,10 @@ node-fetch@2.1.2: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= -node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.4.1.tgz#b2e38f1117b8acbedbe0524f041fb3177188255d" - integrity sha512-P9UbpFK87NyqBZzUuDBDz4f6Yiys8xm8j7ACDbi6usvFm6KItklQUKjeoqTrYS/S1k6I8oaOC2YLLDr/gg26Mw== +node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.5.0.tgz#8028c49fc1191bba56a07adc6e2a954644a48501" + integrity sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw== node-forge@~0.6.45: version "0.6.49" From 30a5e394c7f42ac7578c4bbbb86997da85d963cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 7 May 2019 04:52:37 +0000 Subject: [PATCH 120/124] Bump tiptap from 1.17.0 to 1.18.0 in /webapp Bumps [tiptap](https://github.com/scrumpy/tiptap) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/scrumpy/tiptap/releases) - [Commits](https://github.com/scrumpy/tiptap/compare/tiptap@1.17.0...tiptap@1.18.0) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 4f92f7339..61bf2b124 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -53,7 +53,7 @@ "nuxt-env": "~0.1.0", "stack-utils": "^1.0.2", "string-hash": "^1.1.3", - "tiptap": "^1.17.0", + "tiptap": "^1.18.0", "tiptap-extensions": "^1.17.0", "v-tooltip": "~2.0.2", "vue-count-to": "~1.0.13", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index c113ee306..f4ae858fd 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -10473,10 +10473,10 @@ tippy.js@^4.3.0: dependencies: popper.js "^1.14.7" -tiptap-commands@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.8.0.tgz#416942b60005fc23fc44ce7fdf4d2fe428b0d4c7" - integrity sha512-1/pOhQvCGDsoCFkOrAtoC7PBoXVEpRr8bYitPwZSNvxtqBdn+FaIfGJTHxYlxcY6skcHtlbeh9msb/W9lrz8eg== +tiptap-commands@^1.8.0, tiptap-commands@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.9.0.tgz#f01d227635b1169c249af20f6705ed5135e7741b" + integrity sha512-bvcWZwlzF6/qadnA/mQXjHq4QljcXnPZpNL6++RK7f7oKshvUsId2XEombt81iAKUpVTELFyq/bzfmDCi5vA4A== dependencies: prosemirror-commands "^1.0.7" prosemirror-inputrules "^1.0.1" @@ -10508,10 +10508,10 @@ tiptap-utils@^1.4.0: prosemirror-tables "^0.7.11" prosemirror-utils "^0.7.6" -tiptap@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.17.0.tgz#0dd7c8551378d43efbe1755504ba91d43ccb101d" - integrity sha512-8Qd/lkdgju2d7RdF5Ek6fzMpbPKGIt5YaFkRY0El2GMxt/4QcjX+6HWtcNl05fgIwF8uuPNy0CtSoeUi5KZGsQ== +tiptap@^1.17.0, tiptap@^1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.18.0.tgz#3b3fff322324e6691f31281d8c51b60eeaf5bf9a" + integrity sha512-PVKp9HSz4sO+mSPLwyEGImkKH9oplPWg9zd8xg3b6yeUUbMHhJLObcaceyxs++4548cZcINMPK7oxmZVy+KFZw== dependencies: prosemirror-commands "^1.0.7" prosemirror-dropcursor "^1.1.1" @@ -10521,7 +10521,7 @@ tiptap@^1.17.0: prosemirror-model "^1.7.0" prosemirror-state "^1.2.1" prosemirror-view "^1.8.9" - tiptap-commands "^1.8.0" + tiptap-commands "^1.9.0" tiptap-utils "^1.4.0" tmp@^0.0.33: From db1c22d92e621b418d35daffeae6f1c190ffff95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 7 May 2019 10:46:24 +0000 Subject: [PATCH 121/124] Bump dotenv from 7.0.0 to 8.0.0 in /backend Bumps [dotenv](https://github.com/motdotla/dotenv) from 7.0.0 to 8.0.0. - [Release notes](https://github.com/motdotla/dotenv/releases) - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v7.0.0...v8.0.0) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 5dd2bfcad..b4f951fe7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,7 +45,7 @@ "cross-env": "~5.2.0", "date-fns": "2.0.0-alpha.27", "debug": "~4.1.1", - "dotenv": "~7.0.0", + "dotenv": "~8.0.0", "express": "~4.16.4", "faker": "~4.1.0", "graphql": "~14.2.1", diff --git a/backend/yarn.lock b/backend/yarn.lock index 78046b163..ee462347f 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2800,10 +2800,10 @@ dotenv@^0.4.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-0.4.0.tgz#f6fb351363c2d92207245c737802c9ab5ae1495a" integrity sha1-9vs1E2PC2SIHJFxzeALJq1rhSVo= -dotenv@~7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" - integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== +dotenv@~8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.0.0.tgz#ed310c165b4e8a97bb745b0a9d99c31bda566440" + integrity sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg== duplexer3@^0.1.4: version "0.1.4" From d538b4e1955b5d6ef5c30d656752d17d14c65ca2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 7 May 2019 10:47:00 +0000 Subject: [PATCH 122/124] Bump tiptap-extensions from 1.17.0 to 1.18.1 in /webapp Bumps [tiptap-extensions](https://github.com/scrumpy/tiptap) from 1.17.0 to 1.18.1. - [Release notes](https://github.com/scrumpy/tiptap/releases) - [Commits](https://github.com/scrumpy/tiptap/compare/tiptap-extensions@1.17.0...tiptap-extensions@1.18.1) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 70 ++++++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 61bf2b124..207a1efa2 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -54,7 +54,7 @@ "stack-utils": "^1.0.2", "string-hash": "^1.1.3", "tiptap": "^1.18.0", - "tiptap-extensions": "^1.17.0", + "tiptap-extensions": "^1.18.1", "v-tooltip": "~2.0.2", "vue-count-to": "~1.0.13", "vue-izitoast": "1.1.2", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index f4ae858fd..5e355458b 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -8893,6 +8893,13 @@ proper-lockfile@^4.1.1: retry "^0.12.0" signal-exit "^3.0.2" +prosemirror-collab@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.1.1.tgz#c8f5d951abaeac8a80818b6bd960f5a392b35b3f" + integrity sha512-BpXIB3WBD7UvgxuiasKOxlAZ78TTOdW+SQN4bbJan995tVx/wM/OZXtRJebS+tSWWAbRisHaO3ciFo732vuvdA== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-commands@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz#e5a2ba821e29ea7065c88277fe2c3d7f6b0b9d37" @@ -8961,7 +8968,7 @@ prosemirror-schema-list@^1.0.3: prosemirror-model "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-state@^1.0.0, prosemirror-state@^1.2.1, prosemirror-state@^1.2.2: +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.2.2.tgz#8df26d95fd6fd327c0f9984a760e84d863204154" integrity sha512-j8aC/kf9BJSCQau485I/9pj39XQoce+TqH5xzekT7WWFARTsRYFLJtiXBcCKakv1VSeev+sC3bJP0pLfz7Ft8g== @@ -8980,7 +8987,7 @@ prosemirror-tables@^0.7.11: prosemirror-transform "^1.0.0" prosemirror-view "^1.0.0" -prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0: +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.3.tgz#28cfdf1f9ee514edc40466be7b7db39eed545fdf" integrity sha512-1O6Di5lOL1mp4nuCnQNkHY7l2roIW5y8RH4ZG3hMYmkmDEWzTaFFnxxAAHsE5ipGLBSRcTlP7SsDhYBIdSuLpQ== @@ -8992,10 +8999,10 @@ prosemirror-utils@^0.7.6: resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.7.6.tgz#c462ddfbf2452e56e4b25d1f02b34caccddb0f33" integrity sha512-vzsCBTiJ56R3nRDpIJnKOJzsZP7KFO8BkXk7zvQgQiXpml2o/djPCRhuyaFc7VTqSHlLPQHVI1feTLAwHp+prQ== -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.8.9: - version "1.8.9" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.8.9.tgz#9303def925eba0a8ce4589e64b4a011eaccfc1e0" - integrity sha512-K3/z7qDR6rEMH/gKXqu5pmjmtyhtuTWeQyselK6HMp3jbn1UANU4CfdU/awQT+zbRjv4ZJXGb9lxnuNmdUQS3Q== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.9.1.tgz#3f987d55586909d4156a76b32918338655e32699" + integrity sha512-/8kQ9/CZaaDoQEqBfgyW2ex/SnuqkMim20oMpLwHYuuxfe2K30kymQYP1pEKmTVK1K97s7uE78YPyOrUU+VeuQ== dependencies: prosemirror-model "^1.1.0" prosemirror-state "^1.0.0" @@ -10473,45 +10480,48 @@ tippy.js@^4.3.0: dependencies: popper.js "^1.14.7" -tiptap-commands@^1.8.0, tiptap-commands@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.9.0.tgz#f01d227635b1169c249af20f6705ed5135e7741b" - integrity sha512-bvcWZwlzF6/qadnA/mQXjHq4QljcXnPZpNL6++RK7f7oKshvUsId2XEombt81iAKUpVTELFyq/bzfmDCi5vA4A== +tiptap-commands@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.9.1.tgz#be0c3fd6bbe3a984cfc6bef71d443f813a106735" + integrity sha512-/isKG7s518jofj75qvN1lru7ohx6Gtmqc6e9jAGH43Xk60FK0K2zgnaA36pHBHKBQNFK6mXyfkrJCmsf5dnuNQ== dependencies: prosemirror-commands "^1.0.7" prosemirror-inputrules "^1.0.1" prosemirror-schema-list "^1.0.3" prosemirror-state "^1.2.2" - tiptap-utils "^1.4.0" + tiptap-utils "^1.4.1" -tiptap-extensions@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.17.0.tgz#caa5ea47623f2c13b15b449001a061fbd96eeb32" - integrity sha512-ljlNG5rIpbggSO5FU27NAjfeDN3Kz18GqfNGoEOHHgvC5JDIj6x5QyiPOvCRNpuWN4t4XEMUkq+F8KjI5jceKw== +tiptap-extensions@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.18.1.tgz#7d63da49190c1b622a9adf66263d90631367bcd9" + integrity sha512-Dx4OwnoYohcYSuOx8P6UoY19x5BFinlkwCEEau8MzNGXVzrtCTw9c8vAIiooHobW6haEvYlb6AKWp0z5xDWsRw== dependencies: lowlight "^1.11.0" + prosemirror-collab "^1.1.1" prosemirror-history "^1.0.4" + prosemirror-model "^1.7.0" prosemirror-state "^1.2.2" prosemirror-tables "^0.7.11" + prosemirror-transform "^1.1.3" prosemirror-utils "^0.7.6" - prosemirror-view "^1.8.9" - tiptap "^1.17.0" - tiptap-commands "^1.8.0" + prosemirror-view "^1.9.1" + tiptap "^1.18.1" + tiptap-commands "^1.9.1" -tiptap-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.4.0.tgz#5449bc80afcc54730bd3010b6d5fd8cd6e62160c" - integrity sha512-uPG6LRzFUhkHl3UUYhXj1vA17c43ttZPKwkSkpzbrLBBl661UAAv7Z8grvOFBY9gVjzKeg1uIncRH2al2A3SQg== +tiptap-utils@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.4.1.tgz#fbfb964bed2216f8a8cfed9131ffd2239a358faf" + integrity sha512-fBB70PtCPgZ3nSyzwWKU2ZoqW8QzNBSfCgXgCgYlvVOBi8R9H3c4FlCU3aIPictvY7FoQtw6xC2TlD3/RkUc5w== dependencies: prosemirror-model "^1.7.0" prosemirror-state "^1.2.2" prosemirror-tables "^0.7.11" prosemirror-utils "^0.7.6" -tiptap@^1.17.0, tiptap@^1.18.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.18.0.tgz#3b3fff322324e6691f31281d8c51b60eeaf5bf9a" - integrity sha512-PVKp9HSz4sO+mSPLwyEGImkKH9oplPWg9zd8xg3b6yeUUbMHhJLObcaceyxs++4548cZcINMPK7oxmZVy+KFZw== +tiptap@^1.18.0, tiptap@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.18.1.tgz#ac6d302a6a766b85a16579ac15e2a4c5d5751fba" + integrity sha512-hgDedZuEKHp1w45jNzg65H2nR9eNVlbsSr4cbzvNMlPUYyH1Aby/IXroxFZrwqKjsxhVZ0KSk3AVGk5GaLi1rg== dependencies: prosemirror-commands "^1.0.7" prosemirror-dropcursor "^1.1.1" @@ -10519,10 +10529,10 @@ tiptap@^1.17.0, tiptap@^1.18.0: prosemirror-inputrules "^1.0.1" prosemirror-keymap "^1.0.1" prosemirror-model "^1.7.0" - prosemirror-state "^1.2.1" - prosemirror-view "^1.8.9" - tiptap-commands "^1.9.0" - tiptap-utils "^1.4.0" + prosemirror-state "^1.2.2" + prosemirror-view "^1.9.1" + tiptap-commands "^1.9.1" + tiptap-utils "^1.4.1" tmp@^0.0.33: version "0.0.33" From 264ba2aaf7ee4b1b7452f5ffee72b1c7505c0f51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 7 May 2019 10:47:20 +0000 Subject: [PATCH 123/124] Bump babel-jest from 24.7.1 to 24.8.0 in /backend Bumps [babel-jest](https://github.com/facebook/jest/tree/HEAD/packages/babel-jest) from 24.7.1 to 24.8.0. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/commits/v24.8.0/packages/babel-jest) Signed-off-by: dependabot[bot] --- backend/package.json | 2 +- backend/yarn.lock | 131 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/backend/package.json b/backend/package.json index 5dd2bfcad..d9729c09b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -81,7 +81,7 @@ "apollo-server-testing": "~2.4.8", "babel-core": "~7.0.0-0", "babel-eslint": "~10.0.1", - "babel-jest": "~24.7.1", + "babel-jest": "~24.8.0", "chai": "~4.2.0", "cucumber": "~5.1.0", "eslint": "~5.16.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index 78046b163..8b1e84d9e 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -813,6 +813,15 @@ jest-message-util "^24.7.1" jest-mock "^24.7.0" +"@jest/fake-timers@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1" + integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw== + dependencies: + "@jest/types" "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + "@jest/reporters@^24.7.1": version "24.7.1" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.7.1.tgz#38ac0b096cd691bbbe3051ddc25988d42e37773a" @@ -857,6 +866,15 @@ "@jest/types" "^24.7.0" "@types/istanbul-lib-coverage" "^2.0.0" +"@jest/test-result@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" + integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng== + dependencies: + "@jest/console" "^24.7.1" + "@jest/types" "^24.8.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@jest/test-sequencer@^24.7.1": version "24.7.1" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz#9c18e428e1ad945fa74f6233a9d35745ca0e63e0" @@ -867,33 +885,34 @@ jest-runner "^24.7.1" jest-runtime "^24.7.1" -"@jest/transform@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.7.1.tgz#872318f125bcfab2de11f53b465ab1aa780789c2" - integrity sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw== +"@jest/transform@^24.7.1", "@jest/transform@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" + integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" babel-plugin-istanbul "^5.1.0" chalk "^2.0.1" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.15" - jest-haste-map "^24.7.1" + jest-haste-map "^24.8.0" jest-regex-util "^24.3.0" - jest-util "^24.7.1" + jest-util "^24.8.0" micromatch "^3.1.10" realpath-native "^1.1.0" slash "^2.0.0" source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.7.0": - version "24.7.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" - integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== +"@jest/types@^24.7.0", "@jest/types@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" + integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^12.0.9" "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": @@ -1053,11 +1072,31 @@ resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-14.0.3.tgz#389e2e5b83ecdb376d9f98fae2094297bc112c1c" integrity sha512-TcFkpEjcQK7w8OcrQcd7iIBPjU0rdyi3ldj6d0iJ4PPSzbWqPBvXj9KSwO14hTOX2dm9RoiH7VuxksJLNYdXUQ== +"@types/istanbul-lib-coverage@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + "@types/istanbul-lib-coverage@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85" integrity sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg== +"@types/istanbul-lib-report@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" + integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + "@types/long@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" @@ -1785,13 +1824,13 @@ babel-eslint@~10.0.1: eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" -babel-jest@^24.7.1, babel-jest@~24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.7.1.tgz#73902c9ff15a7dfbdc9994b0b17fcefd96042178" - integrity sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg== +babel-jest@^24.7.1, babel-jest@~24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" + integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== dependencies: - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" "@types/babel__core" "^7.1.0" babel-plugin-istanbul "^5.1.0" babel-preset-jest "^24.6.0" @@ -4685,6 +4724,25 @@ jest-haste-map@^24.7.1: optionalDependencies: fsevents "^1.2.7" +jest-haste-map@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800" + integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ== + dependencies: + "@jest/types" "^24.8.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.4.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + jest-jasmine2@^24.7.1: version "24.7.1" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz#01398686dabe46553716303993f3be62e5d9d818" @@ -4738,6 +4796,20 @@ jest-message-util@^24.7.1: slash "^2.0.0" stack-utils "^1.0.1" +jest-message-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b" + integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + jest-mock@^24.7.0: version "24.7.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.7.0.tgz#e49ce7262c12d7f5897b0d8af77f6db8e538023b" @@ -4745,6 +4817,13 @@ jest-mock@^24.7.0: dependencies: "@jest/types" "^24.7.0" +jest-mock@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" + integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A== + dependencies: + "@jest/types" "^24.8.0" + jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" @@ -4870,6 +4949,24 @@ jest-util@^24.7.1: slash "^2.0.0" source-map "^0.6.0" +jest-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" + integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== + dependencies: + "@jest/console" "^24.7.1" + "@jest/fake-timers" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + jest-validate@^24.7.0: version "24.7.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.7.0.tgz#70007076f338528ee1b1c8a8258b1b0bb982508d" From 90603007f31ee5dca89a9b930a28c7dd196f890d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 7 May 2019 10:47:25 +0000 Subject: [PATCH 124/124] Bump jest from 24.7.1 to 24.8.0 in /webapp Bumps [jest](https://github.com/facebook/jest) from 24.7.1 to 24.8.0. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/compare/v24.7.1...v24.8.0) Signed-off-by: dependabot[bot] --- webapp/package.json | 2 +- webapp/yarn.lock | 594 +++++++++++++++++--------------------------- 2 files changed, 234 insertions(+), 362 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 61bf2b124..b4fec8570 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -77,7 +77,7 @@ "eslint-plugin-prettier": "~3.0.1", "eslint-plugin-vue": "~5.2.2", "fuse.js": "^3.4.4", - "jest": "~24.7.1", + "jest": "~24.8.0", "node-sass": "~4.12.0", "nodemon": "~1.19.0", "prettier": "~1.14.3", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index f4ae858fd..e9a4d19b3 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -779,32 +779,32 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/core@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.7.1.tgz#6707f50db238d0c5988860680e2e414df0032024" - integrity sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA== +"@jest/core@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b" + integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A== dependencies: "@jest/console" "^24.7.1" - "@jest/reporters" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/reporters" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" ansi-escapes "^3.0.0" chalk "^2.0.1" exit "^0.1.2" graceful-fs "^4.1.15" - jest-changed-files "^24.7.0" - jest-config "^24.7.1" - jest-haste-map "^24.7.1" - jest-message-util "^24.7.1" + jest-changed-files "^24.8.0" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" jest-regex-util "^24.3.0" - jest-resolve-dependencies "^24.7.1" - jest-runner "^24.7.1" - jest-runtime "^24.7.1" - jest-snapshot "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" - jest-watcher "^24.7.1" + jest-resolve-dependencies "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + jest-watcher "^24.8.0" micromatch "^3.1.10" p-each-series "^1.0.0" pirates "^4.0.1" @@ -812,24 +812,15 @@ rimraf "^2.5.4" strip-ansi "^5.0.0" -"@jest/environment@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.7.1.tgz#9b9196bc737561f67ac07817d4c5ece772e33135" - integrity sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw== +"@jest/environment@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac" + integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw== dependencies: - "@jest/fake-timers" "^24.7.1" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" - jest-mock "^24.7.0" - -"@jest/fake-timers@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.7.1.tgz#56e5d09bdec09ee81050eaff2794b26c71d19db2" - integrity sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA== - dependencies: - "@jest/types" "^24.7.0" - jest-message-util "^24.7.1" - jest-mock "^24.7.0" + "@jest/fake-timers" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" "@jest/fake-timers@^24.8.0": version "24.8.0" @@ -840,26 +831,27 @@ jest-message-util "^24.8.0" jest-mock "^24.8.0" -"@jest/reporters@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.7.1.tgz#38ac0b096cd691bbbe3051ddc25988d42e37773a" - integrity sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw== +"@jest/reporters@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729" + integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw== dependencies: - "@jest/environment" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.2" - istanbul-api "^2.1.1" istanbul-lib-coverage "^2.0.2" istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" istanbul-lib-source-maps "^3.0.1" - jest-haste-map "^24.7.1" - jest-resolve "^24.7.1" - jest-runtime "^24.7.1" - jest-util "^24.7.1" + istanbul-reports "^2.1.1" + jest-haste-map "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" jest-worker "^24.6.0" node-notifier "^5.2.1" slash "^2.0.0" @@ -875,15 +867,6 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/test-result@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.7.1.tgz#19eacdb29a114300aed24db651e5d975f08b6bbe" - integrity sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg== - dependencies: - "@jest/console" "^24.7.1" - "@jest/types" "^24.7.0" - "@types/istanbul-lib-coverage" "^2.0.0" - "@jest/test-result@^24.8.0": version "24.8.0" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" @@ -893,17 +876,17 @@ "@jest/types" "^24.8.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-sequencer@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz#9c18e428e1ad945fa74f6233a9d35745ca0e63e0" - integrity sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA== +"@jest/test-sequencer@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b" + integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg== dependencies: - "@jest/test-result" "^24.7.1" - jest-haste-map "^24.7.1" - jest-runner "^24.7.1" - jest-runtime "^24.7.1" + "@jest/test-result" "^24.8.0" + jest-haste-map "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" -"@jest/transform@^24.7.1", "@jest/transform@^24.8.0": +"@jest/transform@^24.8.0": version "24.8.0" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA== @@ -924,7 +907,7 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.7.0", "@jest/types@^24.8.0": +"@jest/types@^24.8.0": version "24.8.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== @@ -2139,13 +2122,6 @@ apollo-utilities@1.2.1, apollo-utilities@^1.0.1, apollo-utilities@^1.0.27, apoll ts-invariant "^0.2.1" tslib "^1.9.3" -append-transform@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" - integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== - dependencies: - default-require-extensions "^2.0.0" - aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -2281,7 +2257,7 @@ async-retry@^1.2.1: dependencies: retry "0.12.0" -async@^2.1.4, async@^2.3.0, async@^2.5.0, async@^2.6.1: +async@^2.1.4, async@^2.3.0, async@^2.5.0: version "2.6.2" resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== @@ -2361,7 +2337,7 @@ babel-eslint@~10.0.1: eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" -babel-jest@^24.7.1, babel-jest@~24.8.0: +babel-jest@^24.8.0, babel-jest@~24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== @@ -3202,11 +3178,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -compare-versions@^3.2.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" - integrity sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg== - component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -3849,13 +3820,6 @@ deepmerge@^3.2.0: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e" integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow== -default-require-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" - integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc= - dependencies: - strip-bom "^3.0.0" - defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -4512,16 +4476,16 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.7.1.tgz#d91defbab4e627470a152feaf35b3c31aa1c7c14" - integrity sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw== +expect@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d" + integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" ansi-styles "^3.2.0" - jest-get-type "^24.3.0" - jest-matcher-utils "^24.7.0" - jest-message-util "^24.7.1" + jest-get-type "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" jest-regex-util "^24.3.0" express@^4.16.3, express@^4.16.4, express@~4.16.4: @@ -4704,14 +4668,6 @@ file-loader@^3.0.1: loader-utils "^1.0.2" schema-utils "^1.0.0" -fileset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" - integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= - dependencies: - glob "^7.0.3" - minimatch "^3.0.3" - filesize@^3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" @@ -6000,38 +5956,12 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-api@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0" - integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw== - dependencies: - async "^2.6.1" - compare-versions "^3.2.1" - fileset "^2.0.3" - istanbul-lib-coverage "^2.0.3" - istanbul-lib-hook "^2.0.3" - istanbul-lib-instrument "^3.1.0" - istanbul-lib-report "^2.0.4" - istanbul-lib-source-maps "^3.0.2" - istanbul-reports "^2.1.1" - js-yaml "^3.12.0" - make-dir "^1.3.0" - minimatch "^3.0.4" - once "^1.4.0" - istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba" integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw== -istanbul-lib-hook@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz#e0e581e461c611be5d0e5ef31c5f0109759916fb" - integrity sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA== - dependencies: - append-transform "^1.0.0" - -istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0: +istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971" integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA== @@ -6053,7 +5983,7 @@ istanbul-lib-report@^2.0.4: make-dir "^1.3.0" supports-color "^6.0.0" -istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2: +istanbul-lib-source-maps@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156" integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ== @@ -6081,66 +6011,66 @@ izitoast@^1.3.0: resolved "https://registry.yarnpkg.com/izitoast/-/izitoast-1.4.0.tgz#1aa4e3408b7159fba743506af66d8be54fd929fb" integrity sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w== -jest-changed-files@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.7.0.tgz#39d723a11b16ed7b373ac83adc76a69464b0c4fa" - integrity sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw== +jest-changed-files@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b" + integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" execa "^1.0.0" throat "^4.0.0" -jest-cli@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.7.1.tgz#6093a539073b6f4953145abeeb9709cd621044f1" - integrity sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ== +jest-cli@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989" + integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA== dependencies: - "@jest/core" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/core" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" exit "^0.1.2" import-local "^2.0.0" is-ci "^2.0.0" - jest-config "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" + jest-config "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" prompts "^2.0.1" realpath-native "^1.1.0" yargs "^12.0.2" -jest-config@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.7.1.tgz#6c1dd4db82a89710a3cf66bdba97827c9a1cf052" - integrity sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g== +jest-config@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f" + integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^24.7.1" - "@jest/types" "^24.7.0" - babel-jest "^24.7.1" + "@jest/test-sequencer" "^24.8.0" + "@jest/types" "^24.8.0" + babel-jest "^24.8.0" chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^24.7.1" - jest-environment-node "^24.7.1" - jest-get-type "^24.3.0" - jest-jasmine2 "^24.7.1" + jest-environment-jsdom "^24.8.0" + jest-environment-node "^24.8.0" + jest-get-type "^24.8.0" + jest-jasmine2 "^24.8.0" jest-regex-util "^24.3.0" - jest-resolve "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" + jest-resolve "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" micromatch "^3.1.10" - pretty-format "^24.7.0" + pretty-format "^24.8.0" realpath-native "^1.1.0" -jest-diff@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.7.0.tgz#5d862899be46249754806f66e5729c07fcb3580f" - integrity sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg== +jest-diff@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172" + integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g== dependencies: chalk "^2.0.1" diff-sequences "^24.3.0" - jest-get-type "^24.3.0" - pretty-format "^24.7.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" jest-docblock@^24.3.0: version "24.3.0" @@ -6149,63 +6079,44 @@ jest-docblock@^24.3.0: dependencies: detect-newline "^2.1.0" -jest-each@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.7.1.tgz#fcc7dda4147c28430ad9fb6dc7211cd17ab54e74" - integrity sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA== +jest-each@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775" + integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" - jest-get-type "^24.3.0" - jest-util "^24.7.1" - pretty-format "^24.7.0" + jest-get-type "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" -jest-environment-jsdom@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz#a40e004b4458ebeb8a98082df135fd501b9fbbd6" - integrity sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg== +jest-environment-jsdom@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" + integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ== dependencies: - "@jest/environment" "^24.7.1" - "@jest/fake-timers" "^24.7.1" - "@jest/types" "^24.7.0" - jest-mock "^24.7.0" - jest-util "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" jsdom "^11.5.1" -jest-environment-node@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.7.1.tgz#fa2c047a31522a48038d26ee4f7c8fd9c1ecfe12" - integrity sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA== +jest-environment-node@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231" + integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q== dependencies: - "@jest/environment" "^24.7.1" - "@jest/fake-timers" "^24.7.1" - "@jest/types" "^24.7.0" - jest-mock "^24.7.0" - jest-util "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" -jest-get-type@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da" - integrity sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow== - -jest-haste-map@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.7.1.tgz#772e215cd84080d4bbcb759cfb668ad649a21471" - integrity sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw== - dependencies: - "@jest/types" "^24.7.0" - anymatch "^2.0.0" - fb-watchman "^2.0.0" - graceful-fs "^4.1.15" - invariant "^2.2.4" - jest-serializer "^24.4.0" - jest-util "^24.7.1" - jest-worker "^24.6.0" - micromatch "^3.1.10" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^1.2.7" +jest-get-type@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc" + integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ== jest-haste-map@^24.8.0: version "24.8.0" @@ -6226,58 +6137,44 @@ jest-haste-map@^24.8.0: optionalDependencies: fsevents "^1.2.7" -jest-jasmine2@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz#01398686dabe46553716303993f3be62e5d9d818" - integrity sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w== +jest-jasmine2@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898" + integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" co "^4.6.0" - expect "^24.7.1" + expect "^24.8.0" is-generator-fn "^2.0.0" - jest-each "^24.7.1" - jest-matcher-utils "^24.7.0" - jest-message-util "^24.7.1" - jest-runtime "^24.7.1" - jest-snapshot "^24.7.1" - jest-util "^24.7.1" - pretty-format "^24.7.0" + jest-each "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" throat "^4.0.0" -jest-leak-detector@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz#323ff93ed69be12e898f5b040952f08a94288ff9" - integrity sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ== +jest-leak-detector@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980" + integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g== dependencies: - pretty-format "^24.7.0" + pretty-format "^24.8.0" -jest-matcher-utils@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz#bbee1ff37bc8b2e4afcaabc91617c1526af4bcd4" - integrity sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg== +jest-matcher-utils@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495" + integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw== dependencies: chalk "^2.0.1" - jest-diff "^24.7.0" - jest-get-type "^24.3.0" - pretty-format "^24.7.0" - -jest-message-util@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.7.1.tgz#f1dc3a6c195647096a99d0f1dadbc447ae547018" - integrity sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" - "@types/stack-utils" "^1.0.1" - chalk "^2.0.1" - micromatch "^3.1.10" - slash "^2.0.0" - stack-utils "^1.0.1" + jest-diff "^24.8.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" jest-message-util@^24.8.0: version "24.8.0" @@ -6293,13 +6190,6 @@ jest-message-util@^24.8.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-mock@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.7.0.tgz#e49ce7262c12d7f5897b0d8af77f6db8e538023b" - integrity sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng== - dependencies: - "@jest/types" "^24.7.0" - jest-mock@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" @@ -6317,75 +6207,75 @@ jest-regex-util@^24.3.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== -jest-resolve-dependencies@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz#cf93bbef26999488a96a2b2012f9fe7375aa378f" - integrity sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg== +jest-resolve-dependencies@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0" + integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" jest-regex-util "^24.3.0" - jest-snapshot "^24.7.1" + jest-snapshot "^24.8.0" -jest-resolve@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.7.1.tgz#e4150198299298380a75a9fd55043fa3b9b17fde" - integrity sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw== +jest-resolve@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f" + integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" browser-resolve "^1.11.3" chalk "^2.0.1" jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-runner@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.7.1.tgz#41c8a02a06aa23ea82d8bffd69d7fa98d32f85bf" - integrity sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw== +jest-runner@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb" + integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow== dependencies: "@jest/console" "^24.7.1" - "@jest/environment" "^24.7.1" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" chalk "^2.4.2" exit "^0.1.2" graceful-fs "^4.1.15" - jest-config "^24.7.1" + jest-config "^24.8.0" jest-docblock "^24.3.0" - jest-haste-map "^24.7.1" - jest-jasmine2 "^24.7.1" - jest-leak-detector "^24.7.0" - jest-message-util "^24.7.1" - jest-resolve "^24.7.1" - jest-runtime "^24.7.1" - jest-util "^24.7.1" + jest-haste-map "^24.8.0" + jest-jasmine2 "^24.8.0" + jest-leak-detector "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" jest-worker "^24.6.0" source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.7.1.tgz#2ffd70b22dd03a5988c0ab9465c85cdf5d25c597" - integrity sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A== +jest-runtime@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620" + integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA== dependencies: "@jest/console" "^24.7.1" - "@jest/environment" "^24.7.1" + "@jest/environment" "^24.8.0" "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" "@types/yargs" "^12.0.2" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.1.15" - jest-config "^24.7.1" - jest-haste-map "^24.7.1" - jest-message-util "^24.7.1" - jest-mock "^24.7.0" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" jest-regex-util "^24.3.0" - jest-resolve "^24.7.1" - jest-snapshot "^24.7.1" - jest-util "^24.7.1" - jest-validate "^24.7.0" + jest-resolve "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" realpath-native "^1.1.0" slash "^2.0.0" strip-bom "^3.0.0" @@ -6396,42 +6286,24 @@ jest-serializer@^24.4.0: resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3" integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q== -jest-snapshot@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.7.1.tgz#bd5a35f74aedff070975e9e9c90024f082099568" - integrity sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA== +jest-snapshot@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6" + integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" chalk "^2.0.1" - expect "^24.7.1" - jest-diff "^24.7.0" - jest-matcher-utils "^24.7.0" - jest-message-util "^24.7.1" - jest-resolve "^24.7.1" + expect "^24.8.0" + jest-diff "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^24.7.0" + pretty-format "^24.8.0" semver "^5.5.0" -jest-util@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.7.1.tgz#b4043df57b32a23be27c75a2763d8faf242038ff" - integrity sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A== - dependencies: - "@jest/console" "^24.7.1" - "@jest/fake-timers" "^24.7.1" - "@jest/source-map" "^24.3.0" - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" - callsites "^3.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.15" - is-ci "^2.0.0" - mkdirp "^0.5.1" - slash "^2.0.0" - source-map "^0.6.0" - jest-util@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" @@ -6450,29 +6322,29 @@ jest-util@^24.8.0: slash "^2.0.0" source-map "^0.6.0" -jest-validate@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.7.0.tgz#70007076f338528ee1b1c8a8258b1b0bb982508d" - integrity sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA== +jest-validate@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849" + integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" camelcase "^5.0.0" chalk "^2.0.1" - jest-get-type "^24.3.0" + jest-get-type "^24.8.0" leven "^2.1.0" - pretty-format "^24.7.0" + pretty-format "^24.8.0" -jest-watcher@^24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.7.1.tgz#e161363d7f3f4e1ef3d389b7b3a0aad247b673f5" - integrity sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw== +jest-watcher@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4" + integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw== dependencies: - "@jest/test-result" "^24.7.1" - "@jest/types" "^24.7.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" "@types/yargs" "^12.0.9" ansi-escapes "^3.0.0" chalk "^2.0.1" - jest-util "^24.7.1" + jest-util "^24.8.0" string-length "^2.0.0" jest-worker@^24.6.0: @@ -6483,13 +6355,13 @@ jest-worker@^24.6.0: merge-stream "^1.0.1" supports-color "^6.1.0" -jest@~24.7.1: - version "24.7.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-24.7.1.tgz#0d94331cf510c75893ee32f87d7321d5bf8f2501" - integrity sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA== +jest@~24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081" + integrity sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg== dependencies: import-local "^2.0.0" - jest-cli "^24.7.1" + jest-cli "^24.8.0" joi@^14.3.0: version "14.3.1" @@ -7204,7 +7076,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -8836,12 +8708,12 @@ pretty-error@^2.0.2: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.7.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.7.0.tgz#d23106bc2edcd776079c2daa5da02bcb12ed0c10" - integrity sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA== +pretty-format@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" + integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== dependencies: - "@jest/types" "^24.7.0" + "@jest/types" "^24.8.0" ansi-regex "^4.0.0" ansi-styles "^3.2.0" react-is "^16.8.4"