diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19aaf3301..0eb90a824 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,9 +4,9 @@ Thanks so much for thinking of contributing to the Human Connection project, we ## Getting Set Up -Instructions for how to install all the necessary software can be found in our [documentation](https://docs.human-connection.org/human-connection/) +Instructions for how to install all the necessary software can be found in our [documentation](https://docs.human-connection.org/human-connection/). -We recommend that new folks should ideally work together with an existing developer. Please join our discord instance to chat with developers or just ask them in tickets in [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f/boards?repos=152252353): +We recommend that new folks should ideally work together with an existing developer. Please join our [discord](https://discord.gg/6ub73U3) instance to chat with developers or just ask them in tickets in [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f/boards?repos=152252353): ![](https://dl.dropbox.com/s/vbmcihkduy9dhko/Screenshot%202019-01-03%2015.50.11.png?dl=0) @@ -17,7 +17,7 @@ Here are some general notes on our development flow: * Currently operating in two week sprints * We are using ZenHub to coordinate * estimating time per issue is the crucial feature of [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f) that Github does not have - * "up-for-grabs" links to [Github project](https://github.com/orgs/Human-Connection/projects/10?card_filter_query=label%3A"good+first+issue) + * "up-for-grabs" links to [Github project](https://github.com/Human-Connection/Human-Connection/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) * ordering on ZenHub not necessarily reflected on github projects * AgileVentures run open pairing sessions at 10:30am UTC each week on Tuesdays and Thursdays * Core team @@ -51,19 +51,19 @@ But what do we do when waiting for merge into master \(wanting to keep PRs small * solutions * 1\) put 2nd PR into branch that the first PR is hitting - but requires update after merging * 2\) prefer to leave exiting PR until it can be reviewed, and instead go and work on some other part of the codebase that is not impacted by the first PR - + ### Code Review -* Github setting in place - at least one review is required to merge +* Github setting in place - at least one review is required to merge - in principle anyone (who is not the PR owner) can review - but often it will be the core developers (Robert, Ulf, Greg, Wolfgang?) - once there is a review, and presuming no requested changes, PR opener can merge * CI/tests - - the CI needs to pass + - the CI needs to pass - linting <-- autofix? - tests (unit, feature) (backend, frontend) - codecoverage - + ## Notes question: when you want to pick a task - \(find out priority\) - is it in discord? is it in AV slack? --> Robert says you can always ask in discord - group channels are the best @@ -77,4 +77,3 @@ Matt makes point that new stories will have to be taken off the "New Issues" and Robert notes that everyone is invited to join the kickoff meetings Robert - difference between "important" \(creates a lot of value\) and "beginner friendly" \(easy to implement\) - diff --git a/README.md b/README.md index ac7d2a024..411f7d842 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Thank you lokalise for providing us with a premium account :raised_hands:. ## Developer Chat Join our friendly open-source community on [Discord](https://discord.gg/6ub73U3) :heart_eyes_cat: -Just introduce yourself at `#user-presentation` and mention `@@Mentor` to get you onboard :neckbeard: +Just introduce yourself at `#introduce-yourself` and mention `@@Mentor` to get you onboard :neckbeard: Check out the [contribution guideline](./CONTRIBUTING.md), too! diff --git a/backend/Dockerfile b/backend/Dockerfile index 2e8667461..935077c98 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.5-alpine as base +FROM node:12.6-alpine as base LABEL Description="Backend of the Social Network Human-Connection.org" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)" EXPOSE 4000 diff --git a/backend/package.json b/backend/package.json index 88726f959..4339f32ed 100644 --- a/backend/package.json +++ b/backend/package.json @@ -62,7 +62,7 @@ "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", "graphql-middleware": "~3.0.2", - "graphql-shield": "~6.0.2", + "graphql-shield": "~6.0.3", "graphql-tag": "~2.10.1", "graphql-yoga": "~1.18.0", "helmet": "~3.18.0", @@ -84,11 +84,11 @@ "wait-on": "~3.2.0" }, "devDependencies": { - "@babel/cli": "~7.4.4", - "@babel/core": "~7.4.5", - "@babel/node": "~7.4.5", + "@babel/cli": "~7.5.0", + "@babel/core": "~7.5.0", + "@babel/node": "~7.5.0", "@babel/plugin-proposal-throw-expressions": "^7.2.0", - "@babel/preset-env": "~7.4.5", + "@babel/preset-env": "~7.5.2", "@babel/register": "~7.4.4", "apollo-server-testing": "~2.6.7", "babel-core": "~7.0.0-0", @@ -100,7 +100,7 @@ "eslint-config-prettier": "~6.0.0", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.18.0", - "eslint-plugin-jest": "~22.7.1", + "eslint-plugin-jest": "~22.7.2", "eslint-plugin-node": "~9.1.0", "eslint-plugin-prettier": "~3.1.0", "eslint-plugin-promise": "~4.2.1", diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.js b/backend/src/middleware/handleHtmlContent/handleContentData.js new file mode 100644 index 000000000..6519ddae7 --- /dev/null +++ b/backend/src/middleware/handleHtmlContent/handleContentData.js @@ -0,0 +1,69 @@ +import extractMentionedUsers from './notifications/extractMentionedUsers' +import extractHashtags from './hashtags/extractHashtags' + +const notify = async (postId, idsOfMentionedUsers, context) => { + const session = context.driver.session() + const createdAt = new Date().toISOString() + const cypher = ` + match(u:User) where u.id in $idsOfMentionedUsers + match(p:Post) where p.id = $postId + create(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt}) + merge (n)-[:NOTIFIED]->(u) + merge (p)-[:NOTIFIED]->(n) + ` + await session.run(cypher, { + idsOfMentionedUsers, + createdAt, + postId, + }) + session.close() +} + +const updateHashtagsOfPost = async (postId, hashtags, context) => { + const session = context.driver.session() + // We need two Cypher statements, because the 'MATCH' in the 'cypherDeletePreviousRelations' statement + // functions as an 'if'. In case there is no previous relation, the rest of the commands are omitted + // and no new Hashtags and relations will be created. + const cypherDeletePreviousRelations = ` + MATCH (p:Post { id: $postId })-[previousRelations:TAGGED]->(t:Tag) + DELETE previousRelations + RETURN p, t + ` + const cypherCreateNewTagsAndRelations = ` + MATCH (p:Post { id: $postId}) + UNWIND $hashtags AS tagName + MERGE (t:Tag { id: tagName, name: tagName, disabled: false, deleted: false }) + MERGE (p)-[:TAGGED]->(t) + RETURN p, t + ` + await session.run(cypherDeletePreviousRelations, { + postId, + }) + await session.run(cypherCreateNewTagsAndRelations, { + postId, + hashtags, + }) + session.close() +} + +const handleContentData = async (resolve, root, args, context, resolveInfo) => { + // extract user ids before xss-middleware removes classes via the following "resolve" call + const idsOfMentionedUsers = extractMentionedUsers(args.content) + // extract tag (hashtag) ids before xss-middleware removes classes via the following "resolve" call + const hashtags = extractHashtags(args.content) + + // removes classes from the content + const post = await resolve(root, args, context, resolveInfo) + + await notify(post.id, idsOfMentionedUsers, context) + await updateHashtagsOfPost(post.id, hashtags, context) + + return post +} + +export default { + Mutation: { + CreatePost: handleContentData, + UpdatePost: handleContentData, + }, +} diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js new file mode 100644 index 000000000..aa281e6d7 --- /dev/null +++ b/backend/src/middleware/handleHtmlContent/handleContentData.spec.js @@ -0,0 +1,286 @@ +import { GraphQLClient } from 'graphql-request' +import gql from 'graphql-tag' +import { host, login } from '../../jest/helpers' +import Factory from '../../seed/factories' + +const factory = Factory() +let client + +beforeEach(async () => { + await factory.create('User', { + id: 'you', + name: 'Al Capone', + slug: 'al-capone', + email: 'test@example.org', + password: '1234', + }) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('currentUser { notifications }', () => { + const query = gql` + query($read: Boolean) { + currentUser { + notifications(read: $read, orderBy: createdAt_desc) { + read + post { + content + } + } + } + } + ` + + describe('authenticated', () => { + let headers + beforeEach(async () => { + headers = await login({ + email: 'test@example.org', + password: '1234', + }) + client = new GraphQLClient(host, { + headers, + }) + }) + + describe('given another user', () => { + let authorClient + let authorParams + let authorHeaders + + beforeEach(async () => { + authorParams = { + email: 'author@example.org', + password: '1234', + id: 'author', + } + await factory.create('User', authorParams) + authorHeaders = await login(authorParams) + }) + + describe('who mentions me in a post', () => { + let post + const title = 'Mentioning Al Capone' + const content = + 'Hey @al-capone how do you do?' + + beforeEach(async () => { + const createPostMutation = gql` + mutation($title: String!, $content: String!) { + CreatePost(title: $title, content: $content) { + id + title + content + } + } + ` + authorClient = new GraphQLClient(host, { + headers: authorHeaders, + }) + const { CreatePost } = await authorClient.request(createPostMutation, { + title, + content, + }) + post = CreatePost + }) + + it('sends you a notification', async () => { + const expectedContent = + 'Hey @al-capone how do you do?' + const expected = { + currentUser: { + notifications: [ + { + read: false, + post: { + content: expectedContent, + }, + }, + ], + }, + } + await expect( + client.request(query, { + read: false, + }), + ).resolves.toEqual(expected) + }) + + describe('who mentions me again', () => { + beforeEach(async () => { + const updatedContent = `${post.content} One more mention to @al-capone` + // The response `post.content` contains a link but the XSSmiddleware + // should have the `mention` CSS class removed. I discovered this + // during development and thought: A feature not a bug! This way we + // can encode a re-mentioning of users when you edit your post or + // comment. + const updatePostMutation = gql` + mutation($id: ID!, $title: String!, $content: String!) { + UpdatePost(id: $id, content: $content, title: $title) { + title + content + } + } + ` + authorClient = new GraphQLClient(host, { + headers: authorHeaders, + }) + await authorClient.request(updatePostMutation, { + id: post.id, + title: post.title, + content: updatedContent, + }) + }) + + it('creates exactly one more notification', async () => { + const expectedContent = + 'Hey @al-capone how do you do? One more mention to @al-capone' + const expected = { + currentUser: { + notifications: [ + { + read: false, + post: { + content: expectedContent, + }, + }, + { + read: false, + post: { + content: expectedContent, + }, + }, + ], + }, + } + await expect( + client.request(query, { + read: false, + }), + ).resolves.toEqual(expected) + }) + }) + }) + }) + }) +}) + +describe('Hashtags', () => { + const postId = 'p135' + const postTitle = 'Two Hashtags' + const postContent = + '

Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' + const postWithHastagsQuery = gql` + query($id: ID) { + Post(id: $id) { + tags { + id + name + } + } + } + ` + const postWithHastagsVariables = { + id: postId, + } + const createPostMutation = gql` + mutation($postId: ID, $postTitle: String!, $postContent: String!) { + CreatePost(id: $postId, title: $postTitle, content: $postContent) { + id + title + content + } + } + ` + + describe('authenticated', () => { + let headers + beforeEach(async () => { + headers = await login({ + email: 'test@example.org', + password: '1234', + }) + client = new GraphQLClient(host, { + headers, + }) + }) + + describe('create a Post with Hashtags', () => { + beforeEach(async () => { + await client.request(createPostMutation, { + postId, + postTitle, + postContent, + }) + }) + + it('both Hashtags are created with the "id" set to thier "name"', async () => { + const expected = [ + { + id: 'Democracy', + name: 'Democracy', + }, + { + id: 'Liberty', + name: 'Liberty', + }, + ] + await expect( + client.request(postWithHastagsQuery, postWithHastagsVariables), + ).resolves.toEqual({ + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], + }) + }) + + describe('afterwards update the Post by removing a Hashtag, leaving a Hashtag and add a Hashtag', () => { + // The already existing Hashtag has no class at this point. + const updatedPostContent = + '

Hey Dude, #Elections should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' + const updatePostMutation = gql` + mutation($postId: ID!, $postTitle: String!, $updatedPostContent: String!) { + UpdatePost(id: $postId, title: $postTitle, content: $updatedPostContent) { + id + title + content + } + } + ` + + it('only one previous Hashtag and the new Hashtag exists', async () => { + await client.request(updatePostMutation, { + postId, + postTitle, + updatedPostContent, + }) + + const expected = [ + { + id: 'Elections', + name: 'Elections', + }, + { + id: 'Liberty', + name: 'Liberty', + }, + ] + await expect( + client.request(postWithHastagsQuery, postWithHastagsVariables), + ).resolves.toEqual({ + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], + }) + }) + }) + }) + }) +}) diff --git a/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.js b/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.js new file mode 100644 index 000000000..fd6613065 --- /dev/null +++ b/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.js @@ -0,0 +1,28 @@ +import cheerio from 'cheerio' +// formats of a Hashtag: +// https://en.wikipedia.org/w/index.php?title=Hashtag&oldid=905141980#Style +// here: +// 0. Search for whole string. +// 1. Hashtag has only 'a-z', 'A-Z', and '0-9'. +// 2. If it starts with a digit '0-9' than 'a-z', or 'A-Z' has to follow. +const ID_REGEX = /^\/search\/hashtag\/(([a-zA-Z]+[a-zA-Z0-9]*)|([0-9]+[a-zA-Z]+[a-zA-Z0-9]*))$/g + +export default function(content) { + if (!content) return [] + const $ = cheerio.load(content) + // We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware. + // But we have to know, which Hashtags are removed from the content es well, so we search for the 'a' html-tag. + const urls = $('a') + .map((_, el) => { + return $(el).attr('href') + }) + .get() + const hashtags = [] + urls.forEach(url => { + let match + while ((match = ID_REGEX.exec(url)) != null) { + hashtags.push(match[1]) + } + }) + return hashtags +} diff --git a/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.spec.js b/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.spec.js new file mode 100644 index 000000000..eb581d8f5 --- /dev/null +++ b/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.spec.js @@ -0,0 +1,57 @@ +import extractHashtags from './extractHashtags' + +describe('extractHashtags', () => { + describe('content undefined', () => { + it('returns empty array', () => { + expect(extractHashtags()).toEqual([]) + }) + }) + + describe('searches through links', () => { + it('finds links with and without ".hashtag" class and extracts Hashtag names', () => { + const content = + '

#Elections#Democracy

' + expect(extractHashtags(content)).toEqual(['Elections', 'Democracy']) + }) + + it('ignores mentions', () => { + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + expect(extractHashtags(content)).toEqual([]) + }) + + describe('handles links', () => { + it('ignores links with domains', () => { + const content = + '

#Elections#Democracy

' + expect(extractHashtags(content)).toEqual(['Democracy']) + }) + + it('ignores Hashtag links with not allowed character combinations', () => { + const content = + '

Something inspirational about #AbcDefXyz0123456789!*(),2, #0123456789, #0123456789a and #AbcDefXyz0123456789.

' + expect(extractHashtags(content)).toEqual(['0123456789a', 'AbcDefXyz0123456789']) + }) + }) + + describe('does not crash if', () => { + it('`href` contains no Hashtag name', () => { + const content = + '

Something inspirational about #Democracy and #liberty.

' + expect(extractHashtags(content)).toEqual([]) + }) + + it('`href` contains Hashtag as page anchor', () => { + const content = + '

Something inspirational about #anchor.

' + expect(extractHashtags(content)).toEqual([]) + }) + + it('`href` is empty or invalid', () => { + const content = + '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' + expect(extractHashtags(content)).toEqual([]) + }) + }) + }) +}) diff --git a/backend/src/middleware/notifications/extractIds/index.js b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js similarity index 100% rename from backend/src/middleware/notifications/extractIds/index.js rename to backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js diff --git a/backend/src/middleware/notifications/extractIds/spec.js b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js similarity index 79% rename from backend/src/middleware/notifications/extractIds/spec.js rename to backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js index 341c39cec..f39fbc859 100644 --- a/backend/src/middleware/notifications/extractIds/spec.js +++ b/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js @@ -1,9 +1,9 @@ -import extractIds from '.' +import extractMentionedUsers from './extractMentionedUsers' -describe('extractIds', () => { +describe('extractMentionedUsers', () => { describe('content undefined', () => { it('returns empty array', () => { - expect(extractIds()).toEqual([]) + expect(extractMentionedUsers()).toEqual([]) }) }) @@ -11,33 +11,33 @@ describe('extractIds', () => { it('ignores links without .mention class', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual([]) + expect(extractMentionedUsers(content)).toEqual([]) }) describe('given a link with .mention class', () => { it('extracts ids', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual(['u2', 'u3']) + expect(extractMentionedUsers(content)).toEqual(['u2', 'u3']) }) describe('handles links', () => { it('with slug and id', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual(['u2', 'u3']) + expect(extractMentionedUsers(content)).toEqual(['u2', 'u3']) }) it('with domains', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual(['u2', 'u3']) + expect(extractMentionedUsers(content)).toEqual(['u2', 'u3']) }) it('special characters', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual(['u!*(),2', 'u.~-3']) + expect(extractMentionedUsers(content)).toEqual(['u!*(),2', 'u.~-3']) }) }) @@ -45,13 +45,13 @@ describe('extractIds', () => { it('`href` contains no user id', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual([]) + expect(extractMentionedUsers(content)).toEqual([]) }) it('`href` is empty or invalid', () => { const content = '

Something inspirational about @bob-der-baumeister and @jenny-rostock.

' - expect(extractIds(content)).toEqual([]) + expect(extractMentionedUsers(content)).toEqual([]) }) }) }) diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 14f85f91a..fd631256d 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -10,7 +10,7 @@ import user from './userMiddleware' import includedFields from './includedFieldsMiddleware' import orderBy from './orderByMiddleware' import validation from './validation/validationMiddleware' -import notifications from './notifications' +import handleContentData from './handleHtmlContent/handleContentData' import email from './email/emailMiddleware' export default schema => { @@ -21,7 +21,7 @@ export default schema => { validation: validation, sluggify: sluggify, excerpt: excerpt, - notifications: notifications, + handleContentData: handleContentData, xss: xss, softDelete: softDelete, user: user, @@ -38,7 +38,7 @@ export default schema => { 'sluggify', 'excerpt', 'email', - 'notifications', + 'handleContentData', 'xss', 'softDelete', 'user', diff --git a/backend/src/middleware/notifications/index.js b/backend/src/middleware/notifications/index.js deleted file mode 100644 index ca460a512..000000000 --- a/backend/src/middleware/notifications/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import extractIds from './extractIds' - -const notify = async (resolve, root, args, context, resolveInfo) => { - // extract user ids before xss-middleware removes link classes - const ids = extractIds(args.content) - - const post = await resolve(root, args, context, resolveInfo) - - const session = context.driver.session() - const { id: postId } = post - const createdAt = new Date().toISOString() - const cypher = ` - match(u:User) where u.id in $ids - match(p:Post) where p.id = $postId - create(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt}) - merge (n)-[:NOTIFIED]->(u) - merge (p)-[:NOTIFIED]->(n) - ` - await session.run(cypher, { ids, createdAt, postId }) - session.close() - - return post -} - -export default { - Mutation: { - CreatePost: notify, - UpdatePost: notify, - }, -} diff --git a/backend/src/middleware/notifications/spec.js b/backend/src/middleware/notifications/spec.js deleted file mode 100644 index d214a5571..000000000 --- a/backend/src/middleware/notifications/spec.js +++ /dev/null @@ -1,130 +0,0 @@ -import { GraphQLClient } from 'graphql-request' -import { host, login } from '../../jest/helpers' -import Factory from '../../seed/factories' - -const factory = Factory() -let client - -beforeEach(async () => { - await factory.create('User', { - id: 'you', - name: 'Al Capone', - slug: 'al-capone', - email: 'test@example.org', - password: '1234', - }) -}) - -afterEach(async () => { - await factory.cleanDatabase() -}) - -describe('currentUser { notifications }', () => { - const query = `query($read: Boolean) { - currentUser { - notifications(read: $read, orderBy: createdAt_desc) { - read - post { - content - } - } - } - }` - - describe('authenticated', () => { - let headers - beforeEach(async () => { - headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - }) - - describe('given another user', () => { - let authorClient - let authorParams - let authorHeaders - - beforeEach(async () => { - authorParams = { - email: 'author@example.org', - password: '1234', - id: 'author', - } - await factory.create('User', authorParams) - authorHeaders = await login(authorParams) - }) - - describe('who mentions me in a post', () => { - let post - const title = 'Mentioning Al Capone' - const content = - 'Hey @al-capone how do you do?' - - beforeEach(async () => { - const createPostMutation = ` - mutation($title: String!, $content: String!) { - CreatePost(title: $title, content: $content) { - id - title - content - } - } - ` - authorClient = new GraphQLClient(host, { headers: authorHeaders }) - const { CreatePost } = await authorClient.request(createPostMutation, { title, content }) - post = CreatePost - }) - - it('sends you a notification', async () => { - const expectedContent = - 'Hey @al-capone how do you do?' - const expected = { - currentUser: { - notifications: [{ read: false, post: { content: expectedContent } }], - }, - } - await expect(client.request(query, { read: false })).resolves.toEqual(expected) - }) - - describe('who mentions me again', () => { - beforeEach(async () => { - const updatedContent = `${post.content} One more mention to @al-capone` - const updatedTitle = 'this post has been updated' - // The response `post.content` contains a link but the XSSmiddleware - // should have the `mention` CSS class removed. I discovered this - // during development and thought: A feature not a bug! This way we - // can encode a re-mentioning of users when you edit your post or - // comment. - const updatePostMutation = ` - mutation($id: ID!, $title: String!, $content: String!) { - UpdatePost(id: $id, title: $title, content: $content) { - title - content - } - } - ` - authorClient = new GraphQLClient(host, { headers: authorHeaders }) - await authorClient.request(updatePostMutation, { - id: post.id, - content: updatedContent, - title: updatedTitle, - }) - }) - - it('creates exactly one more notification', async () => { - const expectedContent = - 'Hey @al-capone how do you do? One more mention to @al-capone' - const expected = { - currentUser: { - notifications: [ - { read: false, post: { content: expectedContent } }, - { read: false, post: { content: expectedContent } }, - ], - }, - } - await expect(client.request(query, { read: false })).resolves.toEqual(expected) - }) - }) - }) - }) - }) -}) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 101713f91..a6b6ef0da 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -137,7 +137,7 @@ const permissions = shield( '*': deny, findPosts: allow, Category: allow, - Tag: isAdmin, + Tag: allow, Report: isModerator, Notification: isAdmin, statistics: allow, diff --git a/backend/src/seed/seed-db.js b/backend/src/seed/seed-db.js index e31d09a68..18eefb76f 100644 --- a/backend/src/seed/seed-db.js +++ b/backend/src/seed/seed-db.js @@ -69,47 +69,144 @@ import Factory from './factories' role: 'user', email: 'user@example.org', }), - f.create('User', { id: 'u4', name: 'Tick', role: 'user', email: 'tick@example.org' }), - f.create('User', { id: 'u5', name: 'Trick', role: 'user', email: 'trick@example.org' }), - f.create('User', { id: 'u6', name: 'Track', role: 'user', email: 'track@example.org' }), - f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' }), + f.create('User', { + id: 'u4', + name: 'Tick', + role: 'user', + email: 'tick@example.org', + }), + f.create('User', { + id: 'u5', + name: 'Trick', + role: 'user', + email: 'trick@example.org', + }), + f.create('User', { + id: 'u6', + name: 'Track', + role: 'user', + email: 'track@example.org', + }), + f.create('User', { + id: 'u7', + name: 'Dagobert', + role: 'user', + email: 'dagobert@example.org', + }), ]) const [asAdmin, asModerator, asUser, asTick, asTrick, asTrack] = await Promise.all([ - Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'user@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }), - Factory().authenticateAs({ email: 'track@example.org', password: '1234' }), + Factory().authenticateAs({ + email: 'admin@example.org', + password: '1234', + }), + Factory().authenticateAs({ + email: 'moderator@example.org', + password: '1234', + }), + Factory().authenticateAs({ + email: 'user@example.org', + password: '1234', + }), + Factory().authenticateAs({ + email: 'tick@example.org', + password: '1234', + }), + Factory().authenticateAs({ + email: 'trick@example.org', + password: '1234', + }), + Factory().authenticateAs({ + email: 'track@example.org', + password: '1234', + }), ]) await Promise.all([ - f.relate('User', 'Badges', { from: 'b6', to: 'u1' }), - f.relate('User', 'Badges', { from: 'b5', to: 'u2' }), - f.relate('User', 'Badges', { from: 'b4', to: 'u3' }), - f.relate('User', 'Badges', { from: 'b3', to: 'u4' }), - f.relate('User', 'Badges', { from: 'b2', to: 'u5' }), - f.relate('User', 'Badges', { from: 'b1', to: 'u6' }), - f.relate('User', 'Friends', { from: 'u1', to: 'u2' }), - f.relate('User', 'Friends', { from: 'u1', to: 'u3' }), - f.relate('User', 'Friends', { from: 'u2', to: 'u3' }), - f.relate('User', 'Blacklisted', { from: 'u7', to: 'u4' }), - f.relate('User', 'Blacklisted', { from: 'u7', to: 'u5' }), - f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' }), + f.relate('User', 'Badges', { + from: 'b6', + to: 'u1', + }), + f.relate('User', 'Badges', { + from: 'b5', + to: 'u2', + }), + f.relate('User', 'Badges', { + from: 'b4', + to: 'u3', + }), + f.relate('User', 'Badges', { + from: 'b3', + to: 'u4', + }), + f.relate('User', 'Badges', { + from: 'b2', + to: 'u5', + }), + f.relate('User', 'Badges', { + from: 'b1', + to: 'u6', + }), + f.relate('User', 'Friends', { + from: 'u1', + to: 'u2', + }), + f.relate('User', 'Friends', { + from: 'u1', + to: 'u3', + }), + f.relate('User', 'Friends', { + from: 'u2', + to: 'u3', + }), + f.relate('User', 'Blacklisted', { + from: 'u7', + to: 'u4', + }), + f.relate('User', 'Blacklisted', { + from: 'u7', + to: 'u5', + }), + f.relate('User', 'Blacklisted', { + from: 'u7', + to: 'u6', + }), ]) await Promise.all([ - asAdmin.follow({ id: 'u3', type: 'User' }), - asModerator.follow({ id: 'u4', type: 'User' }), - asUser.follow({ id: 'u4', type: 'User' }), - asTick.follow({ id: 'u6', type: 'User' }), - asTrick.follow({ id: 'u4', type: 'User' }), - asTrack.follow({ id: 'u3', type: 'User' }), + asAdmin.follow({ + id: 'u3', + type: 'User', + }), + asModerator.follow({ + id: 'u4', + type: 'User', + }), + asUser.follow({ + id: 'u4', + type: 'User', + }), + asTick.follow({ + id: 'u6', + type: 'User', + }), + asTrick.follow({ + id: 'u4', + type: 'User', + }), + asTrack.follow({ + id: 'u3', + type: 'User', + }), ]) await Promise.all([ - f.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }), + f.create('Category', { + id: 'cat1', + name: 'Just For Fun', + slug: 'justforfun', + icon: 'smile', + }), f.create('Category', { id: 'cat2', name: 'Happyness & Values', @@ -203,10 +300,22 @@ import Factory from './factories' ]) await Promise.all([ - f.create('Tag', { id: 't1', name: 'Umwelt' }), - f.create('Tag', { id: 't2', name: 'Naturschutz' }), - f.create('Tag', { id: 't3', name: 'Demokratie' }), - f.create('Tag', { id: 't4', name: 'Freiheit' }), + f.create('Tag', { + id: 'Umwelt', + name: 'Umwelt', + }), + f.create('Tag', { + id: 'Naturschutz', + name: 'Naturschutz', + }), + f.create('Tag', { + id: 'Demokratie', + name: 'Demokratie', + }), + f.create('Tag', { + id: 'Freiheit', + name: 'Freiheit', + }), ]) const mention1 = 'Hey @jenny-rostock, what\'s up?' @@ -214,108 +323,347 @@ import Factory from './factories' 'Hey @jenny-rostock, here is another notification for you!' await Promise.all([ - asAdmin.create('Post', { id: 'p0', image: faker.image.unsplash.food() }), - asModerator.create('Post', { id: 'p1', image: faker.image.unsplash.technology() }), - asUser.create('Post', { id: 'p2' }), - asTick.create('Post', { id: 'p3' }), - asTrick.create('Post', { id: 'p4' }), - asTrack.create('Post', { id: 'p5' }), - asAdmin.create('Post', { id: 'p6', image: faker.image.unsplash.buildings() }), - asModerator.create('Post', { id: 'p7', content: `${mention1} ${faker.lorem.paragraph()}` }), - asUser.create('Post', { id: 'p8', image: faker.image.unsplash.nature() }), - asTick.create('Post', { id: 'p9' }), - asTrick.create('Post', { id: 'p10' }), - asTrack.create('Post', { id: 'p11', image: faker.image.unsplash.people() }), - asAdmin.create('Post', { id: 'p12', content: `${mention2} ${faker.lorem.paragraph()}` }), - asModerator.create('Post', { id: 'p13' }), - asUser.create('Post', { id: 'p14', image: faker.image.unsplash.objects() }), - asTick.create('Post', { id: 'p15' }), + asAdmin.create('Post', { + id: 'p0', + image: faker.image.unsplash.food(), + }), + asModerator.create('Post', { + id: 'p1', + image: faker.image.unsplash.technology(), + }), + asUser.create('Post', { + id: 'p2', + }), + asTick.create('Post', { + id: 'p3', + }), + asTrick.create('Post', { + id: 'p4', + }), + asTrack.create('Post', { + id: 'p5', + }), + asAdmin.create('Post', { + id: 'p6', + image: faker.image.unsplash.buildings(), + }), + asModerator.create('Post', { + id: 'p7', + content: `${mention1} ${faker.lorem.paragraph()}`, + }), + asUser.create('Post', { + id: 'p8', + image: faker.image.unsplash.nature(), + }), + asTick.create('Post', { + id: 'p9', + }), + asTrick.create('Post', { + id: 'p10', + }), + asTrack.create('Post', { + id: 'p11', + image: faker.image.unsplash.people(), + }), + asAdmin.create('Post', { + id: 'p12', + content: `${mention2} ${faker.lorem.paragraph()}`, + }), + asModerator.create('Post', { + id: 'p13', + }), + asUser.create('Post', { + id: 'p14', + image: faker.image.unsplash.objects(), + }), + asTick.create('Post', { + id: 'p15', + }), ]) await Promise.all([ - f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }), - f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }), - f.relate('Post', 'Categories', { from: 'p2', to: 'cat2' }), - f.relate('Post', 'Categories', { from: 'p3', to: 'cat3' }), - f.relate('Post', 'Categories', { from: 'p4', to: 'cat4' }), - f.relate('Post', 'Categories', { from: 'p5', to: 'cat5' }), - f.relate('Post', 'Categories', { from: 'p6', to: 'cat6' }), - f.relate('Post', 'Categories', { from: 'p7', to: 'cat7' }), - f.relate('Post', 'Categories', { from: 'p8', to: 'cat8' }), - f.relate('Post', 'Categories', { from: 'p9', to: 'cat9' }), - f.relate('Post', 'Categories', { from: 'p10', to: 'cat10' }), - f.relate('Post', 'Categories', { from: 'p11', to: 'cat11' }), - f.relate('Post', 'Categories', { from: 'p12', to: 'cat12' }), - f.relate('Post', 'Categories', { from: 'p13', to: 'cat13' }), - f.relate('Post', 'Categories', { from: 'p14', to: 'cat14' }), - f.relate('Post', 'Categories', { from: 'p15', to: 'cat15' }), + f.relate('Post', 'Categories', { + from: 'p0', + to: 'cat16', + }), + f.relate('Post', 'Categories', { + from: 'p1', + to: 'cat1', + }), + f.relate('Post', 'Categories', { + from: 'p2', + to: 'cat2', + }), + f.relate('Post', 'Categories', { + from: 'p3', + to: 'cat3', + }), + f.relate('Post', 'Categories', { + from: 'p4', + to: 'cat4', + }), + f.relate('Post', 'Categories', { + from: 'p5', + to: 'cat5', + }), + f.relate('Post', 'Categories', { + from: 'p6', + to: 'cat6', + }), + f.relate('Post', 'Categories', { + from: 'p7', + to: 'cat7', + }), + f.relate('Post', 'Categories', { + from: 'p8', + to: 'cat8', + }), + f.relate('Post', 'Categories', { + from: 'p9', + to: 'cat9', + }), + f.relate('Post', 'Categories', { + from: 'p10', + to: 'cat10', + }), + f.relate('Post', 'Categories', { + from: 'p11', + to: 'cat11', + }), + f.relate('Post', 'Categories', { + from: 'p12', + to: 'cat12', + }), + f.relate('Post', 'Categories', { + from: 'p13', + to: 'cat13', + }), + f.relate('Post', 'Categories', { + from: 'p14', + to: 'cat14', + }), + f.relate('Post', 'Categories', { + from: 'p15', + to: 'cat15', + }), - f.relate('Post', 'Tags', { from: 'p0', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p1', to: 't1' }), - f.relate('Post', 'Tags', { from: 'p2', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p3', to: 't3' }), - f.relate('Post', 'Tags', { from: 'p4', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p5', to: 't1' }), - f.relate('Post', 'Tags', { from: 'p6', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p7', to: 't3' }), - f.relate('Post', 'Tags', { from: 'p8', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p9', to: 't1' }), - f.relate('Post', 'Tags', { from: 'p10', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p11', to: 't3' }), - f.relate('Post', 'Tags', { from: 'p12', to: 't4' }), - f.relate('Post', 'Tags', { from: 'p13', to: 't1' }), - f.relate('Post', 'Tags', { from: 'p14', to: 't2' }), - f.relate('Post', 'Tags', { from: 'p15', to: 't3' }), + f.relate('Post', 'Tags', { + from: 'p0', + to: 'Freiheit', + }), + f.relate('Post', 'Tags', { + from: 'p1', + to: 'Umwelt', + }), + f.relate('Post', 'Tags', { + from: 'p2', + to: 'Naturschutz', + }), + f.relate('Post', 'Tags', { + from: 'p3', + to: 'Demokratie', + }), + f.relate('Post', 'Tags', { + from: 'p4', + to: 'Freiheit', + }), + f.relate('Post', 'Tags', { + from: 'p5', + to: 'Umwelt', + }), + f.relate('Post', 'Tags', { + from: 'p6', + to: 'Naturschutz', + }), + f.relate('Post', 'Tags', { + from: 'p7', + to: 'Demokratie', + }), + f.relate('Post', 'Tags', { + from: 'p8', + to: 'Freiheit', + }), + f.relate('Post', 'Tags', { + from: 'p9', + to: 'Umwelt', + }), + f.relate('Post', 'Tags', { + from: 'p10', + to: 'Naturschutz', + }), + f.relate('Post', 'Tags', { + from: 'p11', + to: 'Demokratie', + }), + f.relate('Post', 'Tags', { + from: 'p12', + to: 'Freiheit', + }), + f.relate('Post', 'Tags', { + from: 'p13', + to: 'Umwelt', + }), + f.relate('Post', 'Tags', { + from: 'p14', + to: 'Naturschutz', + }), + f.relate('Post', 'Tags', { + from: 'p15', + to: 'Demokratie', + }), ]) await Promise.all([ - asAdmin.shout({ id: 'p2', type: 'Post' }), - asAdmin.shout({ id: 'p6', type: 'Post' }), - asModerator.shout({ id: 'p0', type: 'Post' }), - asModerator.shout({ id: 'p6', type: 'Post' }), - asUser.shout({ id: 'p6', type: 'Post' }), - asUser.shout({ id: 'p7', type: 'Post' }), - asTick.shout({ id: 'p8', type: 'Post' }), - asTick.shout({ id: 'p9', type: 'Post' }), - asTrack.shout({ id: 'p10', type: 'Post' }), + asAdmin.shout({ + id: 'p2', + type: 'Post', + }), + asAdmin.shout({ + id: 'p6', + type: 'Post', + }), + asModerator.shout({ + id: 'p0', + type: 'Post', + }), + asModerator.shout({ + id: 'p6', + type: 'Post', + }), + asUser.shout({ + id: 'p6', + type: 'Post', + }), + asUser.shout({ + id: 'p7', + type: 'Post', + }), + asTick.shout({ + id: 'p8', + type: 'Post', + }), + asTick.shout({ + id: 'p9', + type: 'Post', + }), + asTrack.shout({ + id: 'p10', + type: 'Post', + }), ]) await Promise.all([ - asAdmin.shout({ id: 'p2', type: 'Post' }), - asAdmin.shout({ id: 'p6', type: 'Post' }), - asModerator.shout({ id: 'p0', type: 'Post' }), - asModerator.shout({ id: 'p6', type: 'Post' }), - asUser.shout({ id: 'p6', type: 'Post' }), - asUser.shout({ id: 'p7', type: 'Post' }), - asTick.shout({ id: 'p8', type: 'Post' }), - asTick.shout({ id: 'p9', type: 'Post' }), - asTrack.shout({ id: 'p10', type: 'Post' }), + asAdmin.shout({ + id: 'p2', + type: 'Post', + }), + asAdmin.shout({ + id: 'p6', + type: 'Post', + }), + asModerator.shout({ + id: 'p0', + type: 'Post', + }), + asModerator.shout({ + id: 'p6', + type: 'Post', + }), + asUser.shout({ + id: 'p6', + type: 'Post', + }), + asUser.shout({ + id: 'p7', + type: 'Post', + }), + asTick.shout({ + id: 'p8', + type: 'Post', + }), + asTick.shout({ + id: 'p9', + type: 'Post', + }), + asTrack.shout({ + id: 'p10', + type: 'Post', + }), ]) await Promise.all([ - asUser.create('Comment', { id: 'c1', postId: 'p1' }), - asTick.create('Comment', { id: 'c2', postId: 'p1' }), - asTrack.create('Comment', { id: 'c3', postId: 'p3' }), - asTrick.create('Comment', { id: 'c4', postId: 'p2' }), - asModerator.create('Comment', { id: 'c5', postId: 'p3' }), - asAdmin.create('Comment', { id: 'c6', postId: 'p4' }), - asUser.create('Comment', { id: 'c7', postId: 'p2' }), - asTick.create('Comment', { id: 'c8', postId: 'p15' }), - asTrick.create('Comment', { id: 'c9', postId: 'p15' }), - asTrack.create('Comment', { id: 'c10', postId: 'p15' }), - asUser.create('Comment', { id: 'c11', postId: 'p15' }), - asUser.create('Comment', { id: 'c12', postId: 'p15' }), + asUser.create('Comment', { + id: 'c1', + postId: 'p1', + }), + asTick.create('Comment', { + id: 'c2', + postId: 'p1', + }), + asTrack.create('Comment', { + id: 'c3', + postId: 'p3', + }), + asTrick.create('Comment', { + id: 'c4', + postId: 'p2', + }), + asModerator.create('Comment', { + id: 'c5', + postId: 'p3', + }), + asAdmin.create('Comment', { + id: 'c6', + postId: 'p4', + }), + asUser.create('Comment', { + id: 'c7', + postId: 'p2', + }), + asTick.create('Comment', { + id: 'c8', + postId: 'p15', + }), + asTrick.create('Comment', { + id: 'c9', + postId: 'p15', + }), + asTrack.create('Comment', { + id: 'c10', + postId: 'p15', + }), + asUser.create('Comment', { + id: 'c11', + postId: 'p15', + }), + asUser.create('Comment', { + id: 'c12', + postId: 'p15', + }), ]) const disableMutation = 'mutation($id: ID!) { disable(id: $id) }' await Promise.all([ - asModerator.mutate(disableMutation, { id: 'p11' }), - asModerator.mutate(disableMutation, { id: 'c5' }), + asModerator.mutate(disableMutation, { + id: 'p11', + }), + asModerator.mutate(disableMutation, { + id: 'c5', + }), ]) await Promise.all([ - asTick.create('Report', { description: "I don't like this comment", id: 'c1' }), - asTrick.create('Report', { description: "I don't like this post", id: 'p1' }), - asTrack.create('Report', { description: "I don't like this user", id: 'u1' }), + asTick.create('Report', { + description: "I don't like this comment", + id: 'c1', + }), + asTrick.create('Report', { + description: "I don't like this post", + id: 'p1', + }), + asTrack.create('Report', { + description: "I don't like this user", + id: 'u1', + }), ]) await Promise.all([ @@ -342,10 +690,22 @@ import Factory from './factories' ]) await Promise.all([ - f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o1' }), - f.relate('Organization', 'CreatedBy', { from: 'u1', to: 'o2' }), - f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o2' }), - f.relate('Organization', 'OwnedBy', { from: 'u2', to: 'o3' }), + f.relate('Organization', 'CreatedBy', { + from: 'u1', + to: 'o1', + }), + f.relate('Organization', 'CreatedBy', { + from: 'u1', + to: 'o2', + }), + f.relate('Organization', 'OwnedBy', { + from: 'u2', + to: 'o2', + }), + f.relate('Organization', 'OwnedBy', { + from: 'u2', + to: 'o3', + }), ]) /* eslint-disable-next-line no-console */ console.log('Seeded Data...') diff --git a/backend/yarn.lock b/backend/yarn.lock index e0098c448..596a0c917 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -14,10 +14,10 @@ resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.20.tgz#bf9f2acdf319c0959fad8ec1239741dd2ead4e8d" integrity sha512-3LWZa80HcP70Pl+H4KhLDJ7S0px+9/c8GTXdl6SpunRecUaB27g/oOQnAjNHLHdbWuGE0uyqcuGiTfbKB3ilaQ== -"@babel/cli@~7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.4.4.tgz#5454bb7112f29026a4069d8e6f0e1794e651966c" - integrity sha512-XGr5YjQSjgTa6OzQZY57FAJsdeVSAKR/u/KA5exWIz66IKtv/zXtHy+fIZcMry/EgYegwuHE7vzGnrFhjdIAsQ== +"@babel/cli@~7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.5.0.tgz#f403c930692e28ecfa3bf02a9e7562b474f38271" + integrity sha512-qNH55fWbKrEsCwID+Qc/3JDPnsSGpIIiMDbppnR8Z6PxLAqMQCFNqBctkIkBrMH49Nx+qqVTrHRWUR+ho2k+qQ== dependencies: commander "^2.8.1" convert-source-map "^1.1.0" @@ -38,18 +38,18 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@~7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" - integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA== +"@babel/core@^7.1.0", "@babel/core@~7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.0.tgz#6ed6a2881ad48a732c5433096d96d1b0ee5eb734" + integrity sha512-6Isr4X98pwXqHvtigw71CKgmhL1etZjPs5A67jL/w0TkLM9eqmFR40YrnJvEc1WnMZFsskjsmid8bHZyxKEAnw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.4.4" - "@babel/helpers" "^7.4.4" - "@babel/parser" "^7.4.5" + "@babel/generator" "^7.5.0" + "@babel/helpers" "^7.5.0" + "@babel/parser" "^7.5.0" "@babel/template" "^7.4.4" - "@babel/traverse" "^7.4.5" - "@babel/types" "^7.4.4" + "@babel/traverse" "^7.5.0" + "@babel/types" "^7.5.0" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -58,12 +58,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" - integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ== +"@babel/generator@^7.0.0", "@babel/generator@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.0.tgz#f20e4b7a91750ee8b63656073d843d2a736dca4a" + integrity sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA== dependencies: - "@babel/types" "^7.4.4" + "@babel/types" "^7.5.0" jsesc "^2.5.1" lodash "^4.17.11" source-map "^0.5.0" @@ -260,14 +260,14 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5" - integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A== +"@babel/helpers@^7.5.0": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.2.tgz#97424dc82fc0041f4c751119b4d2b1ec68cdb5ba" + integrity sha512-NDkkTqDvgFUeo8djXBOiwO/mFjownznOWvmP9hvNdfiFUmx0nwNOqxuaTTbxjH744eQsD9M5ubC7gdANBvIWPw== dependencies: "@babel/template" "^7.4.4" - "@babel/traverse" "^7.4.4" - "@babel/types" "^7.4.4" + "@babel/traverse" "^7.5.0" + "@babel/types" "^7.5.0" "@babel/highlight@^7.0.0": version "7.0.0" @@ -278,10 +278,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/node@~7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.4.5.tgz#bce71bb44d902bfdd4da0b9c839a8a90fc084056" - integrity sha512-nDXPT0KwYMycDHhFG9wKlkipCR+iXzzoX9bD2aF2UABLhQ13AKhNi5Y61W8ASGPPll/7p9GrHesmlOgTUJVcfw== +"@babel/node@~7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.5.0.tgz#bcc5a286317ad771703889fb658e1f768c0a2a2e" + integrity sha512-VBlCrbJp7HDrKt4HRbtfq4Rs/XjBokvkfxXRQs4qA1C6eV3JycSOMELx4BFTPFRd9QnNA4PsIRfnvJqe/3tHow== dependencies: "@babel/polyfill" "^7.0.0" "@babel/register" "^7.0.0" @@ -290,10 +290,10 @@ node-environment-flags "^1.0.5" v8flags "^3.1.1" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" - integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.4", "@babel/parser@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.0.tgz#3e0713dff89ad6ae37faec3b29dcfc5c979770b7" + integrity sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -304,6 +304,14 @@ "@babel/helper-remap-async-to-generator" "^7.1.0" "@babel/plugin-syntax-async-generators" "^7.2.0" +"@babel/plugin-proposal-dynamic-import@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506" + integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/plugin-proposal-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" @@ -312,10 +320,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-json-strings" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz#1ef173fcf24b3e2df92a678f027673b55e7e3005" - integrity sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g== +"@babel/plugin-proposal-object-rest-spread@^7.5.2": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.2.tgz#ec92b0c6419074ea7af77c78b7c5d42041f2f5a9" + integrity sha512-C/JU3YOx5J4d9s0GGlJlYXVwsbd5JmqQ0AvB7cIDAx7nN57aDTnlJEsZJPuSskeBtMGFWSWU5Q+piTiDe0s7FQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" @@ -352,6 +360,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-dynamic-import@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612" + integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" @@ -387,10 +402,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-async-to-generator@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz#a3f1d01f2f21cadab20b33a82133116f14fb5894" - integrity sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA== +"@babel/plugin-transform-async-to-generator@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e" + integrity sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" @@ -432,10 +447,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-destructuring@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz#9d964717829cc9e4b601fc82a26a71a4d8faf20f" - integrity sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ== +"@babel/plugin-transform-destructuring@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz#f6c09fdfe3f94516ff074fe877db7bc9ef05855a" + integrity sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -448,10 +463,10 @@ "@babel/helper-regex" "^7.4.4" regexpu-core "^4.5.4" -"@babel/plugin-transform-duplicate-keys@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3" - integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw== +"@babel/plugin-transform-duplicate-keys@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853" + integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -492,30 +507,33 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-amd@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6" - integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw== +"@babel/plugin-transform-modules-amd@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91" + integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg== dependencies: "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" + babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e" - integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw== +"@babel/plugin-transform-modules-commonjs@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz#425127e6045231360858eeaa47a71d75eded7a74" + integrity sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ== dependencies: "@babel/helper-module-transforms" "^7.4.4" "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-simple-access" "^7.1.0" + babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-systemjs@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz#dc83c5665b07d6c2a7b224c00ac63659ea36a405" - integrity sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ== +"@babel/plugin-transform-modules-systemjs@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249" + integrity sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg== dependencies: "@babel/helper-hoist-variables" "^7.4.4" "@babel/helper-plugin-utils" "^7.0.0" + babel-plugin-dynamic-import-node "^2.3.0" "@babel/plugin-transform-modules-umd@^7.2.0": version "7.2.0" @@ -631,39 +649,41 @@ core-js "^2.5.7" regenerator-runtime "^0.12.0" -"@babel/preset-env@~7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" - integrity sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w== +"@babel/preset-env@~7.5.2": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.2.tgz#34a46f01aed617b174b8dbaf8fed9239300343d0" + integrity sha512-7rRJLaUqJhQ+8xGrWtMROAgOi/+udIzyK2ES9NHhDIUvR2zfx/ON5lRR8ACUGehIYst8KVbl4vpkgOqn08gBxA== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-dynamic-import" "^7.5.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.4.4" + "@babel/plugin-proposal-object-rest-spread" "^7.5.2" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-syntax-json-strings" "^7.2.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.4.4" + "@babel/plugin-transform-async-to-generator" "^7.5.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" "@babel/plugin-transform-block-scoping" "^7.4.4" "@babel/plugin-transform-classes" "^7.4.4" "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.4.4" + "@babel/plugin-transform-destructuring" "^7.5.0" "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-duplicate-keys" "^7.5.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" "@babel/plugin-transform-for-of" "^7.4.4" "@babel/plugin-transform-function-name" "^7.4.4" "@babel/plugin-transform-literals" "^7.2.0" "@babel/plugin-transform-member-expression-literals" "^7.2.0" - "@babel/plugin-transform-modules-amd" "^7.2.0" - "@babel/plugin-transform-modules-commonjs" "^7.4.4" - "@babel/plugin-transform-modules-systemjs" "^7.4.4" + "@babel/plugin-transform-modules-amd" "^7.5.0" + "@babel/plugin-transform-modules-commonjs" "^7.5.0" + "@babel/plugin-transform-modules-systemjs" "^7.5.0" "@babel/plugin-transform-modules-umd" "^7.2.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5" "@babel/plugin-transform-new-target" "^7.4.4" @@ -678,7 +698,7 @@ "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" "@babel/plugin-transform-unicode-regex" "^7.4.4" - "@babel/types" "^7.4.4" + "@babel/types" "^7.5.0" browserslist "^4.6.0" core-js-compat "^3.1.1" invariant "^2.2.2" @@ -720,25 +740,25 @@ "@babel/parser" "^7.4.4" "@babel/types" "^7.4.4" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216" - integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.0.tgz#4216d6586854ef5c3c4592dab56ec7eb78485485" + integrity sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.4.4" + "@babel/generator" "^7.5.0" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.4.5" - "@babel/types" "^7.4.4" + "@babel/parser" "^7.5.0" + "@babel/types" "^7.5.0" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.11" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" - integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.4", "@babel/types@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.0.tgz#e47d43840c2e7f9105bc4d3a2c371b4d0c7832ab" + integrity sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ== dependencies: esutils "^2.0.2" lodash "^4.17.11" @@ -1154,10 +1174,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.20": - version "0.26.20" - resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.20.tgz#3b85a05f5dd76e2e8475abb6a8aeae7777627143" - integrity sha512-LpCsA6NG7vIU7Umv1k4w3YGIBH5ZLZRPEKo8vJLHVbBUqRy2WaJ002kbsRqcwODpkICAOMuyGOqLQJa5isZ8+g== +"@types/yup@0.26.21": + version "0.26.21" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.21.tgz#bfca27a02a0631495bfd25b6c63647a125e6944e" + integrity sha512-1C45M7hZrVsl8bXxYV01bitRp0r35djt+eX5HWFH3NdH+8ejqta3KM7rmQLRLrupkhF7jRkAtXl2EgDsriIqwA== "@types/zen-observable@^0.5.3": version "0.5.4" @@ -1901,6 +1921,13 @@ babel-jest@^24.8.0, babel-jest@~24.8.0: chalk "^2.4.2" slash "^2.0.0" +babel-plugin-dynamic-import-node@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" + integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + dependencies: + object.assign "^4.1.0" + babel-plugin-istanbul@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz#6892f529eff65a3e2d33d87dc5888ffa2ecd4a30" @@ -3153,10 +3180,10 @@ eslint-plugin-import@~2.18.0: read-pkg-up "^2.0.0" resolve "^1.11.0" -eslint-plugin-jest@~22.7.1: - version "22.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.7.1.tgz#5dcdf8f7a285f98040378220d6beca581f0ab2a1" - integrity sha512-CrT3AzA738neimv8G8iK2HCkrCwHnAJeeo7k5TEHK86VMItKl6zdJT/tHBDImfnVVAYsVs4Y6BUdBZQCCgfiyw== +eslint-plugin-jest@~22.7.2: + version "22.7.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.7.2.tgz#7ab118a66a34e46ae5e16a128b5d24fd28b43dca" + integrity sha512-Aecqe3ulBVI7amgOycVI8ZPL8o0SnGHOf3zn2/Ciu8TXyXDHcjtwD3hOs3ss/Qh/VAwlW/DMcuiXg5btgF+XMA== eslint-plugin-node@~9.1.0: version "9.1.0" @@ -3895,12 +3922,12 @@ graphql-request@~1.8.2: dependencies: cross-fetch "2.2.2" -graphql-shield@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-6.0.2.tgz#3ebad8faacbada91b8e576029732e91b5a041c7f" - integrity sha512-3qV2qjeNZla1Fyg6Q2NR5J9AsMaNePLbUboOwhRXB7IcMnTnrxSiVn2R//8VnjnmBjF9rcvgAIAvETZ8AKGfsg== +graphql-shield@~6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-6.0.3.tgz#a79ca8b2fe58fb9558ffc0e64fa8aa19f63af1b3" + integrity sha512-+yVT/dRWsRqeJOTHcEElJVfvIRPrhBqPlg5FHLmSkWNdGMR4AFqAQGrJteuZuNDvJ3bt380CZ96Bvf4J9hUpKA== dependencies: - "@types/yup" "0.26.20" + "@types/yup" "0.26.21" lightercollective "^0.3.0" object-hash "^1.3.1" yup "^0.27.0" @@ -5986,6 +6013,11 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-keys@^1.0.11: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + object-keys@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" @@ -6003,6 +6035,16 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" diff --git a/cypress/features.md b/cypress/features.md index eb8292c3b..3adfd8771 100644 --- a/cypress/features.md +++ b/cypress/features.md @@ -16,7 +16,7 @@ The following features will be implemented. This gets done in three steps: ### User Account -[Cucumber Features](./integration/user_account) +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/user_account) * Sign-up * Agree to Data Privacy Statement @@ -34,7 +34,7 @@ The following features will be implemented. This gets done in three steps: ### User Profile -[Cucumber Features](./integration/user_profile) +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/user_profile) * Upload and Change Avatar * Upload and Change Profile Picture @@ -59,7 +59,7 @@ The following features will be implemented. This gets done in three steps: ### Posts -[Cucumber Features](./integration/post/) +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/post) * Creating Posts * Persistent Links @@ -78,13 +78,13 @@ The following features will be implemented. This gets done in three steps: ### Comments -* Creating Comments +* Creating Comments * Deleting Comments * Editing Comments * Upvote comments of others ### Notifications -[Cucumber features](./integration/notifications) +[Cucumber features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/notifications) * User @-mentionings * Notify authors for comments @@ -94,12 +94,12 @@ The following features will be implemented. This gets done in three steps: * Show Posts by Tiles * Show Posts as List -* Filter by Category \(Health and Wellbeing, Global Peace & Non-Violence, ...\) +* Filter by Category \(Health and Wellbeing, Global Peace & Non-Violence, ...\) * Filter by Mood \(Funny, Happy, Surprised, Cry, Angry, ...\) * Filter by Source \(Connections, Following, Individuals, Non-Profits, ...\) * Filter by Posts & Tools \(Post, Events, CanDos, ...\) * Filter by Format Type \(Text, Pictures, Video, ...\) -* Extended Filter \(Continent, Country, Language, ...\) +* Extended Filter \(Continent, Country, Language, ...\) * Sort Posts by Date * Sort Posts by Shouts * Sort Posts by most Comments @@ -116,7 +116,7 @@ The following features will be implemented. This gets done in three steps: ### Search -[Cucumber Features](./integration/search) +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/search) * Search for Categories * Search for Tags @@ -186,13 +186,13 @@ The following features will be implemented. This gets done in three steps: ### More Info -Shows autmatically releated information for existing post. +Shows automatically related information for existing post. * Show related Posts * Show Pros and Cons * Show Bestlist * Show Votes -* Link to corresponding Chatroom +* Link to corresponding Chatroom ### Take Action @@ -237,7 +237,7 @@ Shows automatically related actions for existing post. ### Moderation -[Cucumber Features](./integration/moderation) +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/moderation) * Report Button for users for doubtful Content * Moderator Panel @@ -262,7 +262,7 @@ Shows automatically related actions for existing post. ### Internationalization -[Cucumber Features](./integration/internationalization) +[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/internationalization) * Frontend UI * Backend Error Messages @@ -276,4 +276,3 @@ Shows automatically related actions for existing post. * Receiving Undo and Delete Activities for Articles and Notes * Serving Webfinger records and Actor Objects * Serving Followers, Following and Outbox collections - diff --git a/deployment/human-connection/deployment-neo4j.yaml b/deployment/human-connection/deployment-neo4j.yaml index 3c4887194..afc03ca0d 100644 --- a/deployment/human-connection/deployment-neo4j.yaml +++ b/deployment/human-connection/deployment-neo4j.yaml @@ -32,21 +32,9 @@ value: 1G - name: NEO4J_dbms_memory_heap_max__size value: 1G - - name: NEO4J_URI - valueFrom: - configMapKeyRef: - name: configmap - key: NEO4J_URI - - name: NEO4J_USER - valueFrom: - configMapKeyRef: - name: configmap - key: NEO4J_USER - - name: NEO4J_AUTH - valueFrom: - configMapKeyRef: - name: configmap - key: NEO4J_AUTH + envFrom: + - configMapRef: + name: configmap ports: - containerPort: 7687 - containerPort: 7474 diff --git a/deployment/human-connection/templates/configmap.template.yaml b/deployment/human-connection/templates/configmap.template.yaml index 762901ae8..2b7ffeeb8 100644 --- a/deployment/human-connection/templates/configmap.template.yaml +++ b/deployment/human-connection/templates/configmap.template.yaml @@ -10,7 +10,8 @@ GRAPHQL_URI: "http://nitro-backend.human-connection:4000" MOCKS: "false" NEO4J_URI: "bolt://nitro-neo4j.human-connection:7687" - NEO4J_USER: "neo4j" + NEO4J_USERNAME: "neo4j" + NEO4J_PASSWORD: "neo4j" NEO4J_AUTH: "none" CLIENT_URI: "https://nitro-staging.human-connection.org" metadata: diff --git a/edit-this-documentation.md b/edit-this-documentation.md index b01ace78f..cd83ac7a6 100644 --- a/edit-this-documentation.md +++ b/edit-this-documentation.md @@ -1,12 +1,6 @@ # Edit this Documentation -Go to the section and theme you want to change: On the left navigator. - -Click **Edit on GitHub** on the right. - -On the **Issue** tab you’ll find the open issues. Read what need to be done by clicking on the issue you like to fix. - -By going backwards in the browser **\(!\)**, again go to the **Code** tab. +Find the [**table of contents** for this documentation on GitHub](https://github.com/Human-Connection/Human-Connection/blob/master/SUMMARY.md) and navigate to the file you need to update. Click on the **edit pencil** on the right side directly above the text to edit this file on your fork of Human Connection \(HC\). @@ -14,7 +8,7 @@ You can see a preview of your changes by clicking the **Preview changes** tab as If you are ready, fill in the **Propose file change** at the end of the webpage. -After that you have to send your change to the HC basis with a pull request. Here make a comment which issue you have fixed. At least the number. +After that you have to send your change to the HC basis with a pull request. Here make a comment which issue you have fixed. (If you are working on one of our [open issues](https://github.com/Human-Connection/Human-Connection/issues) please include the number.) ## Markdown your documentation @@ -117,4 +111,3 @@ TODO: How to modify screenshots in Linux ... {% endhint %} {% endtab %} {% endtabs %} - diff --git a/neo4j/README.md b/neo4j/README.md index 379a89eec..78c4bc62e 100644 --- a/neo4j/README.md +++ b/neo4j/README.md @@ -20,7 +20,7 @@ for an interactive cypher shell and a visualization of the graph. ## Installation without Docker -Install community edition of [Neo4J]() along with the plugin +Install the community edition of [Neo4j](https://neo4j.com/) along with the plugin [Apoc](https://github.com/neo4j-contrib/neo4j-apoc-procedures) on your system. To do so, go to [releases](https://neo4j.com/download-center/#releases), choose @@ -28,7 +28,13 @@ To do so, go to [releases](https://neo4j.com/download-center/#releases), choose and unpack the files. Download [Neo4j Apoc](https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases) -and drop the file into the `plugins` folder of the just extracted Neo4j-Server. +and drop the `.jar` file into the `plugins` folder of the just extracted Neo4j-Server. + +Then make sure to allow Apoc procedures by adding the following line to your Neo4j configuration \(`conf/neo4j.conf`\): + +``` +dbms.security.procedures.unrestricted=apoc.* +``` ### Alternatives @@ -59,6 +65,6 @@ $ cp .env.template .env $ ./db_setup.sh ``` -Otherwise if you don't have `cypher-shell` available, simply copy the cypher -statements [from the script](./neo4j/db_setup.sh) and paste the scripts into your -database [browser frontend](http://localhost:7474). +Otherwise, if you don't have `cypher-shell` available, copy the cypher +statements [from the `db_setup.sh` script](https://github.com/Human-Connection/Human-Connection/blob/master/neo4j/db_setup.sh) and paste the scripts into your +[database browser frontend](http://localhost:7474). diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 9b7f1329c..d3a17a3aa 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.5-alpine as base +FROM node:12.6-alpine as base LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)" EXPOSE 3000 diff --git a/webapp/README.md b/webapp/README.md index ce27eca2f..604c7e6ba 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -41,4 +41,4 @@ All reusable Components \(for example avatar\) should be done inside the [Nitro- More information can be found here: [https://github.com/Human-Connection/Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) -If you need to change something in the styleguide and want to see the effects on the frontend immediately, then we have you covered. You need to clone the styleguide to the parent directory `../Nitro-Styleguide` and run `yarn && yarn run dev`. After that you run `yarn run dev:styleguide` instead of `yarn run dev` and you will see your changes reflected inside the fronten! +If you need to change something in the styleguide and want to see the effects on the frontend immediately, then we have you covered. You need to clone the styleguide to the parent directory `../Nitro-Styleguide` and run `yarn && yarn run dev`. After that you run `yarn run dev:styleguide` instead of `yarn run dev` and you will see your changes reflected inside the frontend! diff --git a/webapp/assets.md b/webapp/assets.md index 06786539d..6ac7dc388 100644 --- a/webapp/assets.md +++ b/webapp/assets.md @@ -1,8 +1,5 @@ # ASSETS -**This directory is not required, you can delete it if you don't want to use it.** - -This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. +This directory contains your un-compiled assets such as LESS, SASS, or JavaScript – in our case SCSS styles. More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). - diff --git a/webapp/components.md b/webapp/components.md index be43ae454..92b3dd1fb 100644 --- a/webapp/components.md +++ b/webapp/components.md @@ -1,8 +1,5 @@ # COMPONENTS -**This directory is not required, you can delete it if you don't want to use it.** - The components directory contains your Vue.js Components. _Nuxt.js doesn't supercharge these components._ - diff --git a/webapp/components/ContributionForm/ContributionForm.spec.js b/webapp/components/ContributionForm/ContributionForm.spec.js index 0813d16f0..3d136ff4b 100644 --- a/webapp/components/ContributionForm/ContributionForm.spec.js +++ b/webapp/components/ContributionForm/ContributionForm.spec.js @@ -47,7 +47,9 @@ describe('ContributionForm.vue', () => { }, }, }) - .mockRejectedValue({ message: 'Not Authorised!' }), + .mockRejectedValue({ + message: 'Not Authorised!', + }), }, $toast: { error: jest.fn(), @@ -74,12 +76,26 @@ describe('ContributionForm.vue', () => { getters, }) const Wrapper = () => { - return mount(ContributionForm, { mocks, localVue, store, propsData }) + return mount(ContributionForm, { + mocks, + localVue, + store, + propsData, + }) } beforeEach(() => { wrapper = Wrapper() - wrapper.setData({ form: { languageOptions: [{ label: 'Deutsch', value: 'de' }] } }) + wrapper.setData({ + form: { + languageOptions: [ + { + label: 'Deutsch', + value: 'de', + }, + ], + }, + }) }) describe('CreatePost', () => { diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue index c6bb2cdc4..593ff2dc6 100644 --- a/webapp/components/ContributionForm/ContributionForm.vue +++ b/webapp/components/ContributionForm/ContributionForm.vue @@ -11,7 +11,12 @@ - + +
{{ $t('actions.cancel') }} import gql from 'graphql-tag' -import HcEditor from '~/components/Editor' +import HcEditor from '~/components/Editor/Editor' import orderBy from 'lodash/orderBy' import locales from '~/locales' import PostMutations from '~/graphql/PostMutations.js' @@ -95,6 +101,7 @@ export default { disabled: false, slug: null, users: [], + hashtags: [], } }, watch: { @@ -193,17 +200,34 @@ export default { apollo: { User: { query() { - return gql(`{ - User(orderBy: slug_asc) { - id - slug + return gql` + { + User(orderBy: slug_asc) { + id + slug + } } - }`) + ` }, result(result) { this.users = result.data.User }, }, + Tag: { + query() { + return gql` + { + Tag(orderBy: name_asc) { + id + name + } + } + ` + }, + result(result) { + this.hashtags = result.data.Tag + }, + }, }, } diff --git a/webapp/components/Editor/spec.js b/webapp/components/Editor/Editor.spec.js similarity index 94% rename from webapp/components/Editor/spec.js rename to webapp/components/Editor/Editor.spec.js index b982d941d..d457609bd 100644 --- a/webapp/components/Editor/spec.js +++ b/webapp/components/Editor/Editor.spec.js @@ -1,5 +1,5 @@ import { mount, createLocalVue } from '@vue/test-utils' -import Editor from './' +import Editor from './Editor' import Vuex from 'vuex' import Styleguide from '@human-connection/styleguide' @@ -36,7 +36,9 @@ describe('Editor.vue', () => { propsData, localVue, sync: false, - stubs: { transition: false }, + stubs: { + transition: false, + }, store, })) } diff --git a/webapp/components/Editor/index.vue b/webapp/components/Editor/Editor.vue similarity index 67% rename from webapp/components/Editor/index.vue rename to webapp/components/Editor/Editor.vue index 84649f436..4413bfa0d 100644 --- a/webapp/components/Editor/index.vue +++ b/webapp/components/Editor/Editor.vue @@ -1,18 +1,51 @@